Samenvoegingsquery’s (merge)
Samenvoegingsquery’s zijn een speciaal type hybride query. Hoewel de syntaxis hiervoor is gedefinieerd in de SQL 2003-specificatie, ondersteunt vrijwel geen enkele database de standaardsyntaxis. De meeste databases bieden echter een alternatieve implementatie met een database-specifieke syntaxis. De merge query builder in Drupal abstraheert het concept van een samenvoegingsquery naar een gestructureerd object dat kan worden gecompileerd naar de juiste syntaxis voor elke database. Ze worden soms ook wel "UPSERT"-query’s genoemd, een combinatie van UPDATE en INSERT.
In algemene zin is een merge-query een combinatie van een insert-query en een update-query. Als een bepaalde voorwaarde waar is, bijvoorbeeld als er al een rij met een bepaalde primaire sleutel bestaat, wordt er een update-query uitgevoerd. Zo niet, dan wordt er een insert-query uitgevoerd. In het meest voorkomende geval komt dit overeen met:
if ($connection->query("SELECT COUNT(*) FROM {example} WHERE id = :id", [':id' => $id])->fetchField()) {
// Run een update met WHERE id = $id
}
else {
// Run een insert, waarbij $id voor id wordt ingevoegd
}
De daadwerkelijke implementatie verschilt sterk per database. Merk op dat hoewel merge-query’s conceptueel een atomaire operatie zijn, ze al dan niet echt atomair zijn, afhankelijk van de implementatie van de specifieke database. Zo is de MySQL-implementatie een enkele atomaire query, maar het hierboven getoonde uitgewerkte geval is dat niet.
De meest gebruikte idiomen voor merge-query’s staan hieronder.
Gewoon instellen
$connection->merge('example')
->key('name', $name)
->fields([
'field1' => $value1,
'field2' => $value2,
])
->execute();
In het bovenstaande voorbeeld geven we aan dat de query moet werken met de tabel "example". Vervolgens specificeren we één sleutelveld 'name' met de waarde $name. Daarna geven we een array van waarden op om in te stellen.
Als er al een rij bestaat waarin het veld "name" de waarde $name heeft, dan worden de velden field1 en field2 in die bestaande rij ingesteld op de overeenkomstige waarden. Als zo’n rij niet bestaat, wordt er een nieuwe aangemaakt met name = $name, field1 = $value1 en field2 = $value2. Het eindresultaat van de query blijft dus hetzelfde, ongeacht of de rij al bestond of niet.
Voorwaardelijke set
In sommige gevallen wil je waarden verschillend instellen, afhankelijk van of een record al bestaat zoals gedefinieerd door de velden in key(). Er zijn twee manieren om dit te doen.
$connection->merge('example')
->insertFields([
'field1' => $value1,
'field2' => $value2,
])
->updateFields([
'field1' => $alternate1,
])
->key('name', $name)
->execute();
Het bovenstaande voorbeeld gedraagt zich hetzelfde als het eerste, behalve dat als een record al bestaat en we het updaten, field1 wordt ingesteld op $alternate1 in plaats van $value1, en field2 wordt niet beïnvloed. De methode updateFields() accepteert ofwel één associatieve array met waarden, ofwel twee parallelle numerieke arrays: één met velden en één met waarden, die in dezelfde volgorde moeten staan.
$connection->merge('example')
->key('name', $name)
->fields([
'field1' => $value1,
'field2' => $value2,
])
->expression('field1', 'field1 + :inc', [':inc' => 1])
->execute();
In dit voorbeeld, als een record al bestaat, wordt field1 ingesteld op zijn huidige waarde plus 1. Dit maakt het erg nuttig voor "counter queries", waarbij je een teller in de database wilt verhogen telkens wanneer een bepaald evenement plaatsvindt. Field2 behoudt dezelfde waarde, ongeacht of het record bestaat of niet.
Merk op dat expression() meerdere keren kan worden aangeroepen, één keer per veld dat moet worden ingesteld op een expressie als het record al bestaat. De eerste parameter is het veld, de tweede is een stukje SQL dat de expressie specificeert waaraan het veld moet worden toegewezen, en de optionele derde parameter is een array met placeholderwaarden om in de expressie in te voegen.
Het is ook niet vereist dat een veld dat in expression() wordt gebruikt al aanwezig is in fields().
Gezien de bovenstaande API is het zeker mogelijk om query’s te definiëren die niet logisch zijn, bijvoorbeeld als een veld zowel wordt genegeerd als ingesteld op een expressie wanneer het record al bestaat. Om mogelijke fouten te minimaliseren, gelden de volgende regels:
- Als er een
expression()voor een veld is ingesteld, heeft dit prioriteit bovenupdateFields(). - Als er waarden zijn opgegeven in
updateFields(), worden alleen die velden gewijzigd als het record al bestaat. Velden die niet zijn opgegeven inupdateFields()worden niet beïnvloed.
Merk op dat het nog steeds mogelijk is om query’s te definiëren die niet logisch zijn. De ontwikkelaar moet ervoor zorgen dat er geen zinloze query wordt opgegeven, aangezien het gedrag in dat geval niet gedefinieerd is.