⌘+k ctrl+k
1.4 (LTS)
搜索快捷键 cmd + k | ctrl + k
SQL 简介

本页面概述了如何使用 SQL 执行简单操作。本教程仅旨在为您提供入门指导,绝非关于 SQL 的完整教程。本教程改编自 PostgreSQL 教程

DuckDB 的 SQL 方言非常遵循 PostgreSQL 方言的约定。少数例外情况列在 PostgreSQL 兼容性页面上。

在接下来的示例中,我们假设您已经安装了 DuckDB 命令行界面 (CLI) shell。有关如何安装 CLI 的信息,请参阅安装页面

概念

DuckDB 是一个关系型数据库管理系统 (RDBMS)。这意味着它是一个用于管理存储在“关系”中的数据的系统。“关系”本质上是表的数学术语。

每个表都是一个命名的行集合。给定表的每一行都具有相同的一组命名列,且每一列都有特定的数据类型。表本身存储在模式(Schema)中,模式的集合构成了您可以访问的整个数据库。

创建新表

您可以通过指定表名以及所有列名及其类型来创建新表

CREATE TABLE weather (
    city    VARCHAR,
    temp_lo INTEGER, -- minimum temperature on a day
    temp_hi INTEGER, -- maximum temperature on a day
    prcp    FLOAT,
    date    DATE
);

您可以将此内容连同换行符一起输入 shell。命令直到分号处才算结束。

在 SQL 命令中可以自由使用空白字符(即空格、制表符和换行符)。这意味着您可以以不同于上文的对齐方式输入命令,甚至可以将整个命令写在一行上。两个连字符 (--) 表示注释。它们后面的内容直到行尾都会被忽略。SQL 对关键字和标识符不区分大小写。返回标识符时,将保留其原始大小写

在 SQL 命令中,我们首先指定要执行的命令类型:CREATE TABLE。之后是命令的参数。首先给出表名 weather,然后是列名和列类型。

city VARCHAR 指定该表有一个名为 city 且类型为 VARCHAR 的列。VARCHAR 指定了一种可以存储任意长度文本的数据类型。温度字段存储在 INTEGER 类型中,这是一种存储整数(即没有小数点的整数)的类型。FLOAT 列存储单精度浮点数(即带有小数点的数字)。DATE 存储日期(即年、月、日的组合)。DATE 仅存储特定的某一天,而不存储与该日期关联的时间。

DuckDB 支持标准 SQL 类型 INTEGERSMALLINTFLOATDOUBLEDECIMALCHAR(n)VARCHAR(n)DATETIMETIMESTAMP

第二个示例将存储城市及其关联的地理位置

CREATE TABLE cities (
    name VARCHAR,
    lat  DECIMAL,
    lon  DECIMAL
);

最后需要提到的是,如果您不再需要某个表,或者想以不同的方式重新创建它,可以使用以下命令将其删除

DROP TABLE tablename;

向表中填充行

INSERT 语句用于向表中填充行

INSERT INTO weather
VALUES ('San Francisco', 46, 50, 0.25, '1994-11-27');

非数值类型的常量(例如文本和日期)必须用单引号 ('') 括起来,如示例中所示。DATE 类型的输入日期必须格式化为 'YYYY-MM-DD'

我们可以以同样的方式插入到 cities 表中。

INSERT INTO cities
VALUES ('San Francisco', -194.0, 53.0);

到目前为止所使用的语法要求您记住列的顺序。另一种语法允许您显式列出列名

INSERT INTO weather (city, temp_lo, temp_hi, prcp, date)
VALUES ('San Francisco', 43, 57, 0.0, '1994-11-29');

您可以根据需要以不同的顺序列出列,甚至可以省略某些列,例如,如果 prcp(降水量)未知

INSERT INTO weather (date, city, temp_hi, temp_lo)
VALUES ('1994-11-29', 'Hayward', 54, 37);

提示:许多开发人员认为显式列出列名比依赖隐式顺序更具可读性。

请输入上述所有命令,以便在后续章节中有数据可供使用。

或者,您可以使用 COPY 语句。对于大量数据,这种方式更快,因为 COPY 命令针对批量加载进行了优化,虽然其灵活性不如 INSERT。一个使用 weather.csv 的示例如下

COPY weather
FROM 'weather.csv';

其中源文件的文件名必须在运行该过程的机器上可用。还有许多其他将数据加载到 DuckDB 的方法,有关更多信息,请参阅相应的文档部分

查询表

要从表中检索数据,需要对表进行查询。为此使用 SQL SELECT 语句。该语句分为选择列表(列出要返回的列的部分)、表列表(列出要从中检索数据的表的部分)以及可选的限定条件(指定任何限制的部分)。例如,要检索 weather 表的所有行,请输入

SELECT *
FROM weather;

