Requêtes de fusion (merge)
Les requêtes de fusion représentent un type particulier de requête hybride. Bien que leur syntaxe soit définie dans la spécification SQL 2003, pratiquement aucune base de données ne supporte la syntaxe standard. Cependant, la plupart fournissent une implémentation alternative utilisant une syntaxe spécifique à la base de données. Le constructeur de requêtes de fusion dans Drupal abstrait le concept de requête de fusion en un objet structuré qui peut être compilé avec la syntaxe appropriée pour chaque base de données. Elles sont parfois appelées requêtes "UPSERT", une combinaison de UPDATE et INSERT.
En termes généraux, une requête de fusion est une combinaison d'une requête d'insertion et d'une requête de mise à jour. Si une condition est remplie, par exemple, une ligne avec une clé primaire donnée existe déjà, une requête de mise à jour est exécutée. Sinon, une requête d'insertion est effectuée. Dans le cas le plus courant, cela équivaut à :
if ($connection->query("SELECT COUNT(*) FROM {example} WHERE id = :id", [':id' => $id])->fetchField()) { // Exécuter une mise à jour WHERE id = $id } else { // Exécuter une insertion, insérant $id pour id }
L'implémentation réelle varie fortement d'une base de données à l'autre. Notez que même si conceptuellement les requêtes de fusion sont des opérations atomiques, elles peuvent ou non être réellement atomiques selon l'implémentation spécifique de la base de données. Par exemple, l'implémentation MySQL est une requête atomique distincte, mais le cas dégénéré ci-dessus ne l'est pas.
Les idiomes les plus courants pour les requêtes Merge sont listés ci-dessous.
Faites-le simplement
$connection->merge('example') ->key('name', $name) ->fields([ 'field1' => $value1, 'field2' => $value2, ]) ->execute();
Dans l'exemple ci-dessus, nous indiquons à la requête de travailler avec la table « example ». Ensuite, nous spécifions un champ clé 'name' avec la valeur $name. Puis, nous spécifions un tableau de valeurs à définir.
Si une ligne existe déjà avec le champ « name » égal à $name, alors les champs field1 et field2 seront définis aux valeurs correspondantes dans cette ligne existante. Si une telle ligne n'existe pas, une nouvelle ligne sera créée où name a la valeur $name, field1 a la valeur $value1 et field2 la valeur $value2. Ainsi, à la fin de la requête, le résultat final est le même qu'il y ait eu ou non une ligne existante.
Assignation conditionnelle
Dans certains cas, il peut être nécessaire de définir les valeurs différemment selon que l'enregistrement existe déjà, comme défini par les champs de key(). Il y a deux façons de faire cela.
$connection->merge('example') ->insertFields([ 'field1' => $value1, 'field2' => $value2, ]) ->updateFields([ 'field1' => $alternate1, ]) ->key('name', $name) ->execute();
L'exemple ci-dessus se comporte de la même manière que le premier, sauf que si l'enregistrement existe déjà et est mis à jour, field1 sera défini à $alternate1 au lieu de $value1, et field2 ne sera pas affecté. La méthode updateFields() accepte soit un tableau associatif de valeurs, soit deux tableaux numériques parallèles, un pour les champs, un pour les valeurs, qui doivent être dans le même ordre.
$connection->merge('example') ->key('name', $name) ->fields([ 'field1' => $value1, 'field2' => $value2, ]) ->expression('field1', 'field1 + :inc', [':inc' => 1]) ->execute();
Dans cet exemple, si l'enregistrement existe déjà, field1 sera défini à sa valeur actuelle plus 1. Cela rend cette méthode très utile pour des "requêtes de comptage" où vous souhaitez incrémenter un compteur dans la base de données chaque fois qu'un certain événement se produit. field2 aura toujours la même valeur, que l'enregistrement existe ou non.
Notez que expression() peut être appelée plusieurs fois, une fois pour chaque champ qui doit être défini avec une expression si l'enregistrement existe déjà. Le premier paramètre est le champ, le deuxième est un fragment SQL indiquant l'expression à assigner au champ, et le troisième paramètre optionnel est un tableau de valeurs de remplacement à insérer dans l'expression.
Il n'est pas non plus nécessaire que le champ utilisé dans expression() soit déjà présent dans fields().
Étant donné l'API ci-dessus, il est tout à fait possible de définir des requêtes qui n'ont pas de sens logique, par exemple si un champ est à la fois ignoré et défini avec une expression lorsque l'enregistrement existe. Pour minimiser les erreurs possibles, les règles suivantes s'appliquent :
- Si expression() est défini pour un champ, il prend la priorité sur updateFields().
- Si des valeurs sont spécifiées dans updateFields(), seuls ces champs seront modifiés si l'enregistrement existe déjà. Les champs non spécifiés dans updateFields() ne seront pas affectés.
Notez qu'il est toujours possible de définir des requêtes sans sens. Le développeur doit s'assurer qu'aucune requête absurde n'est soumise, car le comportement dans ce cas n'est pas défini.