任何查询都可以有对应的“计数查询”。计数查询返回原始查询中的行数。要从现有查询(即实现了 SelectInterface 的 select 查询对象)获取计数查询,请使用 countQuery() 方法。
$count_query = $query->countQuery();
$count_query 现在是一个新的动态 select 查询,没有排序限制,在执行时会返回仅包含一个值的结果集——与原始查询匹配的记录数。由于 PHP 支持返回对象的链式方法调用,下面的写法是常见方式:
某些 SQL 查询可能会返回重复的结果。在这种情况下,可以在静态查询中使用关键字「DISTINCT」来过滤重复的行。在动态查询中,请使用 distinct() 方法。
// 强制在结果集中过滤重复记录。
$connection = \Drupal::database();
$query = $connection->select('my_table', 'mt');
$query->fields('mt', ['my_fields']);
$query->distinct()->execute()->fetchAll();
请注意,DISTINCT 可能会导致性能下降,因此除非没有其他方式可以限制结果集以避免重复,否则不要使用它。
要按指定字段进行分组,请使用 groupBy() 方法。
$query->groupBy('uid');
上面的代码会指示查询按 uid 字段进行分组。请注意,这里的字段名必须是通过 addField() 或 addExpression() 方法创建的别名,因此在大多数情况下,你需要使用这些方法的返回值,以确保使用的是正确的别名。
如果你想获取按某个字段(例如 uid)分组的行数,可以这样做:
$query->addExpression('count(uid)', 'uid_node_count');
要按多个字段分组,只需多次调用 groupBy(),按照所需顺序即可。
Having
可以为聚合值添加条件。
要在动态查询中添加 WHERE 子句,请使用 condition() 方法:
$query->condition('bundle', 'article', '=');
上面的代码会指示查询筛选结果,只返回属于 article 内容类型的记录。请注意,这里的字段名必须是通过 addField() 或 addExpression() 方法创建的别名。
Condition 参数
condition() 方法接受三个参数:
- $field - 要比较的字段(必填)。
- $value - 用于比较的值(可选,默认 NULL)。
- $operator - 比较运算符(可选,默认 ‘=’)。
支持的运算符
通用的比较运算符 '=', '<>', '<', '<=', '>', '>=' 在所有受支持的数据库类型中都可用。
使用 IN, NOT IN
IN 和 NOT IN 运算符在 $value 中接受一个数组,并将字段值与数组中的值进行比较。
Select 查询总是会返回一个结果集对象,该对象包含零条或多条记录。根据使用场景不同,有多种方式可以从该结果集中获取数据。默认情况下,如果不修改提取模式,记录会被作为对象提取(参见:setFetchMode)。
最常见的情况是通过 foreach() 循环遍历结果集:
查询可以通过用户自定义类来提取为对象。例如,如果我们有一个名为 ExampleClass 的类,下面的查询将返回 exampleClass 类型的对象。
$result = $connection->query("SELECT id, title FROM {example_table}", [], [
'fetch' => 'ExampleClass',
]);
如果类中有 __construct() 方法,对象会被创建,属性会被添加到对象中,然后调用 __construct() 方法。例如,如果你有以下类和查询:
插入查询始终应当使用查询构建器对象。在某些数据库中,需要对 LOB(Large OBject,例如 MySQL 中的 TEXT)和 BLOB(Binary Large OBject)字段进行特殊处理,因此需要抽象层,以便各个数据库驱动能够实现所需的特殊处理。
插入查询通过 insert() 方法来执行,如下所示:
$query = $connection->insert('mytable', $options);
这会创建一个插入查询对象,用于向 mytable 表中插入一条或多条记录。请注意,表名不需要使用花括号,因为查询构建器会自动处理。
插入查询对象使用的是流式 API。也就是说,所有方法(除了 execute())都会返回查询对象本身,从而允许方法链式调用。在很多情况下,这意味着根本不需要将查询对象保存在变量中。
插入查询对象支持多种不同的用法模式,以满足不同需求。通常工作流程包括:指定要插入的字段,定义要插入这些字段的值,然后执行查询。以下列出了最常见且推荐的使用模式。
紧凑形式
对于大多数插入查询,推荐的形式是紧凑形式:
合并查询是一种特殊类型的混合查询。虽然它们的语法在 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 的实现是单独的原子查询,但上面的简化情况则不是。
删除查询始终应当使用查询构建器对象。它们通过 delete() 方法来执行,如下所示:
$query = $connection->delete('mytable', $options);
这会创建一个删除查询对象,用于从 mytable 表中删除记录。请注意,表名不需要使用花括号,因为查询构建器会自动处理。
删除查询对象使用的是流式 API。也就是说,所有方法(除了 execute())都会返回查询对象本身,从而允许方法链式调用。在很多情况下,这意味着根本不需要将查询对象保存在变量中。
删除查询在概念上非常简单,仅由 WHERE 子句组成。WHERE 子句的完整结构在“条件子句”部分中有详细描述,这里只会简单提及。
一个完整的删除查询形式如下:
Drupal 也支持事务,包括对不支持事务的数据库提供透明的备用方案。然而,当你尝试同时运行两个事务时,事务可能会变得相当复杂。在这种情况下的行为也依赖于具体的数据库。
在 C / C++ 中嵌套锁会出现类似的问题。如果代码已经获取了锁 A,然后再次尝试获取锁 A,代码将被阻塞。如果你编写的代码会检查它是否已经持有锁,并且不会再次尝试获取它,你可以避免死锁,但可能会过早释放锁。
在 SQL 中我们遇到了相同的问题。如果你的代码已经在一个事务中,再次启动一个新事务会带来意想不到且不理想的后果——即提交当前事务并启动新的事务。
Java 使用其锁机制通过实现类似嵌套结构的支持来解决嵌套问题,正如我们下面测试的那样。Java 允许将函数标记为“同步”,这会强制函数在启动前等待获取锁,并在不再需要时释放锁。如果一个同步函数调用了同一个类中的另一个同步函数,Java 会追踪锁的嵌套。外层函数获取锁,内层函数不会执行额外的加锁操作,而外层函数在返回时释放锁。