Merge query は少し特殊なクエリです。SQL 2003 の仕様では定義されているものですが、ほとんどのデータベースは、標準の構文をサポートしていません。ほとんどがそれぞれデータベース固有の構文の実装で代替されています。Drupal のマージクエリは抽象化されたオブジェクトであり、それぞれのデータベースにおける適切な構文にコンパイルすることが出来ます。これは「UPDATE」と「INSERT」を足し合わせたようなもので、しばしば「UPSERT」と呼ばれます。
一般的に、マージクエリは INSERT クエリと UPDATE クエリを合わせたものです。指定したプライマリキーを持つレコードが既に存在する場合は、それを更新し、存在しない場合は、新たなレコードとしてインサートします。つまり次のような構文と同等です。
<?php if (db_query("SELECT COUNT(*) FROM {example} WHERE id = :id", array(':id' => $id))->fetchField()) { // Run an update using WHERE id = $id } else { // Run an insert, inserting $id for id } ?>
実際にはデータベースによって大きく異なります。Merge query は不可分操作の概念ですが、データベースによっては厳密には不可分操作でない場合があります。上の例は不可分操作ではありませんが、MySQL では不可分操作として実装されます。
Merge query の記述例は以下のようになります。
<?php db_merge('example') ->key(array('name' => $name)) ->fields(array( 'field1' => $value1, 'field2' => $value2, )) ->execute(); ?>
この例では、「example」テーブルを操作します。キー「name」の値に $name を指定し、そしてそこに挿入したい値の配列を渡します。
「name」フィールドの値に $name を持つレコードが既に存在する場合、field1、field2 のフィールドはそれぞれ対応する値がセットされアップデートされます。そのようなレコードがまだ存在しない場合、「name」フィールドの値に $name、「field1」フィールドの値に $value1、「field2」フィールドの値に $value2 を持つレコードが新たにインサートされます。このように、既存のレコードがあるか否かに関わらず、結果的には同一のレコードが作られます。
ときに、キーの値を持つレコードが存在するかどうかによって、どの値を挿入するかが異なる場合があります。その場合は 2 つの方法があります。
<?php db_merge('example') ->insertFields(array( 'field1' => $value1, 'field2' => $value2, )) ->updateFields(array( 'field1' => $alternate1, )) ->key(array('name' => $name)) ->execute(); ?>
上の例では、該当するレコードが存在しない場合においては最初の例と同じですが、既に存在している場合は「field1」には $value1 ではなく $alternate1 という値をセットし、「field2」の値はそのままでアップデートします。updateField() メソッドはフィールドとその値を示す連想配列か、もしくはフィールドの配列と、その値の配列からなる添字配列を受け付けます。
<?php db_merge('example') ->key(array('name' => $name)) ->fields(array( 'field1' => $value1, 'field2' => $value2, )) ->expression('field1', 'field1 + :inc', array(':inc' => 1)) ->execute(); ?>
この例では、レコードが既に存在する場合、field1 の値には既存の値に 1 を足した値がセットされます。これは、たとえば何らかのイベントに応じて値が増えていくような「カウンター」の動作を実装する際に便利なものです。field2 はこの場合既存のレコードがあるか否かに関わらず、同じ値がセットされます。
expression() メソッドは、それぞれのフィールドに対して、レコードが存在する場合にセットする expression をひとつずつ、複数設置することが出来ます。ひとつめのパラメータは対象のフィールド、ふたつめはそのフィールドにセットする expression を示す SQL フラグメント、三つ目のパラメータはオプションで、expression にのプレイスホルダーに代入する値を示す配列です。
expression() で指定するフィールドは必ずしも fields() にセットされている必要はありません。
この API では、論理的にまったくおかしなクエリも発行できてしまいます。レコードが存在する場合に、あるフィールドに対して値をそのままにしておくのか、expression をセットするのか。潜在的なエラーを回避するため、次のルールが適用されます。
それでもなお、おかしなクエリが発行できてしまいます。そのあたりの確認をしっかりするかどうかは開発者次第です。