- 安装
- 文档
- 入门
- 连接
- 数据导入
- 客户端 API
- 概览
- ADBC
- C
- C++
- CLI
- Dart
- Go
- Java (JDBC)
- Julia
- Node.js (已弃用)
- Node.js (Neo)
- ODBC
- PHP
- Python
- R
- Rust
- Swift
- Wasm
- SQL
- 介绍
- 语句
- 概览
- ANALYZE
- ALTER TABLE
- ALTER VIEW
- ATTACH 和 DETACH
- CALL
- CHECKPOINT
- COMMENT ON
- COPY
- CREATE INDEX
- CREATE MACRO
- CREATE SCHEMA
- CREATE SECRET
- CREATE SEQUENCE
- CREATE TABLE
- CREATE VIEW
- CREATE TYPE
- DELETE
- DESCRIBE
- DROP
- EXPORT 和 IMPORT DATABASE
- INSERT
- LOAD / INSTALL
- PIVOT
- 性能分析
- SELECT
- SET / RESET
- SET VARIABLE
- SUMMARIZE
- 事务管理
- UNPIVOT
- UPDATE
- USE
- VACUUM
- 查询语法
- SELECT
- FROM 和 JOIN
- WHERE
- GROUP BY
- GROUPING SETS
- HAVING
- ORDER BY
- LIMIT 和 OFFSET
- SAMPLE
- 展开嵌套
- WITH
- WINDOW
- QUALIFY
- VALUES
- FILTER
- 集合操作
- 预处理语句
- 数据类型
- 表达式
- 函数
- 概览
- 聚合函数
- 数组函数
- 位字符串函数
- Blob 函数
- 日期格式化函数
- 日期函数
- 日期部分函数
- 枚举函数
- 间隔函数
- Lambda 函数
- 列表函数
- 映射函数
- 嵌套函数
- 数值函数
- 模式匹配
- 正则表达式
- 结构体函数
- 文本函数
- 时间函数
- 时间戳函数
- 带时区时间戳函数
- 联合函数
- 实用函数
- 窗口函数
- 约束
- 索引
- 元查询
- DuckDB 的 SQL 方言
- 示例
- 配置
- 扩展
- 核心扩展
- 概览
- 自动补全
- Avro
- AWS
- Azure
- Delta
- DuckLake
- 编码
- Excel
- 全文搜索
- httpfs (HTTP 和 S3)
- Iceberg
- ICU
- inet
- jemalloc
- MySQL
- PostgreSQL
- 空间
- SQLite
- TPC-DS
- TPC-H
- UI
- VSS
- 指南
- 概览
- 数据查看器
- 数据库集成
- 文件格式
- 概览
- CSV 导入
- CSV 导出
- 直接读取文件
- Excel 导入
- Excel 导出
- JSON 导入
- JSON 导出
- Parquet 导入
- Parquet 导出
- 查询 Parquet 文件
- 使用 file: 协议访问文件
- 网络和云存储
- 概览
- HTTP Parquet 导入
- S3 Parquet 导入
- S3 Parquet 导出
- S3 Iceberg 导入
- S3 Express One
- GCS 导入
- Cloudflare R2 导入
- 通过 HTTPS / S3 使用 DuckDB
- Fastly 对象存储导入
- 元查询
- ODBC
- 性能
- Python
- 安装
- 执行 SQL
- Jupyter Notebooks
- marimo Notebooks
- Pandas 上的 SQL
- 从 Pandas 导入
- 导出到 Pandas
- 从 Numpy 导入
- 导出到 Numpy
- Arrow 上的 SQL
- 从 Arrow 导入
- 导出到 Arrow
- Pandas 上的关系型 API
- 多个 Python 线程
- 与 Ibis 集成
- 与 Polars 集成
- 使用 fsspec 文件系统
- SQL 编辑器
- SQL 功能
- 代码片段
- 故障排除
- 术语表
- 离线浏览
- 操作手册
- 开发
- 内部结构
- 为什么选择 DuckDB
- 行为准则
- 发布日历
- 路线图
- 站点地图
- 在线演示
子查询是用括号括起来的查询表达式,作为更大、外部查询的一部分出现。子查询通常基于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);
课程 |
---|
数学 |
子查询比较:ALL
、ANY
和 SOME
在标量子查询部分中,标量表达式使用等式比较运算符(=
)直接与子查询进行比较。这种直接比较仅适用于标量子查询。
标量表达式仍然可以通过指定量词与返回多行的单列子查询进行比较。可用的量词有ALL
、ANY
和SOME
。量词ANY
和SOME
是等效的。
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
使用:ANY
和SOME
可以互换。
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
子句,但在EXISTS
和NOT 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} |