⌘+k ctrl+k
1.3 (稳定版)
搜索快捷键 cmd + k | ctrl + k
子查询

子查询是用括号括起来的查询表达式,作为更大、外部查询的一部分出现。子查询通常基于SELECT ... FROM,但在DuckDB中,PIVOT等其他查询构造也可以作为子查询出现。

标量子查询

标量子查询是返回单个值的子查询。它们可以在任何可以使用表达式的地方使用。如果标量子查询返回多个值,则会引发错误(除非将scalar_subquery_error_on_multiple_rows设置为false,在这种情况下会随机选择一行)。

考虑以下表格

成绩

成绩 课程
7 数学
9 数学
8 计算机科学
CREATE TABLE grades (grade INTEGER, course VARCHAR);
INSERT INTO grades VALUES (7, 'Math'), (9, 'Math'), (8, 'CS');

我们可以运行以下查询以获取最低成绩

SELECT min(grade) FROM grades;
min(成绩)
7

通过在WHERE子句中使用标量子查询,我们可以找出该成绩是哪个课程获得的

SELECT course FROM grades WHERE grade = (SELECT min(grade) FROM grades);
课程
数学

子查询比较:ALLANYSOME

标量子查询部分中,标量表达式使用等式比较运算符=)直接与子查询进行比较。这种直接比较仅适用于标量子查询。

标量表达式仍然可以通过指定量词与返回多行的单列子查询进行比较。可用的量词有ALLANYSOME。量词ANYSOME是等效的。

ALL

ALL量词指定,当比较运算符左侧的表达式比较运算符右侧子查询中的每个值的单个比较结果**全部**评估为true时,整个比较结果为true

SELECT 6 <= ALL (SELECT grade FROM grades) AS adequate;

返回

满足
true

因为6小于或等于子查询的每个结果7、8和9。

然而,以下查询

SELECT 8 >= ALL (SELECT grade FROM grades) AS excellent;

返回

不满足
false

因为8不大于或等于子查询结果7。因此,由于并非所有比较都评估为true,所以>= ALL作为一个整体评估为false

ANY

ANY量词指定,当至少一个单个比较结果评估为true时,整个比较结果为true。例如

SELECT 5 >= ANY (SELECT grade FROM grades) AS fail;

返回

失败
false

因为子查询的结果中没有小于或等于5的值。

量词SOME可以代替ANY使用:ANYSOME可以互换。

EXISTS

EXISTS运算符用于测试子查询中是否存在任何行。当子查询返回一条或多条记录时,它返回true;否则返回false。EXISTS运算符通常作为关联子查询最有用,用于表达半连接操作。然而,它也可以作为非关联子查询使用。

例如,我们可以使用它来判断给定课程是否存在任何成绩

SELECT EXISTS (FROM grades WHERE course = 'Math') AS math_grades_present;
数学成绩是否存在
true
SELECT EXISTS (FROM grades WHERE course = 'History') AS history_grades_present;
历史成绩是否存在
false

上述示例中的子查询利用了由于DuckDB的FROM优先语法,您可以省略SELECT *。其他SQL系统中子查询需要SELECT子句,但在EXISTSNOT EXISTS子查询中不起任何作用。

NOT EXISTS

NOT EXISTS运算符测试子查询中是否没有行存在。当子查询返回空结果时,它返回true;否则返回false。NOT EXISTS运算符通常作为关联子查询最有用,用于表达反连接操作。例如,查找没有兴趣的人员节点

CREATE TABLE Person (id BIGINT, name VARCHAR);
CREATE TABLE interest (PersonId BIGINT, topic VARCHAR);

INSERT INTO Person VALUES (1, 'Jane'), (2, 'Joe');
INSERT INTO interest VALUES (2, 'Music');

SELECT *
FROM Person
WHERE NOT EXISTS (FROM interest WHERE interest.PersonId = Person.id);
id 姓名
1 Jane

NOT EXISTS查询表达反连接操作时,DuckDB会自动检测。无需手动将此类查询重写为使用LEFT OUTER JOIN ... WHERE ... IS NULL

IN运算符

IN运算符检查左侧表达式是否包含在子查询定义的结果或右侧(RHS)表达式集中。当表达式存在于RHS中时,IN运算符返回true;当表达式不存在于RHS且RHS不包含NULL值时返回false;或者当表达式不存在于RHS且RHS包含NULL值时返回NULL

我们可以像使用EXISTS运算符那样,以类似的方式使用IN运算符

SELECT 'Math' IN (SELECT course FROM grades) AS math_grades_present;
数学成绩是否存在
true

关联子查询

到目前为止,此处介绍的所有子查询都是非关联子查询,这些子查询本身是完全独立的,可以在没有父查询的情况下运行。存在第二种类型的子查询,称为关联子查询。对于关联子查询,子查询使用来自父查询的值。

从概念上讲,子查询对父查询中的每一行运行一次。一个简单的设想方式是,关联子查询是一个应用于源数据集中每一行的函数

例如,假设我们想找出每门课程的最低成绩。我们可以这样做

SELECT *
FROM grades grades_parent
WHERE grade =
    (SELECT min(grade)
     FROM grades
     WHERE grades.course = grades_parent.course);
成绩 课程
7 数学
8 计算机科学

子查询使用了父查询中的列(grades_parent.course)。从概念上讲,我们可以将子查询视为一个函数,其中关联列是该函数的参数

SELECT min(grade)
FROM grades
WHERE course = ?;

现在,当我们对每一行执行此函数时,我们可以看到,对于Math,它将返回7;对于CS,它将返回8。然后我们将其与实际行的成绩进行比较。因此,行(Math, 9)将被过滤掉,因为9 <> 7

将子查询的每一行作为结构体返回

SELECT子句中使用子查询的名称(不引用特定列),将子查询的每一行转换为一个结构体,其字段对应于子查询的列。例如

SELECT t
FROM (SELECT unnest(generate_series(41, 43)) AS x, 'hello' AS y) t;
t
{'x': 41, 'y': hello}
{'x': 42, 'y': hello}
{'x': 43, 'y': hello}