logo

额外区块类型 (EBT) - 全新的布局构建器体验❗

额外区块类型 (EBT) - 样式化、可定制的区块类型:幻灯片、标签页、卡片、手风琴等更多类型。内置背景、DOM Box、JavaScript 插件的设置。立即体验布局构建的未来。

演示 EBT 模块 下载 EBT 模块

❗额外段落类型 (EPT) - 全新的 Paragraphs 体验

额外段落类型 (EPT) - 类似的基于 Paragraph 的模块集合。

演示 EPT 模块 滚动

滚动
01/10/2025, by Ivan

合并查询是一种特殊类型的混合查询。虽然它们的语法在 SQL 2003 规范中被定义,但几乎没有数据库支持该标准语法。然而,大多数数据库提供了使用数据库特定语法的替代实现。Drupal 中的合并查询构建器将合并查询的概念抽象为一个结构化对象,可以根据每个数据库生成相应的语法。有时它们也被称为 “UPSERT” 查询,即 UPDATE 和 INSERT 的组合。

从总体上看,合并查询就是插入和更新查询的结合。如果满足某个条件,例如具有指定主键的行已经存在,则会执行更新查询。如果不存在,则执行插入查询。在最常见的情况下,这等同于:

if ($connection->query("SELECT COUNT(*) FROM {example} WHERE id = :id", [':id' => $id])->fetchField()) {
  // 使用 WHERE id = $id 执行更新
}
else {
  // 执行插入,为 id 插入 $id
}

实际实现因数据库而异。请注意,虽然合并查询在概念上是一个原子操作,但它是否真正是原子的取决于具体数据库的实现。例如,MySQL 的实现是单独的原子查询,但上面的简化情况则不是。

合并查询最常见的用法如下所示。

直接设置

$connection->merge('example')
  ->key('name', $name)
  ->fields([
      'field1' => $value1,
      'field2' => $value2,
  ])
  ->execute();

在上面的示例中,我们指定了查询操作的表为 “example”。接着我们指定了一个键字段 'name',其值为 $name。然后我们提供了一个要设置的值数组。

如果已经存在一条 name 字段等于 $name 的记录,那么该记录的 field1 和 field2 会被更新为相应的值。如果不存在这样的记录,就会新建一条记录,其中 name = $name,field1 = $value1,field2 = $value2。因此,无论记录是否存在,最终结果保持一致。

条件设置

在某些情况下,可能需要根据记录是否存在(由 key() 字段决定)来以不同方式设置值。可以通过两种方式来实现。

$connection->merge('example')
  ->insertFields([
      'field1' => $value1,
      'field2' => $value2,
  ])
  ->updateFields([
    'field1' => $alternate1,
  ])
  ->key('name', $name)
  ->execute();

上面的示例与第一个类似,但如果记录已存在并且我们进行更新,则 field1 的值会设置为 $alternate1 而不是 $value1,field2 则不受影响。updateFields() 方法可以接受一个关联数组,也可以接受两个平行的数字索引数组,一个表示字段,一个表示值,且顺序必须对应。

$connection->merge('example')
  ->key('name', $name)
  ->fields([
      'field1' => $value1,
      'field2' => $value2,
  ])
  ->expression('field1', 'field1 + :inc', [':inc' => 1])
  ->execute();

在这个示例中,如果记录已经存在,field1 的值会在原有基础上加 1。这对于“计数类查询”非常有用,例如当某个事件发生时你希望数据库中的计数器递增一次。field2 无论记录是否存在都会被设置为相同的值。

请注意,expression() 可以被多次调用,每个字段一次,用于定义当记录已存在时该字段的表达式赋值。第一个参数是字段名,第二个参数是 SQL 表达式片段,指定字段应赋予的表达式,第三个可选参数是一个占位符数组,用于替换表达式中的值。

并不要求 expression() 使用的字段必须已经出现在 fields() 中。

由于以上 API 的灵活性,完全可能定义一些没有逻辑意义的查询,例如如果为某个字段同时指定了忽略和使用表达式赋值。为了最小化可能的错误,应用了以下规则:

  • 如果某个字段设置了 expression(),它的优先级高于 updateFields()。
  • 如果在 updateFields() 中指定了字段,则仅这些字段会在记录已存在时被更新。未在 updateFields() 中指定的字段不会受到影响。

请注意,仍然有可能定义出没有意义的查询。开发者必须确保不要编写出这种无意义的查询,否则其行为是未定义的。