logo

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

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

演示 EBT 模块 下载 EBT 模块

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

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

演示 EPT 模块 滚动

滚动
01/10/2025, by Ivan

插入查询始终应当使用查询构建器对象。在某些数据库中,需要对 LOB(Large OBject,例如 MySQL 中的 TEXT)和 BLOB(Binary Large OBject)字段进行特殊处理,因此需要抽象层,以便各个数据库驱动能够实现所需的特殊处理。

插入查询通过 insert() 方法来执行,如下所示:

$query = $connection->insert('mytable', $options);

这会创建一个插入查询对象,用于向 mytable 表中插入一条或多条记录。请注意,表名不需要使用花括号,因为查询构建器会自动处理。

插入查询对象使用的是流式 API。也就是说,所有方法(除了 execute())都会返回查询对象本身,从而允许方法链式调用。在很多情况下,这意味着根本不需要将查询对象保存在变量中。

插入查询对象支持多种不同的用法模式,以满足不同需求。通常工作流程包括:指定要插入的字段,定义要插入这些字段的值,然后执行查询。以下列出了最常见且推荐的使用模式。

紧凑形式

对于大多数插入查询,推荐的形式是紧凑形式:

$result = $connection->insert('mytable')
  ->fields([
    'title' => 'Example',
    'uid' => 1,
    'created' => \Drupal::time()->getRequestTime(),
  ])
  ->execute();

这等同于以下 SQL 查询:

INSERT INTO {mytable} (title, uid, created) VALUES ('Example', 1, 1221717405);

上面的代码片段整合了插入过程的关键部分。

$connection->insert('mytable')

这行代码为 mytable 表创建了一个新的插入查询对象。

  ->fields([
    'title' => 'Example',
    'uid' => 1,
    'created' => \Drupal::time()->getRequestTime(),
  ])

fields() 方法接受多种参数形式,但最常用的是一个关联数组。数组的键是要插入的表列,值是对应要插入的内容。这将对指定表执行一次插入操作。

 ->execute();

execute() 方法执行查询。如果没有调用该方法,查询不会运行。

与插入查询对象的其他方法不同(它们返回查询对象本身),execute() 会返回由插入操作填充的自增字段(在 hook_schema() 中为 serial 类型)的值(如果有)。这就是为什么上面示例中的返回值会赋给 $result。如果没有自增字段,execute() 的返回值未定义,不能依赖。

在典型情况下,这种紧凑形式是首选格式。

冗长形式

$result = $connection->insert('mytable')
  ->fields(['title', 'uid', 'created'])
  ->values([
    'title' => 'Example',
    'uid' => 1,
    'created' => \Drupal::time()->getRequestTime(),
  ])
  ->execute();

这是前一个查询的更详细等价形式,结果完全相同。

  ->fields(['title', 'uid', 'created'])

当 fields() 使用索引数组而不是关联数组时,它只会设置将在查询中使用的字段(数据库列),但不会为它们设置值。这对后续执行批量查询时很有用。

  ->values([
    'title' => 'Example',
    'uid' => 1,
    'created' => \Drupal::time()->getRequestTime(),
  ])

这个方法调用提供了一个关联数组,字段名对应要插入的值。values() 也可以接受索引数组,如果使用索引数组,值的顺序必须与 fields() 中字段的顺序一致。如果使用关联数组,顺序无关。通常为可读性,推荐使用关联数组。

这种形式很少使用,因为紧凑形式更推荐。大多数情况下,唯一将 fields() 和 values() 分开的原因是需要执行批量插入。

多条插入形式

插入查询对象还可以接受多个值集。也就是说,values() 可以被多次调用,以排队多个插入语句。具体如何执行取决于数据库驱动。在大多数数据库中,多个插入语句会在事务中一起执行,以提高数据完整性和性能。在 MySQL 中会使用 MySQL 的多值插入语法。

$values = [
  [
    'title' => 'Example',
    'uid' => 1,
    'created' => \Drupal::time()->getRequestTime(),
  ],
  [
    'title' => 'Example 2',
    'uid' => 1,
    'created' => \Drupal::time()->getRequestTime(),
  ],
  [
    'title' => 'Example 3',
    'uid' => 2,
    'created' => \Drupal::time()->getRequestTime(),
  ],
];
$query = $connection->insert('mytable')->fields(['title', 'uid', 'created']);
foreach ($values as $record) {
  $query->values($record);
}
$query->execute();

在上面的示例中,三条插入语句将作为一个整体一起执行,使用针对特定数据库驱动的最高效方法。注意,这里我们将查询对象保存在变量中,以便可以循环处理 $values 并多次调用 values()。

在最简单的情况下,上面的示例等同于以下三个查询:

INSERT INTO {mytable} (title, uid, created) VALUES ('Example', 1, 1221717405);
INSERT INTO {mytable} (title, uid, created) VALUES ('Example2', 1, 1221717405);
INSERT INTO {mytable} (title, uid, created) VALUES ('Example3', 2, 1221717405);

注意,在多条插入查询中,execute() 的返回值未定义,不能依赖,因为它可能因数据库驱动而异。

基于 select 的插入

如果你想用其他表的查询结果填充某个表,你需要么从源表执行 SELECT,在 PHP 中循环处理数据并插入到新表中,或者执行 INSERT INTO ... SELECT FROM 查询,将 SELECT 返回的每条记录插入到目标表中。

在这个示例中,我们想要构建一个 mytable 表,其中包含系统中所有类型为 page 的节点的 nid 和用户名。

<?php
// 构建 SELECT 查询。
$query = $connection->select('node', 'n');
// 连接 users 表。
$query->join('users', 'u', 'n.uid = u.uid');
// 添加需要的字段。
$query->addField('n','nid');
$query->addField('u','name');
// 添加条件,仅获取 page 节点。
$query->condition('type', 'page');

// 执行插入。
$connection->insert('mytable')
  ->from($query)
  ->execute();
?>

默认值

在正常情况下,如果你没有为某个字段提供值,并且该字段在表结构中定义了默认值,那么数据库会自动插入默认值。但在某些情况下,你需要显式告诉数据库使用默认值。这包括你希望整个记录都使用默认值的情况。为了显式指定数据库使用默认值,可以使用 useDefaults() 方法。

$query->useDefaults(['field1', 'field2']);

这行代码告诉查询在插入时使用字段 field1 和 field2 的默认值。请注意,在 useDefaults() 和 fields() 或 values() 中同时指定相同字段是错误的,这会触发异常。

$connection->insert() 还是 $connection->query()

这是一个常见问题。(见本页评论。)insert() 和 query() 有什么区别?

在 insert() 方法中,每个列都作为字段数组中的单独项指定,代码可以检查和清理每个列的值。而 query() 使用原始 SQL 字符串,无法逐列检查。如果你在 query() 中使用占位符,代码可以检查列值,但占位符只是一个选项,不能保证 SQL 中没有包含未清理的值。

insert() 会让查询经过一组 hook,从而允许其他模块检查和修改你的查询。这是与其他模块协同工作的正确方式。而 query() 稍微快一些,因为它不会让查询经过 hook。你可以节省处理时间,但你的代码将不会允许其他模块参与。

insert() 更有可能在其他数据库和未来版本的 Drupal 中正常工作。