在 Drupal 7 中操作数据库 - 第11课 - 合并查询(MERGE)
合并查询是一种特殊的混合类型查询。尽管这种语法在 SQL 2003 中被定义,但实际上几乎没有数据库原生支持该语法。然而,大多数数据库都提供了各自特定的替代实现。Drupal 中的合并查询构造器(Merge Query Builder)将“合并查询”的概念抽象为对象结构,这样系统可以根据不同数据库的特性分别编译对应的查询语句。
总体而言,合并查询是 INSERT 与 UPDATE 查询的组合。如果满足某个条件(例如表中存在指定键的记录),则执行一个查询;否则执行另一个查询。大多数情况下,它等价于以下逻辑:
<?php if (db_result(db_query("SELECT COUNT(*) FROM {example} WHERE id=:id", array(':id' => $id))->fetchField())) { // 如果记录存在,执行 UPDATE 查询 WHERE id = $id } else { // 如果记录不存在,执行 INSERT 查询,插入 id = $id } ?>
在实际中,不同数据库的实现方式略有不同。请注意,虽然在概念上合并查询应是原子操作,但其原子性是否真正生效取决于数据库的实现。例如,MySQL 的实现是原子的,而上面的伪代码逻辑则不是。
合并查询的通用形式如下:
基础示例
<?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。
设置条件更新
在某些情况下,我们希望根据记录是否存在来设置不同的值。有两种方法实现这一点:
<?php db_merge('example') ->key(array('name' => $name)) ->fields(array( 'field1' => $value1, 'field2' => $value2, )) ->updateFields(array( 'field1' => $alternate1, )) ->execute(); ?>
上述示例与第一个类似,只是当记录已存在时,我们将 field1 更新为 $alternate1。如果记录不存在,则创建新记录,字段值为 field1 = $value1、field2 = $value2。
updateFields() 方法接受一个关联数组,也可以接受两个等长的数值数组(一个为字段名,一个为对应的值)。
<?php db_merge('example') ->key(array('name' => $name)) ->fields(array( 'field1' => $value1, 'field2' => $value2, )) ->expression('field1', 'field1 + :inc', array(':inc' => 1)) ->execute(); ?>
在此示例中,如果记录存在,则 field1 的值在原有基础上加 1。这种方式常用于“计数器更新”场景,例如统计事件发生次数。若记录不存在,则插入新数据,其中 field1 和 field2 的值按定义设置。
请注意,expression() 可以被多次调用,每个字段调用一次,用于定义更新时的表达式。其第一个参数为字段名,第二个参数为 SQL 表达式片段,第三个可选参数为替换变量数组。
同时,被 expression() 使用的字段不一定要出现在 fields() 中。
限制更新
<?php db_merge('example') ->key(array('name' => $name)) ->fields(array( 'field1' => $value1, 'field2' => $value2, )) ->updateExcept('field1') ->execute(); ?>
updateExcept() 方法接受字段数组或多个独立字段名参数。被列出的字段在记录存在时不会被更新。
例如,如果表中存在 name = $name 的记录,则仅更新 field2 的值为 $value2,而 field1 保持不变。如果记录不存在,则插入一条新记录并设置所有字段。
优先级规则
上述 API 提供了高度灵活的功能,但如果使用不当,也可能导致逻辑矛盾的查询。例如同时在同一字段上定义 ignore 和 expression()。为了避免这种情况,Drupal 遵循以下规则:
- 如果字段定义了 expression(),则其优先级高于 update() 和 updateExcept()。
- 如果字段同时出现在 update() 和 updateExcept() 中,则 updateExcept() 被忽略。
- 如果定义了 update(),则只有这些字段会在记录存在时更新;未列出的字段不会被处理。
请注意,理论上仍然可能定义出无意义的查询,因此应谨慎使用。