分享到

简介

要在数据库中使用数据,您需要能够有效地检索和定位特定的记录。通过在查询中使用筛选子句,您可以添加特定条件,以便仅返回最相关的记录。

在本指南中,我们将了解 PostgreSQL 中可用的一些最常见的筛选操作,并演示如何使用它们来缩小语句的焦点。我们将展示如何使用 WHERE 子句测试单个记录中的特征,如何使用 GROUP BY 将记录分组在一起以汇总信息,如何使用 HAVING 子句筛选记录组,以及如何使用 LIMIT 子句设置返回的最大行数。

使用 WHERE 子句定义匹配条件

WHERE 子句是指示查询要求的最常见和广泛有用的方法之一。WHERE 子句允许您通过指定所有匹配记录必须为真的条件,为查询语句定义实际的搜索条件。

WHERE 子句的工作原理是定义针对数据的每个候选行检查的布尔表达式。如果表达式的结果为假,则该行将从结果中删除,并且不会返回或继续进行下一阶段的处理。如果表达式的结果为真,则它满足搜索条件,并将作为候选行继续进行任何进一步的处理。

WHERE 子句的基本语法如下所示

SELECT * FROM my_table WHERE <condition>;

<condition> 可以是任何产生布尔值的内容。在 PostgreSQL 中,布尔值可以是 TRUEFALSENULL 中的任何一个。

条件通常使用以下一个或多个运算符形成

  • =:等于
  • >:大于
  • <:小于
  • >=:大于或等于
  • <=:小于或等于
  • <>!=:不等于
  • AND:逻辑“与”运算符 — 连接两个条件,如果两个条件都为 TRUE,则返回 TRUE
  • OR:逻辑“或”运算符 — 连接两个条件,如果至少一个条件为 TRUE,则返回 TRUE
  • IN:值包含在后面的列表、系列或范围中
  • BETWEEN:值包含在后面的最小值和最大值之间的范围内,包括端点
  • IS NULL:如果值为 NULL,则匹配
  • NOT:否定后面的布尔值
  • EXISTS:后面的查询包含结果
  • LIKE:根据模式匹配(使用通配符 % 匹配 0 个或多个字符,_ 匹配单个字符)
  • ILIKE:根据模式匹配(使用通配符 % 匹配 0 个或多个字符,_ 匹配单个字符),不区分大小写
  • SIMILAR TO:使用 SQL 的正则表达式方言根据模式匹配
  • ~:使用 POSIX 正则表达式根据模式匹配,区分大小写
  • ~*:使用 POSIX 正则表达式根据模式匹配,不区分大小写
  • !~:不使用 POSIX 正则表达式根据模式匹配,区分大小写
  • !~*:不使用 POSIX 正则表达式根据模式匹配,不区分大小写

虽然以上列表代表了一些最常见的测试结构,但还有许多其他产生布尔结果的运算符可以与 WHERE 子句结合使用。

使用 WHERE 的示例

最常见和最直接的检查之一是使用 = 运算符进行相等性检查。在这里,我们检查 customer 表中的每一行是否具有等于 Smithlast_name

SELECT * FROM customer WHERE last_name = 'Smith';

我们可以向其中添加其他条件,以使用逻辑运算符创建复合表达式。此示例使用 AND 子句添加针对 first_name 列的附加测试。有效行必须满足给定的两个条件

SELECT * FROM customer WHERE first_name = 'John' AND last_name = 'Smith';

同样,我们可以检查是否满足一系列条件中的任何一个。在这里,我们检查 address 表中的行,以查看 zip_code 值是否等于 60626,或者 neighborhood 列是否等于字符串“Roger's Park”。我们使用两个单引号来指示应搜索文字单引号

SELECT * FROM address WHERE zip_code = '60626' OR neighborhood = 'Roger''s Park';

IN 运算符可以像在括号中包装的多个值之间的比较一样工作。如果与任何给定值匹配,则表达式为 TRUE

SELECT * FROM customer WHERE last_name IN ('Smith', 'Johnson', 'Fredrich');