此处 * 是“所有列”的简写。因此,相同的结果也可以通过以下方式获得

SELECT city, temp_lo, temp_hi, prcp, date
FROM weather;

输出应为

城市 temp_lo temp_hi prcp date
San Francisco 46 50 0.25 1994-11-27
San Francisco 43 57 0.0 1994-11-29
Hayward 37 54 NULL 1994-11-29

您可以在选择列表中编写表达式,而不仅仅是简单的列引用。例如,您可以这样做

SELECT city, (temp_hi + temp_lo) / 2 AS temp_avg, date
FROM weather;

这应该会得到

城市 temp_avg date
San Francisco 48.0 1994-11-27
San Francisco 50.0 1994-11-29
Hayward 45.5 1994-11-29

请注意 AS 子句是如何用于重命名输出列的。(AS 子句是可选的。)

查询可以通过添加指定所需行的 WHERE 子句进行“限定”。WHERE 子句包含一个布尔(真值)表达式,仅返回布尔表达式为真的行。常规布尔运算符(ANDORNOT)在限定条件中是允许的。例如,以下内容检索旧金山在下雨天的天气情况

SELECT *
FROM weather
WHERE city = 'San Francisco'
  AND prcp > 0.0;

结果

城市 temp_lo temp_hi prcp date
San Francisco 46 50 0.25 1994-11-27

您可以要求查询结果以排序顺序返回

SELECT *
FROM weather
ORDER BY city;
城市 temp_lo temp_hi prcp date
Hayward 37 54 NULL 1994-11-29
San Francisco 43 57 0.0 1994-11-29
San Francisco 46 50 0.25 1994-11-27

在此示例中,排序顺序未完全指定,因此您可能会以任一顺序获得旧金山的行。但如果您执行以下操作,将始终获得如上所示的结果

SELECT *
FROM weather
ORDER BY city, temp_lo;

您可以要求从查询结果中删除重复行

SELECT DISTINCT city
FROM weather;
城市
San Francisco
Hayward

同样,结果行的顺序可能会有所不同。您可以通过同时使用 DISTINCTORDER BY 来确保结果一致

SELECT DISTINCT city
FROM weather
ORDER BY city;

表之间的连接 (Joins)

到目前为止,我们的查询一次只访问一个表。查询可以一次访问多个表,或者以同时处理表的多行的方式访问同一个表。一次访问同一或不同表的多个行的查询称为连接查询。例如,假设您希望列出所有天气记录以及相关城市的位置。为此,我们需要比较 weather 表中每一行的 city 列与 cities 表中所有行的 name 列,并选择这些值匹配的行对。

这可以通过以下查询来完成

SELECT *
FROM weather, cities
WHERE city = name;
城市 temp_lo temp_hi prcp date name lat lon
San Francisco 46 50 0.25 1994-11-27 San Francisco -194.000 53.000
San Francisco 43 57 0.0 1994-11-29 San Francisco -194.000 53.000

关于结果集,请观察两点

  • 对于 Hayward 市,没有结果行。这是因为在 cities 表中没有 Hayward 的匹配条目,所以连接会忽略 weather 表中不匹配的行。我们将很快看到如何解决这个问题。
  • 有两列包含城市名称。这是正确的,因为 weathercities 表的列列表已连接在一起。但在实践中这是不可取的,因此您可能希望显式列出输出列,而不是使用 *
SELECT city, temp_lo, temp_hi, prcp, date, lon, lat
FROM weather, cities
WHERE city = name;
城市 temp_lo temp_hi prcp date lon lat
San Francisco 46 50 0.25 1994-11-27 53.000 -194.000
San Francisco 43 57 0.0 1994-11-29 53.000 -194.000

由于列名各不相同,解析器自动找到了它们所属的表。如果两个表中存在重复的列名,则需要限定列名以指明您指的是哪一个,如下所示

SELECT weather.city, weather.temp_lo, weather.temp_hi,
       weather.prcp, weather.date, cities.lon, cities.lat
FROM weather, cities
WHERE cities.name = weather.city;

在连接查询中限定所有列名被广泛认为是良好的风格,这样如果以后在其中一个表中添加了重复的列名,查询就不会失败。

到目前为止所见的这类连接查询也可以写成以下替代形式

SELECT *
FROM weather
INNER JOIN cities ON weather.city = cities.name;

这种语法不如上面的常用,但我们在这里展示它是为了帮助您理解后续主题。

现在我们将弄清楚如何把 Hayward 的记录找回来。我们希望查询扫描 weather 表,并为每一行找到匹配的 cities 行。如果没有找到匹配的行,我们希望用“空值”替换 cities 表的列。这种查询称为外连接。(到目前为止我们看到的连接是内连接。)命令如下所示

