logo

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

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

演示 EBT 模块 下载 EBT 模块

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

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

演示 EPT 模块 滚动

滚动
01/10/2025, by Ivan

任何查询都可以有对应的“计数查询”。计数查询返回原始查询中的行数。要从现有查询(即实现了 SelectInterface 的 select 查询对象)获取计数查询,请使用 countQuery() 方法。

$count_query = $query->countQuery();

$count_query 现在是一个新的动态 select 查询,没有排序限制,在执行时会返回仅包含一个值的结果集——与原始查询匹配的记录数。由于 PHP 支持返回对象的链式方法调用,下面的写法是常见方式:

01/10/2025, by Ivan

某些 SQL 查询可能会返回重复的结果。在这种情况下,可以在静态查询中使用关键字「DISTINCT」来过滤重复的行。在动态查询中,请使用 distinct() 方法。

// 强制在结果集中过滤重复记录。
$connection = \Drupal::database();
$query = $connection->select('my_table', 'mt');
$query->fields('mt', ['my_fields']);
$query->distinct()->execute()->fetchAll();

请注意,DISTINCT 可能会导致性能下降,因此除非没有其他方式可以限制结果集以避免重复,否则不要使用它。

01/10/2025, by Ivan

要按指定字段进行分组,请使用 groupBy() 方法。

$query->groupBy('uid');

上面的代码会指示查询按 uid 字段进行分组。请注意,这里的字段名必须是通过 addField() 或 addExpression() 方法创建的别名,因此在大多数情况下,你需要使用这些方法的返回值,以确保使用的是正确的别名。

如果你想获取按某个字段(例如 uid)分组的行数,可以这样做:

$query->addExpression('count(uid)', 'uid_node_count');

要按多个字段分组,只需多次调用 groupBy(),按照所需顺序即可。

Having

可以为聚合值添加条件。

01/10/2025, by Ivan

要在动态查询中添加 WHERE 子句,请使用 condition() 方法:

$query->condition('bundle', 'article', '=');

上面的代码会指示查询筛选结果,只返回属于 article 内容类型的记录。请注意,这里的字段名必须是通过 addField() 或 addExpression() 方法创建的别名。

Condition 参数

condition() 方法接受三个参数:

  • $field - 要比较的字段(必填)。
  • $value - 用于比较的值(可选,默认 NULL)。
  • $operator - 比较运算符(可选,默认 ‘=’)。

支持的运算符

通用的比较运算符 '=', '<>', '<', '<=', '>', '>=' 在所有受支持的数据库类型中都可用。

使用 IN, NOT IN

IN 和 NOT IN 运算符在 $value 中接受一个数组,并将字段值与数组中的值进行比较。

01/10/2025, by Ivan

Select 查询总是会返回一个结果集对象,该对象包含零条或多条记录。根据使用场景不同,有多种方式可以从该结果集中获取数据。默认情况下,如果不修改提取模式,记录会被作为对象提取(参见:setFetchMode)。

最常见的情况是通过 foreach() 循环遍历结果集:

01/10/2025, by Ivan

查询可以通过用户自定义类来提取为对象。例如,如果我们有一个名为 ExampleClass 的类,下面的查询将返回 exampleClass 类型的对象。

$result = $connection->query("SELECT id, title FROM {example_table}", [], [
  'fetch' => 'ExampleClass',
]);

如果类中有 __construct() 方法,对象会被创建,属性会被添加到对象中,然后调用 __construct() 方法。例如,如果你有以下类和查询:

01/10/2025, by Ivan

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

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

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

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

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

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

紧凑形式

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

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 的实现是单独的原子查询,但上面的简化情况则不是。

01/10/2025, by Ivan

删除查询始终应当使用查询构建器对象。它们通过 delete() 方法来执行,如下所示:

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

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

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

删除查询在概念上非常简单,仅由 WHERE 子句组成。WHERE 子句的完整结构在“条件子句”部分中有详细描述,这里只会简单提及。

一个完整的删除查询形式如下:

01/10/2025, by Ivan

Drupal 也支持事务,包括对不支持事务的数据库提供透明的备用方案。然而,当你尝试同时运行两个事务时,事务可能会变得相当复杂。在这种情况下的行为也依赖于具体的数据库。

在 C / C++ 中嵌套锁会出现类似的问题。如果代码已经获取了锁 A,然后再次尝试获取锁 A,代码将被阻塞。如果你编写的代码会检查它是否已经持有锁,并且不会再次尝试获取它,你可以避免死锁,但可能会过早释放锁。

在 SQL 中我们遇到了相同的问题。如果你的代码已经在一个事务中,再次启动一个新事务会带来意想不到且不理想的后果——即提交当前事务并启动新的事务。

Java 使用其锁机制通过实现类似嵌套结构的支持来解决嵌套问题,正如我们下面测试的那样。Java 允许将函数标记为“同步”,这会强制函数在启动前等待获取锁,并在不再需要时释放锁。如果一个同步函数调用了同一个类中的另一个同步函数,Java 会追踪锁的嵌套。外层函数获取锁,内层函数不会执行额外的加锁操作,而外层函数在返回时释放锁。