Query di unione (merge)
Le query di unione (merge) rappresentano un tipo speciale di query ibrida. Sebbene la loro sintassi sia definita nella specifica SQL 2003, praticamente nessun database supporta la sintassi standard. Tuttavia, la maggior parte fornisce un’implementazione alternativa con una sintassi specifica per il database. Il query builder di merge in Drupal astrae il concetto di query di unione in un oggetto strutturato, che può essere compilato con la sintassi appropriata per ciascun database. Talvolta vengono chiamate query "UPSERT", una combinazione di UPDATE e INSERT.
In senso generale, una query di merge è una combinazione di una query di inserimento e di una query di aggiornamento. Se una determinata condizione è soddisfatta, ad esempio se esiste già una riga con una chiave primaria specificata, viene eseguita una query di aggiornamento. In caso contrario, viene eseguita una query di inserimento. Nel caso più comune è equivalente a:
if ($connection->query("SELECT COUNT(*) FROM {example} WHERE id = :id", [':id' => $id])->fetchField()) {
// Eseguire un update usando WHERE id = $id
}
else {
// Eseguire un insert, inserendo $id come id
}
L’implementazione effettiva varia notevolmente da database a database. Nota che, sebbene le query di merge siano concettualmente un’operazione atomica, potrebbero esserlo o meno realmente a seconda dell’implementazione del database specifico. Ad esempio, l’implementazione MySQL è una singola query atomica, mentre il caso degenerato (sopra) non lo è.
Le idiomi più comuni per le query di merge sono elencati di seguito.
Semplicemente impostalo
$connection->merge('example')
->key('name', $name)
->fields([
'field1' => $value1,
'field2' => $value2,
])
->execute();
Nell’esempio sopra indichiamo alla query di lavorare con la tabella «example». Poi specifichiamo un campo chiave 'name' con il valore $name. Successivamente, indichiamo un array di valori da impostare.
Se esiste già una riga in cui il campo «name» ha il valore $name, allora i campi field1 e field2 verranno impostati sui rispettivi valori in quella riga esistente. Se tale riga non esiste, ne verrà creata una con name uguale a $name, field1 uguale a $value1 e field2 uguale a $value2. Così, alla fine della query, il risultato finale sarà lo stesso indipendentemente dal fatto che la riga esistesse o meno.
Set condizionale
In alcuni casi potrebbe essere necessario impostare i valori in modo diverso a seconda che il record esista già o meno, come definito dai campi key(). Ci sono due modi per farlo.
$connection->merge('example')
->insertFields([
'field1' => $value1,
'field2' => $value2,
])
->updateFields([
'field1' => $alternate1,
])
->key('name', $name)
->execute();
L’esempio sopra si comporterà come il primo, tranne per il fatto che se il record esiste già e viene aggiornato, per field1 verrà impostato $alternate1 invece di $value1, e field2 non sarà modificato. Il metodo updateFields() accetta o un array associativo di valori, oppure due array numerici paralleli: uno di campi e uno di valori, che devono essere nello stesso ordine.
$connection->merge('example')
->key('name', $name)
->fields([
'field1' => $value1,
'field2' => $value2,
])
->expression('field1', 'field1 + :inc', [':inc' => 1])
->execute();
In questo esempio, se il record esiste già, per field1 verrà impostato il suo valore corrente più 1. Questo lo rende molto utile per le «query contatore», quando vuoi incrementare un contatore nel database ogni volta che si verifica un determinato evento. field2 continuerà ad avere lo stesso valore indipendentemente dall’esistenza o meno del record.
Nota che expression() può essere chiamato più volte, una per ciascun campo che deve essere impostato a un’espressione se il record esiste già. Il primo parametro è il campo, il secondo è un frammento SQL che specifica l’espressione a cui il campo deve essere impostato, e il terzo parametro opzionale è un array di valori placeholder da inserire nell’espressione.
Inoltre, non è necessario che il campo usato in expression() sia già presente in fields().
Considerando l’API sopra, è possibile definire query che non hanno senso logico, ad esempio se per un campo viene impostato che sia ignorato e contemporaneamente impostato con un’espressione se il record esiste già. Per ridurre al minimo possibili errori, vengono applicate le seguenti regole:
- Se per un campo è impostato expression(), questo ha la precedenza su updateFields().
- Se i valori sono specificati in updateFields(), solo quei campi verranno modificati se il record esiste già. I campi non specificati in updateFields() non verranno toccati.
Nota che è comunque possibile definire query che non hanno senso. Lo sviluppatore deve assicurarsi di non scrivere query prive di logica, poiché il comportamento in questi casi non è definito.