在这里,我们使用 LIKE 检查字符串模式。% 用作通配符,匹配零个或多个字符,因此“Pete”、“Peter”以及任何以“Pete”开头的字符串都将匹配

SELECT * FROM customer WHERE last_name LIKE 'Pete%';

我们可以使用 ~* 运算符进行类似的搜索,以检查使用 POSIX 正则表达式的匹配项,而不考虑大小写。在这种情况下,我们检查 last_name 的值是否以“d”开头并包含子字符串“on”,这将匹配诸如“Dickson”、“Donald”和“Devon”之类的名称

SELECT * FROM customer WHERE last_name ~* '^D.*on.*';

我们可以使用 BETWEENAND 运算符定义包含范围,来检查街道号码是否在 4000 号地址块内

SELECT * FROM address WHERE street_number BETWEEN 4000 AND 4999;

在这里,我们可以显示任何社会安全号码长度不是 9 位数字的 customer 条目。我们使用 LENGTH() 运算符获取字段中的位数,并使用 <> 检查不相等

SELECT * FROM customer WHERE LENGTH(SSN) <> 9;

使用 GROUP BY 子句汇总多个记录

GROUP BY 子句是另一种非常常见的筛选结果的方法,它用单行表示多个结果。GROUP BY 子句的基本语法如下所示

SELECT <columns> FROM some_table GROUP BY <columns_to_group>

当语句中添加 GROUP BY 子句时,它会告诉 PostgreSQL 为给定列或列的每个唯一值显示一行。这有一些重要的含义。

由于 GROUP BY 子句是将多行表示为单行的一种方式,因此只有当 PostgreSQL 可以计算出它要显示的每个列的值时,它才能执行查询。这意味着由语句的 SELECT 部分标识的每个列都必须是

  • 包含在 GROUP BY 子句中,以保证每行都有一个唯一值
  • 抽象化以汇总每个组中的所有行

实际上,这意味着 SELECT 列表中未包含在 GROUP BY 子句中的任何列都必须使用聚合函数,以便为每个组的列生成单个结果。

使用 GROUP BY 的示例

对于本节中的示例,假设我们有一个名为 pet 的表,我们已定义并填充如下

CREATE TABLE pet (
id SERIAL PRIMARY KEY,
type TEXT,
name TEXT,
color TEXT,
age INT
);
INSERT INTO pet (type, name, color, age) VALUES
('dog', 'Spot', 'brown', 3),
('dog', 'Rover', 'black', 7),
('dog', 'Sally', 'brown', 1),
('cat', 'Sabrina', 'black', 8),
('cat', 'Felix', 'white', 4),
('cat', 'Simon', 'orange', 8),
('rabbit', 'Buttons', 'grey', 4),
('rabbit', 'Bunny', 'brown', 8),
('rabbit', 'Briony', 'brown', 6);

GROUP BY 最简单的用法是显示单个列的唯一值范围。为此,请在 SELECTGROUP BY 中使用相同的列。在这里,我们看到了表中使用的所有颜色

SELECT color FROM pet GROUP BY color;
color
--------
black
grey
brown
white
orange
(5 rows)

当您在 SELECT 列列表中超出单列时,您必须将列添加到 GROUP BY 子句,或者使用聚合函数为表示的行组生成单个值。

在这里,我们将 type 添加到 GROUP BY 子句,这意味着每一行将表示 typecolor 值的唯一组合。我们还添加了 age 列,通过 avg() 函数汇总,以查找每个组的平均年龄

SELECT type, color, avg(age) AS average_age FROM pet GROUP BY type, color;
type | color | average_age
--------+--------+--------------------
rabbit | brown | 7.0000000000000000
cat | black | 8.0000000000000000
rabbit | grey | 4.0000000000000000
dog | black | 7.0000000000000000
dog | brown | 2.0000000000000000
cat | orange | 8.0000000000000000
cat | white | 4.0000000000000000
(7 rows)

聚合函数在 GROUP BY 子句中使用单列的效果也很好。在这里,我们找到了每种动物类型的平均年龄