SELECT *
FROM weather
LEFT OUTER JOIN cities ON weather.city = cities.name;
城市 temp_lo temp_hi prcp date name lat lon
San Francisco 46 50 0.25 1994-11-27 San Francisco -194.000 53.000
San Francisco 43 57 0.0 1994-11-29 San Francisco -194.000 53.000
Hayward 37 54 NULL 1994-11-29 NULL NULL NULL

此查询称为左外连接,因为连接运算符左侧提到的表中的每一行至少会出现在输出中一次,而右侧的表只会输出那些与左表某一行匹配的行。在输出没有右表匹配的左表行时,右表列将被替换为空(null)值。

聚合函数

像大多数其他关系型数据库产品一样,DuckDB 支持聚合函数。聚合函数根据多个输入行计算单个结果。例如,有用于计算一组行上的 count(计数)、sum(求和)、avg(平均值)、max(最大值)和 min(最小值)的聚合函数。

例如,我们可以找出任何地方的最高低温读数

SELECT max(temp_lo)
FROM weather;
max(temp_lo)
46

如果我们想知道该读数发生在哪个(或哪些)城市,我们可能会尝试

SELECT city
FROM weather
WHERE temp_lo = max(temp_lo);

但这是行不通的,因为聚合函数 max 不能在 WHERE 子句中使用

Binder Error:
WHERE clause cannot contain aggregates!

存在这种限制是因为 WHERE 子句决定了哪些行将被包含在聚合计算中;因此显然必须在计算聚合函数之前对其进行评估。然而,通常可以通过使用子查询重写查询来达到预期的结果

SELECT city
FROM weather
WHERE temp_lo = (SELECT max(temp_lo) FROM weather);
城市
San Francisco

这样做是可以的,因为子查询是一个独立的计算,它独立于外部查询发生的事情来计算其自身的聚合。

聚合在与 GROUP BY 子句结合使用时也非常有用。例如,我们可以得到每个城市观测到的最高低温

SELECT city, max(temp_lo)
FROM weather
GROUP BY city;
城市 max(temp_lo)
San Francisco 46
Hayward 37

这将为每个城市提供一个输出行。每个聚合结果都是针对匹配该城市的表行进行计算的。我们可以使用 HAVING 来过滤这些分组的行

SELECT city, max(temp_lo)
FROM weather
GROUP BY city
HAVING max(temp_lo) < 40;
城市 max(temp_lo)
Hayward 37

这对于只有所有 temp_lo 值都低于 40 的城市,给出了相同的结果。最后,如果我们只关心名称以 S 开头的城市,可以使用 LIKE 运算符

SELECT city, max(temp_lo)
FROM weather
WHERE city LIKE 'S%'            -- (1)
GROUP BY city
HAVING max(temp_lo) < 40;

有关 LIKE 运算符的更多信息,可以在模式匹配页面中找到。

理解聚合函数与 SQL 的 WHEREHAVING 子句之间的交互非常重要。WHEREHAVING 之间的根本区别在于:WHERE 在计算分组和聚合之前选择输入行(因此它控制哪些行进入聚合计算),而 HAVING 在计算分组和聚合之后选择组行。因此,WHERE 子句不得包含聚合函数;试图使用聚合函数来确定哪些行作为聚合的输入是没有意义的。另一方面,HAVING 子句总是包含聚合函数。

在前面的例子中,我们可以在 WHERE 中应用城市名称限制,因为它不需要聚合。这比将限制添加到 HAVING 中更有效,因为我们避免了对所有未通过 WHERE 检查的行进行分组和聚合计算。

更新

您可以使用 UPDATE 命令更新现有行。假设您发现 11 月 28 日之后的温度读数都偏差了 2 度。您可以按如下方式更正数据

UPDATE weather
SET temp_hi = temp_hi - 2,  temp_lo = temp_lo - 2
WHERE date > '1994-11-28';

查看数据的新状态

SELECT *
FROM weather;
城市 temp_lo temp_hi prcp date
San Francisco 46 50 0.25 1994-11-27
San Francisco 41 55 0.0 1994-11-29
Hayward 35 52 NULL 1994-11-29

删除

可以使用 DELETE 命令从表中删除行。假设您不再对 Hayward 的天气感兴趣。那么您可以执行以下操作来从表中删除这些行

DELETE FROM weather
WHERE city = 'Hayward';

所有属于 Hayward 的天气记录都被删除了。

SELECT *
FROM weather;
城市 temp_lo temp_hi prcp date
San Francisco 46 50 0.25 1994-11-27
San Francisco 41 55 0.0 1994-11-29

在发出以下形式的语句时应该谨慎

DELETE FROM table_name;

警告:如果没有限定条件,DELETE 将从给定表中删除所有行,使其变为空表。系统在执行此操作之前不会请求确认。

© 2025 DuckDB 基金会,阿姆斯特丹,荷兰
行为准则 商标使用指南