插入查询 insert
插入查询始终应当使用查询构建器对象。在某些数据库中,需要对 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 中正常工作。