SELECT type, avg(age) AS average_age FROM PET GROUP BY type;
type | average_age
--------+--------------------
rabbit | 6.0000000000000000
dog | 3.6666666666666667
cat | 6.6666666666666667
(3 rows)

如果我们想显示每种动物类型中最年长的动物,我们可以改为在 age 列上使用 max() 函数。GROUP BY 子句将结果折叠到与以前相同的行中,但是新函数会更改另一列中的结果

SELECT type, max(age) AS oldest FROM pet GROUP BY type;
type | oldest
--------+-------
rabbit | 8
dog | 7
cat | 8
(3 rows)

使用 HAVING 子句筛选记录组

GROUP BY 子句是一种通过将多个记录折叠为单个代表行来汇总数据的方法。但是,如果您想根据其他因素缩小这些组的范围怎么办?

HAVING 子句是 GROUP BY 子句的修饰符,可让您指定每个组必须满足的条件才能包含在结果中。

通用语法如下所示

SELECT <columns> FROM some_table GROUP BY <columns_to_group> HAVING <condition>

该操作与 WHERE 子句非常相似,不同之处在于 WHERE 筛选单个记录,而 HAVING 筛选记录组。

使用 HAVING 的示例

使用我们在上一节中介绍的同一张表,我们可以演示 HAVING 子句的工作原理。

在这里,我们按 type 列中唯一的值对 pet 表的行进行分组,并找到 age 的最小值。HAVING 子句然后筛选结果,以删除年龄不大于 1 的任何组

SELECT type, min(age) AS youngest FROM pet GROUP BY type HAVING min(age) > 1;
type | youngest
--------+----------
rabbit | 4
cat | 4
(2 rows)

在此示例中,我们按颜色对 pet 中的行进行分组。然后,我们筛选仅表示单行的组。结果向我们显示了每个出现多次的颜色

SELECT color FROM pet GROUP BY color HAVING count(color) > 1;
color
-------
black
brown
(2 rows)

我们可以执行类似的查询来获取只有一种动物的 typecolor 的组合

SELECT type, color FROM pet GROUP BY type, color HAVING count(color) = 1;
type | color
--------+--------
cat | black
rabbit | grey
dog | black
cat | orange
cat | white
(5 rows)

使用 LIMIT 子句设置最大记录数

LIMIT 子句提供了一种不同的方法来削减查询返回的记录。 LIMIT 子句不是根据行本身内的条件消除数据行,而是设置查询返回的最大记录数。

LIMIT 的基本语法如下所示

SELECT * FROM my_table LIMIT <num_rows> [OFFSET <num_rows_to_skip>];

在这里,<num_rows> 指示从执行的查询中显示的最大行数。这通常与 ORDER BY 子句结合使用,以获取在特定列中具有最极端值的行。例如,要获得考试中排名前五名的分数,用户可以 ORDER BY score 列,然后将结果 LIMIT 为 5。

虽然 LIMIT 默认情况下从结果的顶部开始计数,但可选的 OFFSET 关键字可用于偏移它使用的起始位置。实际上,这允许您通过显示由 LIMIT 定义的结果数,然后将 LIMIT 数添加到 OFFSET 以检索下一页,从而分页浏览结果。

使用 LIMIT 的示例

在本节的示例中,我们将使用前面提到的 pet 表。

如上所述,LIMIT 通常与 ORDER BY 子句结合使用,以显式定义在切片适当数量之前的行排序。在这里,我们根据 pet 条目的 age(从最老到最年轻)对其进行排序。然后,我们使用 LIMIT 显示最老的 5 种动物

SELECT * FROM pet ORDER BY age DESC LIMIT 5;
type | name | color | age | id
--------+---------+--------+-----+----
cat | Simon | orange | 8 | 6
cat | Sabrina | black | 8 | 4
rabbit | Bunny | brown | 8 | 8
dog | Rover | black | 7 | 2
rabbit | Briany | brown | 6 | 9
(5 rows)

如果没有 ORDER BY 子句,LIMIT 将以完全可预测的方式进行选择。返回的结果可能会受到表内条目的顺序或索引的影响。这并不总是一件坏事。

如果我们需要表中任何单个 dog 的记录,我们可以构造如下查询。请记住,虽然结果可能难以预测,但这并不是随机选择,不应如此使用

SELECT * FROM pet WHERE type = 'dog' LIMIT 1;
type | name | color | age | id
------+------+-------+-----+----
dog | Spot | brown | 3 | 1
(1 row)

我们可以使用 OFFSET 子句分页浏览结果。我们包含一个 ORDER BY 子句来定义结果的特定顺序。

对于第一个查询,我们限制结果而不指定 OFFSET,以获取前 3 个最年轻的条目

SELECT * FROM pet ORDER BY age LIMIT 3;
type | name | color | age | id
------+-------+-------+-----+----
dog | Sally | brown | 1 | 3
dog | Spot | brown | 3 | 1
cat | Felix | white | 4 | 5
(3 rows)

要获取接下来的 3 个最年轻的条目,我们可以将 LIMIT 中定义的数字添加到 OFFSET,以跳过我们已经检索到的结果

SELECT * FROM pet ORDER BY age LIMIT 3 OFFSET 3;
type | name | color | age | id
--------+---------+-------+-----+----
rabbit | Buttons | grey | 4 | 7
rabbit | Briany | brown | 6 | 9
dog | Rover | black | 7 | 2
(3 rows)

如果我们再次将 LIMIT 添加到 OFFSET,我们将获得接下来的 3 个结果

SELECT * FROM pet ORDER BY age LIMIT 3 OFFSET 6;
type | name | color | age | id
--------+---------+--------+-----+----
cat | Simon | orange | 8 | 6
rabbit | Bunny | brown | 8 | 8
cat | Sabrina | black | 8 | 4
(3 rows)

这使我们可以从查询中以可管理的数据块检索行。

结论

有很多方法可以筛选和以其他方式约束从查询中获得的结果。WHEREHAVING 之类的子句评估潜在的行或行组,以查看它们是否满足某些条件。GROUP BY 子句通过将具有一个或多个公共列值的记录分组在一起,帮助您汇总数据。LIMIT 子句为用户提供了设置要检索的最大记录数的硬性限制的能力。

学习如何单独或组合应用这些子句,将使您能够从大型数据集中提取特定数据。查询修饰符和筛选器对于将 PostgreSQL 中存在的数据转化为有用的答案至关重要。

FAQ

您可以通过添加其他条件以使用运算符创建复合表达式,从而在 PostgreSQL 中筛选多个列。

附加条件的一个示例是逻辑运算符 AND。语法的示例如下所示

SELECT * FROM customer WHERE first_name = 'John' AND last_name = 'Smith';

您可以使用 LIMIT 子句限制查询返回的记录数。 LIMIT 子句不是根据行本身内的条件消除数据行,而是设置返回的最大记录数。

基本语法如下所示

SELECT * FROM my_table LIMIT <num_rows> [OFFSET <num_rows_to_skip>];

PostgreSQL 中的聚合函数是从一组输入值计算单个结果的函数。

PostgreSQL 的 array_agg() 是一个用户定义的聚合函数,它接受一组值并返回一个数组,其中输入集中的每个值都分配给数组的元素。

是的,您可以在 PostgreSQL 中按时间戳值进行 GROUP BY。但是,此值包含一个时间元素,该元素将需要时间戳精确到毫秒才能分组在一起。

按天、月或年将时间戳分组在一起的最佳方法是将时间戳值强制转换为PosgreSQL 的 DATE 数据类型,该数据类型没有关联的时间值。

关于作者
Justin Ellingwood

Justin Ellingwood

自 2013 年以来,Justin 一直在撰写关于数据库、Linux、基础设施和开发者工具的文章。他目前与妻子和两只兔子住在柏林。他通常不必用第三人称写作,这对所有相关方来说都是一种解脱。