unvdb基础之sql语句

SQL基本语句使用及解释

SELECT查询、检索语句

此语句可从单个或多个表中检索数据,经常使用

单个表检索数据:

语法:

第一种

SELECT
 user_id
FROM
 table_name 
 [other_clauses];

第二种

SELECT user_id FROM table_name [other_clauses];

以上两种并没有区别,但是本人觉得第一种SQL语句更清晰一点,但是本人习惯使用第二种。

此语法中,SELECTFROM 是关键字, 意思是 选择

其中, user_id 表明要查找那个字段,而 table_name 是从那个数据表中查找。

字段可以自己定义查找的范围,还有全表扫描**(但是不建议使用)** 如下:

SELECT user_id,name,age FROM table_name [other_clauses];
#以下全表扫描
SELECT * FROM table_name [other_clauses];

[other_clauses] 是表示SELECT子句的占位符,子句相当于附加条件。

以下是子句的解释:

DISTINCT 运算符用于从结果集中选择不重复的行。

ORDER BY 子句用于对查询结果集中的行进行排序。

WHERE 子句用于根据指定的条件过滤行。

LIMITFETCH 子句用于从查询结果中选择一定数量(或百分比)的行比如前几行后几行。

GROUP BY 可以根据一个或多个列的值将查询结果划分为多个组。

HAVING 用于在GROUP BY子句之后对分组进行过滤。就是把过滤出的分组按要求在进行过滤。

​ 使用诸如 INNER JOIN, LEFT JOIN, FULL OUTER JOIN, CROSS JOIN 之类的与其他表连接。

​ 使用 UNIONINTERSECTEXCEPT执行集合运算。其中的大致意思是

UNION 操作会合并两个集合,并去除重复的元素,返回一个包含两个集合所有不重复元素的新集合。

INTERSECT 操作会返回两个集合中共有的元素,即返回一个包含两个集合共有元素的新集合。

EXCEPT 操作会从第一个集合中移除与第二个集合中相同的元素,返回一个包含第一个集合中不属于第二个集合的元素的新集合。

详细的子句使用方法将在之后提及。

unvdb SELECT语句实际使用

单列查询
SELECT user_id FROM table_name;

 user_id 
---------
       1
       2
       3
       4
       5
       6
(6 行记录)
多列查询

此实例大致意思就是可以查看自己想看的列

 SELECT user_id,name,age,locked,  created_at  FROM table_name;
  user_id | name  | age | locked |         created_at         
---------+-------+-----+--------+----------------------------
       1 | huaji |  18 | t      | 2023-07-14 14:28:41.628464
       2 | huaj  |  18 | t      | 2023-07-14 14:29:04.38336
       3 | huaj2 |  18 | t      | 2023-07-14 14:29:23.121366
       4 | huaj2 |  18 | t      | 2023-07-14 14:29:42.07722
       5 | huaj3 |  18 | t      | 2023-07-14 14:29:54.351195
       6 | huaj6 |  18 | f      | 2023-07-14 14:35:31.500747
(6 行记录)
全表扫描

此实例是全表扫描所有列 ,其中(*)代表 所有的意思。

SELECT * FROM table_name ;
 user_id | name  | age | locked |         created_at         
---------+-------+-----+--------+----------------------------
       1 | huaji |  18 | t      | 2023-07-14 14:28:41.628464
       2 | huaj  |  18 | t      | 2023-07-14 14:29:04.38336
       3 | huaj2 |  18 | t      | 2023-07-14 14:29:23.121366
       4 | huaj2 |  18 | t      | 2023-07-14 14:29:42.075722
       5 | huaj3 |  18 | t      | 2023-07-14 14:29:54.351195
       6 | huaj6 |  18 | f      | 2023-07-14 14:35:31.500747
(6 行记录)

此查询语句不建议使用,由于生产环境下表的数据量大,不像自己的实验或者测试环境下比较少,这样使用的话会加大性能的损耗,占用更多的资源从而使自己的应用性能下降。

表达式查询

表达式的使用范围很广以下是一个简单的表达式检索。

SELECT name||''||created_at  FROM huaji ;
            ?column?             
---------------------------------
 huaji2023-07-14 14:28:41.628464
 huaj2023-07-14 14:29:04.38336
 huaj22023-07-14 14:29:23.121366
 huaj22023-07-14 14:29:42.075722
 huaj32023-07-14 14:29:54.351195
 huaj62023-07-14 14:35:31.500747
(6 行记录)

在SQL中,双竖线(||)用于字符串的拼接操作。在查询中|| ‘’ || 用于将 name、空字符串(’’)和 created_at这三个字符串拼接在一起,形成一个完整的姓名。查询结果会返回每个客户的完整姓名和时间。

SELECT语句可以进行计算表达示例

如果只想要单纯的计算一个表达式,可以省略 SELECT 语句中的 FROM 子句。 以下 SELECT 语句用来计算 5 * 3 的结果:

test_1=# SELECT 5*3;
 ?column? 
----------
       15
(1 row)

WHERE的用法与示例

WHERE子句通常用与过滤返回的行,使最后的结果限定在我们定义的条件中。

WHERE子句的概述及例子

以下三个示例中的query_condition (查询条件)代表WHERE子句的占位符。

要返回满足指定条件的行,请按如下语法使用带有 WHERE 子句的 SELECT 语句:

SELECT columns_list
FROM table_name
WHERE query_condition;

要更新满足指定条件的行,请按如下语法使用带有 WHERE 子句的 UPDATA 语句:

UPDATA table_name
SET column_name = value1, ...
WHERE query_condition;

要删除满足指定条件的行,请按如下语法使用带有 WHERE 子句的 DELETE 语句:

DELETE FROM table_name
WHERE query_condition;

WHERE 子句中的 query_condition (查询条件)为布尔表达式。布尔表达式的结果必须为 true, false, 或者 NULL。它可以是一个布尔表达式或使用 ANDOR 运算符的布尔表达式的组合。

WHERE 子句使用 query_condition 来过滤表中的行:

  • 只有导致 query_condition 评估为真的行才会包含在 SELECT 语句返回的结果集中。

  • 只有导致 query_condition 评估为真的行才会被 UPDATE 语句更新。

  • 只有导致 query_condition 评估为真的行才会被 DELETE 语句删除。

以下是WHERE子句的示例

运算符的使用灵活可以根据自己的想法使用

下面的子句用于查询lockedf 的用户
SELECT name,locked FROM huaji WHERE locked = 'f';
 name  | locked 
-------+--------
 huaj6 | f
(1 row)
WHERE 子句中使用 AND 运算符

AND(和)

AND代表组合两个布尔表达式来检索idname为6和huaji6的用户

此语句一旦有一个条件未检索到则不反回数据。

 SELECT user_id,name FROM huaji WHERE user_id = '6' AND name = 'huaj6';
 user_id | name  
---------+-------
       6 | huaj6
(1 row)
WHERE 子句中使用 OR 运算符

OR(或)

OR 运算符检索user_id1namehuaji3的用户

此语句一个条件未检索到不影响后一个条件的执行检索。

 SELECT user_id,name FROM huaji WHERE user_id = '1' OR name = 'huaj3';
 user_id | name  
---------+-------
       1 | huaji
       5 | huaj3
(2 rows)
WHERE 子句中使用 IN 运算符

IN 运算符检索列表中一个你给出的字符串范围中的数据。

SELECT user_id,name,age FROM huaji WHERE age IN (18);
 user_id | name  | age 
---------+-------+-----
       1 | huaji |  18
       2 | huaj  |  18
       3 | huaj2 |  18
       4 | huaj2 |  18
       5 | huaj3 |  18
       6 | huaj6 |  18
(6 rows)
WHERE 子句中使用 LIKE 运算符

LIKE 检索指定模式匹配的字符串。

test_1=# SELECT name,age FROM huaji WHERE name LIKE 'h%';
 name  | age 
-------+-----
 huaji |  18
 huaj  |  18
 huaj2 |  18
 huaj2 |  18
 huaj3 |  18
 huaj6 |  18
(6 rows)
这个是查找以h开头的用户
SELECT name,age FROM huaji WHERE name LIKE '%6';
 name  | age 
-------+-----
 huaj6 |  18
(1 row)
这个是查询以6结尾的用户

通配符 % 可以匹配任意长度的任意字符,但是必须有这个字符串开头或结尾的用户否则不反回数据。

WHERE 子句中使用 BETWEEN 运算符

BETWEEN (之间)

VETWEEN 运算符检索名字以h开头包含

SELECT name,LENGTH(name) changdu FROM huaji WHERE name LIKE'h%'ANDLENGTH(name) BETWEEN 4 AND 5 ORDER BY changdu;
 name  | changdu 
-------+---------
 huaj  |       4
 huaji |       5
 huaj2 |       5
 huaj2 |       5
 huaj3 |       5
 huaj6 |       5
(6 rows)

在这个例子中,我们使用了获取输入字符串的字符数的 LENGTH() 函数。

WHERE 子句中使用不等运算符 (<>)运算符

此示例查找名字以 hua 开头而locked不以 f 开头的客户

SELECT name,locked FROM huaji WHERE name LIKE 'hua%' AND locked <> 'f';
name  | locked 
-------+--------
 huaji | t
 huaj  | t
 huaj2 | t
 huaj2 | t
 huaj3 | t
(5 rows)

您可以使用 != 运算符代替 <> 运算符,因为它们是等效的。

以下是可以在WHERE子句中使用的运算符

  • = : 相等

  • > : 大于

  • < : 小于

  • >= : 大于或等于

  • <= : 小于或等于

  • <> : 不相等

  • != : 不相等,等同于 <>

  • AND : 逻辑与运算符

  • OR : 逻辑或运算符

  • IN : 如果值与列表中的任何值匹配,则返回 true

  • BETWEEN : 如果一个值在一个值范围之间,则返回 true

  • LIKE : 如果值与模式匹配,则返回 true

  • IS NULL : 如果值为 NULL,则返回 true

  • NOT : 否定其他运算符的结果

ORDER BY 运算符用法与示例

ORDER BY 子句用来对 SELECT 语句返回的结果集排序。如果没有为 SELECT 语句指定 ORDER BY 子句,SELECT 语句返回的结果集是按照数据库默认的规则排序。

ORDER BY 子句的使用示例

SELECT column1, column2, ... FROM table_name [WHERE clause] ORDER BY column1 [ASC|DESC],[column2 [ASC|DESC],...][NULLS FIRST | NULLS LAST];

说明:

  • 您可以为 ORDER BY 子句指定一个或多个列或者表达式。

  • ASC 代表升序,DESC 代表降序。这是可选的,默认值是 ASC

  • 当指定多个排序表达式时,首先按照前面的表达式排序,其次按照后面的列排序。

  • NULLS FIRSTNULLS LAST 用来指定对 null 值排序规则:

    • NULLS FIRST: null 值在非 null 值之前。

    • NULLS LAST: null 值在非 null 值之后。

    默认情况下,nuvdb采用升序排序时采用 NULLS LAST,降序排序时采用 NULLS FIRST。 也就是说,unvdb 默认 null 值比非 null 值大。

ORDER BY 排序规则说明

下面说明多种情况下的排序规则:

  • ORDER BY column ASC;

    ORDER BY 子句对结果集按 column 列的值升序排序。

  • ORDER BY column DESC;

    ORDER BY 子句对结果集按 column 列的值降序排序。

  • ORDER BY column;

    ORDER BY 子句对结果集按 column 列的值升序排序。这个语句等效于: ORDER BY column ASC;

  • ORDER BY column1, column2;

    ORDER BY 子句对结果集先按 column1 列的值升序排序,然后再按 column2 列的值升序排序。

    也就是说主排序按 column1 列升序排序,在主排序的基础上,那些 column1 列值相同的行,再按 column2 列升序排序。

  • ORDER BY column1 DESC, column2;

    ORDER BY 子句对结果集先按 column1 列的值降序排序,然后再按按 column2 列的值升序排序。

    也就是说主排序按 column1 列降序排序,在主排序的基础上,那些 column1 列值相同的行,再按 column2 列升序排序。

unvdb ORDER BY 实例

按字段升序排序

以下 SQL 语句使用 ORDER BY 子句按员工姓氏升序进行排序。

SELECT
    actor_id, first_name, last_name
FROM
    actor
ORDER BY last_name;
 actor_id | first_name  |  last_name
----------+-------------+--------------
      182 | DEBBIE      | AKROYD
       92 | KIRSTEN     | AKROYD
       58 | CHRISTIAN   | AKROYD
      194 | MERYL       | ALLEN
      145 | KIM         | ALLEN
      118 | CUBA        | ALLEN
       76 | ANGELINA    | ASTAIRE
      112 | RUSSELL     | BACALL
      190 | AUDREY      | BAILEY
       67 | JESSICA     | BAILEY
      115 | HARRISON    | BALE
      187 | RENEE       | BALL
...
(200 rows)
按字段降序排序

以下 SQL 语句使用 ORDER BY 子句按演员姓氏降序进行排序。

SELECT
    actor_id, first_name, last_name
FROM
    actor
ORDER BY last_name DESC;
 actor_id | first_name  |  last_name
----------+-------------+--------------
      186 | JULIA       | ZELLWEGER
      111 | CAMERON     | ZELLWEGER
       85 | MINNIE      | ZELLWEGER
       63 | CAMERON     | WRAY
       13 | UMA         | WOOD
      156 | FAY         | WOOD
      144 | ANGELA      | WITHERSPOON
       68 | RIP         | WINSLET
      147 | FAY         | WINSLET
      168 | WILL        | WILSON
      164 | HUMPHREY    | WILLIS
       96 | GENE        | WILLIS
...
(200 rows)
按多字段排序

以下 SQL 语句使用 ORDER BY 子句先按演员姓氏升序排序,再按演员名字升序排序。

SELECT
    actor_id, first_name, last_name
FROM
    actor
ORDER BY last_name, first_name;
 actor_id | first_name  |  last_name
----------+-------------+--------------
       58 | CHRISTIAN   | AKROYD
      182 | DEBBIE      | AKROYD
       92 | KIRSTEN     | AKROYD
      118 | CUBA        | ALLEN
      145 | KIM         | ALLEN
      194 | MERYL       | ALLEN
       76 | ANGELINA    | ASTAIRE
      112 | RUSSELL     | BACALL
      190 | AUDREY      | BAILEY
       67 | JESSICA     | BAILEY
      115 | HARRISON    | BALE
      187 | RENEE       | BALL
...
(200 rows)
按自定义顺序排序

有时候单纯的按照字段的值排序并不能满足要求,我们需要按照自定义的顺序的排序。比如,我们需要按照电影分级 'G', 'PG', 'PG-13', 'R', 'NC-17' 的顺序对影片进行排序。

对于这样的需求,它可以理解为按照列表中元素的索引位置进行排序。我们使用 CASE 子句函数实现它。

在以下实例中,我们使用 Sakila 示例数据库中的 film作为演示。

假设您要根据影片的分级按照的 'G', 'PG', 'PG-13', 'R', 'NC-17' 顺序对影片进行排序。

使用 CASE 实现自定义排序
SELECT
    film_id, title, rating
FROM
    film
ORDER BY CASE rating
    WHEN 'G' THEN 1
    WHEN 'PG' THEN 2
    WHEN 'PG-13' THEN 3
    WHEN 'R' THEN 4
    WHEN 'NC-17' THEN 5
END;
     357 | GILBERT PELICAN             | G
     597 | MOONWALKER FOOL             | G
     354 | GHOST GROUNDHOG             | G
...
     595 | MOON BUNCH                  | PG
       6 | AGENT TRUMAN                | PG
     600 | MOTIONS DETAILS             | PG
...
       9 | ALABAMA DEVIL               | PG-13
     657 | PARADISE SABRINA            | PG-13
     956 | WANDA CHAMBER               | PG-13
...
     749 | RULES HUMAN                 | R
       8 | AIRPORT POLLOCK             | R
      17 | ALONE TRIP                  | R
...
     520 | LICENSE WEEKEND             | NC-17
     517 | LESSON CLEOPATRA            | NC-17
     114 | CAMELOT VACATION            | NC-17
...
(1000 rows)

在这个例子中,我们使用 CASE 子句将电影的分级转换为一个数字。然后使用 ORDER BY 按照这个数字进行排序。

ORDER BY 和 NULL

在unvdb 中的升序排序中, NULL 值出现在非 NULL 值之前。

我们下面的实例使用以下临时数据作为演示:

SELECT 'A' AS v
UNION ALL
SELECT 'B' AS v
UNION ALL
SELECT NULL AS v
UNION ALL
SELECT '0' AS v
UNION ALL
SELECT '1' AS v;
   v
--------
 A
 B
 <null>
 0
 1
(5 rows)

当我们使用 ORDER BY 子句升序 ASC 排序时, NULL 值默认排在非 NULL 值的后面。如下:

SELECT 'A' AS v
UNION ALL
SELECT 'B' AS v
UNION ALL
SELECT NULL AS v
UNION ALL
SELECT '0' AS v
UNION ALL
SELECT '1' AS v
ORDER BY v;
   v
--------
 0
 1
 A
 B
 <null>

这里,ASC 排序采用 NULLS LAST 规则,所以 NULL 值在最后。 如果您想要改用 NULLS FIRST,请执行以下语句:

SELECT 'A' AS v
UNION ALL
SELECT 'B' AS v
UNION ALL
SELECT NULL AS v
UNION ALL
SELECT '0' AS v
UNION ALL
SELECT '1' AS v
ORDER BY v NULLS FIRST;
   v
--------
 <null>
 0
 1
 A
 B

当我们使用 ORDER BY 子句降序 DESC 排序时, NULL 值排在非 NULL 值的前面。这是因为 DESC 排序默认采用 NULLS FIRST 规则。如下:

SELECT 'A' AS v
UNION ALL
SELECT 'B' AS v
UNION ALL
SELECT NULL AS v
UNION ALL
SELECT '0' AS v
UNION ALL
SELECT '1' AS v
ORDER BY v DESC;
   v
--------
 <null>
 B
 A
 1
 0

FETCH 用法与示例

在 unvdb 您可以在SELECT 语句中使用 FETCH 子句来限定返回的行数,降低系统资源的消耗。

FETCHLIMIT 子句作用相同,但是FETCH是SQL标准(正规军),而LIMIT子句是不标准的(杂牌军)

FETCH语法

要使用 FETCH 子句限制返回的行数,按照以下语法使用:

FETCH { FIRST | NEXT } [ rows_count ] { ROW | ROWS } ONLY
  • 可以使用 FIRSTNEXT 中的任意一个,他们含义相同。

  • 可以使用 ROWROWS 中的任意一个,他们含义相同。

  • rows_count 是要限制的行数,即返回的最大的行数。 这个是可选的,默认值为1 rows_count 必须为一个大于0的整数

完整语法

SELECT name FROM table_name [other_clauses] FETCH FIRST 1 ROWS ONLY;
 name  
-------
 huaji

其中,other_clauses 是那些可以在 SELECT 语句中使用的其他子句,比如 WHERE, ORDER BY, OFFSET

此语句经常的使用方法是一同使用 ORDER BY 子句,最后查询结果是按照指定的顺序排序,简洁明了。

在一些分页的查询的应用中,需要在 SELECT 语句结合使用 OFFSET、 子句和 FETCH 子句。如下:

SELECT column_list FROM table_name ORDER BY ... OFFSET skipped_rows FETCH FIRST rows_count ROWS ONLY;

例如:

  • 第一页可以使用: OFFSET 0 FETCH FIRST 10 ROWS ONLY 表示最多返回 10 行。

  • 第二页可以使用: OFFSET 10 FETCH FIRST 10 ROWS ONLY 表示跳过第一页的 10 行后最多返回 10 行。

  • 第三页可以使用: OFFSET 20 FETCH FIRST 10 ROWS ONLY 表示跳过前两页的 20 行后最多返回 10 行。

  • 以此类推…

FETCH 实例

FETCH 限制返回行数示例

SELECT name FROM huaji  FETCH FIRST 1 ROWS ONLY;
 name  
-------
 huaji
(1 row)

FETCH 获取前 N 行

通常,我们经常使用 FETCH 子句从表中选择具有最高或最低值的行。

例如,要获得租金最高的 10 部电影,可以按租金降序对电影进行排序,然后使用 FETCH 子句获得前 10 部电影。可以通过以下查询达到这个目的:

SELECT film_id,title,rental_rate FROM film ORDER BY rental_rate DESC, film_id FETCH FIRST 10 ROWS ONLY;

film_id |        title         | rental_rate
---------+----------------------+-------------
       2 | ACE GOLDFINGER       |        4.99
       7 | AIRPLANE SIERRA      |        4.99
       8 | AIRPORT POLLOCK      |        4.99
      10 | ALADDIN CALENDAR     |        4.99
      13 | ALI FOREVER          |        4.99
      20 | AMELIE HELLFIGHTERS  |        4.99
      21 | AMERICAN CIRCUS      |        4.99
      28 | ANTHEM LUKE          |        4.99
      31 | APACHE DIVINE        |        4.99
      32 | APOCALYPSE FLAMINGOS |        4.99

这里,为了获取租金最高的 10 部影片,我们使用了 ORDER BY rental_rate DESC, film_id 子句对影片按照租金逆序排序,并使用 FETCH FIRST 10 ROWS ONLY 子句获取前 10 行。

FETCH 和 OFFSET 分页查询示例

film 表中共有 1000 行关于影片的信息。可以通过以下带有 COUNT(*) 表达式的 SELECT 语句得到验证:

SELECT COUNT(*) FROM film;

 count
-------
  1000

假设你有一个系统需要在前端展示所有的影片信息,将 1000 行信息全部展示在一页上并不是一个好的方案。因为这个方案如下的缺点:

  • 数据库性能。 一个语句中返回大量的数据会带给数据库服务器更大的内存开销和 IO 消耗。

  • 应用性能。大量的数据会导致应用占用更大的内存,甚至回导致应用卡顿、卡死。

  • 用户的体验。用户面对大量的数据会眩晕。

一个更好的方案是分页显示所有的影片。可以很轻松地使用 带有 FETCHOFFSET 子句的 SELECT 语句实现分页查询。

假设需要每页显示 10 个影片信息,那么可以使用如下的语句获取第一页的所有行:

SELECT film_id,title,release_year FROM film ORDER BY film_id FETCH FIRST 10 ROWS ONLY;
#这里,为了让所有分页的顺序一致,我们使用 ORDER BY film_id 让影片按照 film_id 排序,并使用 FETCH FIRST 10 ROWS ONLY 限制了此查询最多返回 10 行。
 film_id |      title       | release_year
---------+------------------+--------------
       1 | ACADEMY DINOSAUR |         2006
       2 | ACE GOLDFINGER   |         2006
       3 | ADAPTATION HOLES |         2006
       4 | AFFAIR PREJUDICE |         2006
       5 | AFRICAN EGG      |         2006
       6 | AGENT TRUMAN     |         2006
       7 | AIRPLANE SIERRA  |         2006
       8 | AIRPORT POLLOCK  |         2006
       9 | ALABAMA DEVIL    |         2006
      10 | ALADDIN CALENDAR |         2006

要获取第二页要展示的10行,可以使用OFFSET 10 子句 跳过第一页的10行,再使用 FIRST 10 ROWS ONLY 限制最多返回10行。以下语句查询第二页:

SELECT film_id,title,release_year FROM film ORDER BY film_id OFFSET 10 FETCH FIRST 10 ROWS ONLY;
 film_id |        title        | release_year
---------+---------------------+--------------
      11 | ALAMO VIDEOTAPE     |         2006
      12 | ALASKA PHANTOM      |         2006
      13 | ALI FOREVER         |         2006
      14 | ALICE FANTASIA      |         2006
      15 | ALIEN CENTER        |         2006
      16 | ALLEY EVOLUTION     |         2006
      17 | ALONE TRIP          |         2006
      18 | ALTER VICTORY       |         2006
      19 | AMADEUS HOLY        |         2006
      20 | AMELIE HELLFIGHTERS |         2006

以此类推。

LIMIT 用法与实例

注意, LIMIT 子句虽然被很多数据库系统支持,但它不是 SQL 标准。 为了符合 SQL 标准,可以使用 FETCH 子句来完成同样的事情。

以下是LIMIT语句的语法:

LIMIT 最大返回行数;

完整语法如下

SELECT name FROM [子句占位] LIMIT 

在一些分页的查询的应用中,需要在 SELECT 语句使用 LIMIT 子句和 OFFSET 子句。如下:

SELECT name FROM table_name ORDER BY ... LIMIT 最大返回行数 OFFSET 跳过几行;

比如:

  • 第一页可以使用: LIMIT 10 OFFSET 0 表示最多返回 10 行。

  • 第二页可以使用: LIMIT 10 OFFSET 10 表示跳过第一页的 10 行后最多返回 10 行。

  • 第三页可以使用: LIMIT 10 OFFSET 20 表示跳过前两页的 20 行后最多返回 10 行。

  • 以此类推…

LIMIT限制返回行数示例

限制film 表查询时最多返回5行:

SELECT film_id,title,release_year FROM film ORDER BY film_id LIMIT 5;
film_id |      title       | release_year
---------+------------------+--------------
       1 | ACADEMY DINOSAUR |         2006
       2 | ACE GOLDFINGER   |         2006
       3 | ADAPTATION HOLES |         2006
       4 | AFFAIR PREJUDICE |         2006
       5 | AFRICAN EGG      |         2006

LIMIT 获取数字最高的前 N 行

SELECT user_id,name,age FROM table_name ORDER BY user_id DESC LIMIT 3;
 user_id | name  | age 
---------+-------+-----
       6 | huaj6 |  18
       5 | huaj3 |  18
       4 | huaj2 |  18

分页查询

SELECT user_id,name,age FROM table_name ORDER BY user_id LIMIT 3;

此SQL语句和FETCH语句极为相似,区别就是此语句不是SQL标准而 FETCH是SQL标准语句。

3.6.OFFSET 用法与实例

可能在使用SELECT语句时要跳过指定的行数,返回剩余的行。比如,您想要获取除成绩前 10 名之外的所有人的信息。

在此可以使用OFFSET 子句跳过指定的行数。

以下是OFFSET 子句语法:

OFFSET 要跳过几行;

完整语法如下:

SELECT column_list FROM table_name [other_clauses] OFFSET skipped_rows;

other_clauses 表示可以在 SELECT 语句中使用的其他子句,比如 WHERE, ORDER BY, LIMIT

在一些分页的查询的应用中,您需要在 SELECT 语句使用 LIMIT OR FETCH 子句和 OFFSET 子句。如下:

  • 第一页可以使用: LIMIT OR FETCH 10 OFFSET 0 表示最多返回 10 行。

  • 第二页可以使用: LIMIT OR FETCH 10 OFFSET 10 表示跳过第一页的 10 行后最多返回 10 行。

  • 第三页可以使用: LIMIT OR FETCH 10 OFFSET 20 表示跳过前两页的 20 行后最多返回 10 行。

  • 以此类推…

OFFSET示例

SELECT film_id,title,release_year FROM film ORDER BY film_id OFFSET 995;
 film_id |       title       | release_year
---------+-------------------+--------------
     996 | YOUNG LANGUAGE    |         2006
     997 | YOUTH KICK        |         2006
     998 | ZHIVAGO CORE      |         2006
     999 | ZOOLANDER FICTION |         2006
    1000 | ZORRO ARK         |         2006

此语句结合LIMIT OR FETCH 使用。

DISTINCT 用法与实例

DISTINCT 语句用于 SELECT 语句中,以使其返回一个没有重复行的结果。

DISTINCT 语法

以下是DISTINCT的使用语法:

SELECT DISTINCT column1 [, column2, ...] FROM table_name;
  • 关键字 DISTINCT 要在 SELECT 后指定。

  • 在关键字 DISTINCT 后指定需要评估是否重复的列。

  • 多个列名需要使用逗号 , 间隔。如果指定了多个列名, unvdb将根据这些列的值的组合后的值评估是否重复。

  • 您可以使用 DISTINCT * 来对所有的列进行评估重复。

我们还提供了 DISTINCT ON (expression) 使用以下语法保留每组重复项的“第一”行

SELECT DISTINCT ON (column1) column_alias,column2 FROM table_name ORDER BY column1,column2;

请注意, DISTINCT ON 表达式必须与 ORDER BY 子句中最左边的表达式相匹配。

DISTINCT 示例

要从 film 表中检索所有的同学的评级,请使用以下语句:

SELECT DISTINCT rating FROM film;
rating
--------
 A
 B
 C
 D
 E
(5 rows)

这里,为了找到所有的同学评级,我们使用了 DISTINCT rating,让每个评级只在结果集中出现一次

要从 film 表中检索所有的同学评级和分数的组合,请使用以下语句:

SELECT DISTINCT rating,mark FROM film ORDER BY rating;
 rating |  mark
--------+---------
 E      |     500
 E      |     512
 E      |     533
 D      |     560
 D      |     589
 D      |     591
 C      |     610
 C      |     633
 C      |     650
 B      |     671
 B      |     687
 B      |     693
 A      |     712
 A      |     720
 A      |     721

如果您想返回每组评级的第一项,请使用以下带有 DISTINCT ON 的语句:

SELECT DISTINCT ON (rating) rating,film_id,title FROM film ORDER BY rating, film_id DESC;
 rating | film_id |      title
--------+---------+------------------
 E      |       2 | ACE GOLDFINGER
 D      |       1 | ACADEMY DINOSAUR
 C      |       7 | AIRPLANE SIERRA
 B      |       8 | AIRPORT POLLOCK
 A      |       3 | ADAPTATION HOLES

DISTINCT 与 NULL

DISTINCT 遇到 NULL 值时,只保留一个 NULL 值。因为 DISTINCT 认为所有的 NULL 值都是相同的,这与字段的类型无关。

例如下面的 SQL 返回多行 NULL 记录:

SELECT NULL nullable_col
UNION ALL
SELECT NULL nullable_col
UNION ALL
SELECT NULL nullable_col;
 nullable_col
--------------
 <null>
 <null>
 <null>
(3 rows)

这里,我们拥有 3 行,其中每行的 nullable_col 列的值都为 NULL

当使用 DISTINCT 之后:

SELECT
    DISTINCT nullable_col
FROM
    (
    SELECT NULL nullable_col
    UNION ALL
    SELECT NULL nullable_col
    UNION ALL
    SELECT NULL nullable_col
    ) t;
 nullable_col
--------------
 <null>
(1 row)

本例使用 UNION ALL 模拟包含多个 NULL 值的记录集

DISTINCT 子句的用法要点如下:

  • SELECT DISTINCT 返回一个没有重复行的结果集。

  • DISTINCT 后面可以指定一个列或者多个列,也可以用 *

  • DISTINCT 将所有的 NULL 视为相等的,并只保留一个。

  • DISTINCT ON 子句用来返回每组重复值的第一个行。

列别名

unvdb 允许您为 SELECT 返回的列指定别名,以提高可读性和使用的便利性。

列别名语法

要在 SELECT 语句为指定的列分配别名,请使用以下的语法:

SELECT expr AS alias_name [FROM table_name];

这里:

  • expr 是一个表达式或者列名。

  • alias_nameexpr 列的别名。 SELECT 语句返回的结果集中将使用别名。

  • 如果列别名中包含空格,请使用双引号 " 将列别名包围起来。

  • AS 是一个关键字,它是可选的。您可以省略它。

也可以设置多个列别名:

SELECT expr1 AS alias_name1,expr2 AS alias_name2,... [FROM table_name];

列别名示例

要从 actor 表中检索演员的名字和姓氏,请使用以下语句:

SELECT
   first_name,
   last_name
FROM actor
LIMIT 10;
 first_name |  last_name
------------+--------------
 PENELOPE   | GUINESS
 NICK       | WAHLBERG
 ED         | CHASE
 JENNIFER   | DAVIS
 JOHNNY     | LOLLOBRIGIDA
 BETTE      | NICHOLSON
 GRACE      | MOSTEL
 MATTHEW    | JOHANSSON
 JOE        | SWANK
 CHRISTIAN  | GABLE

要为 last_name 指定一个别名 surname,请使用如下语句:

SELECT
   first_name,
   last_name AS surname
FROM
   actor
LIMIT 10;
 first_name |   surname
------------+--------------
 PENELOPE   | GUINESS
 NICK       | WAHLBERG
 ED         | CHASE
 JENNIFER   | DAVIS
 JOHNNY     | LOLLOBRIGIDA
 BETTE      | NICHOLSON
 GRACE      | MOSTEL
 MATTHEW    | JOHANSSON
 JOE        | SWANK
 CHRISTIAN  | GABLE

这里, 结果集中的列名 last_name 已经被替换为 surname

为表达式示例 分配列别名

要从 actor 表中检索演员的全名,请使用以下语句:

SELECT
   first_name || ' ' || last_name
FROM
   actor
LIMIT 10;
      ?column?
---------------------
 PENELOPE GUINESS
 NICK WAHLBERG
 ED CHASE
 JENNIFER DAVIS
 JOHNNY LOLLOBRIGIDA
 BETTE NICHOLSON
 GRACE MOSTEL
 MATTHEW JOHANSSON
 JOE SWANK
 CHRISTIAN GABLE

这里,我们使用了 || 操作符连接两个字符串。 表达式 first_name || ' ' || last_namefirst_name, 空格 和 last_name 连接在一起。

我看可以看到,表达式输出的列名为 ?column?,这是没有意义的。为了让表达式的输出列名更易读,我们需要为表达式指定一个列名,例如 full_name

要解决此问题,您可以为表达式分配 first_name || ' ' || last_name 一个列别名,例如 full_name

SELECT
    first_name || ' ' || last_name full_name
FROM
    actor
LIMIT 10;
      full_name
---------------------
 PENELOPE GUINESS
 NICK WAHLBERG
 ED CHASE
 JENNIFER DAVIS
 JOHNNY LOLLOBRIGIDA
 BETTE NICHOLSON
 GRACE MOSTEL
 MATTHEW JOHANSSON
 JOE SWANK
 CHRISTIAN GABLE

包含空格的列别名

在上面的例子中,如果您要使用 Full Name 作为列别名,因为它包含了空格,请使用双引号引用起来,即:"Full Name"

SELECT
    first_name || ' ' || last_name "Full Name"
FROM
    actor
LIMIT 10;
     Full Name
---------------------
 PENELOPE GUINESS
 NICK WAHLBERG
 ED CHASE
 JENNIFER DAVIS
 JOHNNY LOLLOBRIGIDA
 BETTE NICHOLSON
 GRACE MOSTEL
 MATTHEW JOHANSSON
 JOE SWANK
 CHRISTIAN GABLE
  • 使用语法 expr AS alias_name or 为列或表达式分配列别名 expression AS alias_name

  • AS 关键字是可选的。

  • 使用双引号 (”) 将包含空格的列别名括起来。

表别名

在 unvdb 中,除了可以为列指定别名,您也可以为表指定别名。使用表别名一般处于以下的目的:

  • 表别名可以提高 SQL 语句的可读性。

  • 表别名可以为书写 SQL 的带来便利。

  • 表别名可以解决不同表具有相同列名时可能带来的冲突。

表别名语法

要为在 unvdb 中为表指定别名,请按照如下语法:

table_name [AS] table_alias;
  • table_alias 是表 table_name 的别名。

  • 如果表别名中包含空格,使用双引号 " 将别名包围起来。不过大多数情况下,使用表别名是为了简化,不建议使用带有空格的表别名。

  • AS 关键字是可选的,它可以被省略。

表别名实例

film 表存储了所有的人员信息, inventory表中存储所有人员的存储信息。要从 film 表中查找没有存储记录的人员,请使用以下语句:

SELECT f.film_id,f.title FROM film f WHERE NOT EXISTS (SELECT 1 FROM inventory i WHERE i.film_id = f.film_id);
 film_id |         title
---------+------------------------
      14 | ALICE FANTASIA
      33 | APOLLO TEEN
      36 | ARGONAUTS TOWN
      38 | ARK RIDGEMONT
      41 | ARSENIC INDEPENDENCE
      87 | BOONDOCK BALLROOM
...
     954 | WAKE JAWS
     955 | WALLS ARTIST
(42 rows)

这里,我们为 film 指定了别名 f,为 inventory 表指定了别名 i。 请注意 EXISTS 表达式使用的子查询中的 WHERE子句:

WHERE i.film_id = f.film_id

其中,比较来自 film 表中的 film_id 和 来自 inventory 表中 film_id 是否相等。 由于两个表使用了相同的列名 film_id, 因此这里使用了他们别名,以标识是来自哪个表的 film_id

如果不使用别名,您还可以直接使用表名来引用列,如下:

WHERE inventory.film_id = film.film_id

在 unvdb 中,表别名可以简化 SQL 语句书写,增加 SQL 语句的可读性。

IN 运算符用法与实例

IN 运算符是一个布尔运算符,用来检查一个值列表是否包含一个指定的值。如果值列表包含了指定的值,则 IN 运算符返回真,否则它返回假。

IN 运算符语法:

要检查一个值是否位于一个值列表之内,请按照以下语法使用 IN 运算符:

expr IN (value1,value2,...)
或
expr IN (subquery)
  • expr 可以是一个字段名、值或其他的表达式(比如函数调用、运算等)。

  • (value1, value2, ...) 是一个值列表,多个值之间使用 , 分隔,并使用小括号 () 将它们包围起来。

  • value1 是具体的值,ke 比如:1, 2, 'A', 'B' 等。

  • subquery 是一个只返回一个列的子查询。

如果值列表或者子查询返回的值中包含了 exprIN 运算符返回真,否则它返回假。

IN 运算符的否定操作是 NOT IN

IN 与 OR 相比

IN表达式都可以使用 OR 运算符改写,如:

val IN (1,2,3)
等同于以下OR表达式
val = 1 OR val = 2 OR val=3

同理,NOT IN 表达式都可以用AND 运算符改写。

val IN (1,2,3)
等同于以下AND的表达式
val <> 1 AND val <> 2 OR val <> 3

IN 运算符实例

数据库中, actor 表存储了所有的员工的信息。

要从 actor 表中查询姓氏为 ALLENDAVIS 的所有员工,请使用以下带有 IN 运算符的语句:

SELECT * FROM actor WHERE last_name IN ('ALLEN', 'DAVIS');

您可以使用 OR 运算符改写上面的语句:

SELECT * FROM actor WHERE last_name = 'ALLEN' OR last_name = 'DAVIS';

在unvdbIN 中使用子查询

要从actor 表中检索拥有员工的数量,您可以使用带有子查询的 IN 的表达式:

SELECT COUNT(*) FROM actor WHERE actor_id IN (SELECT actor_id FROM private);
 count
-------
   666

BETWEEN 运算符的用法与实例

BETWEEN 运算符检查一个值是否位于一个区间之内。

假如,在一个应用系统中,您需要判断根据用户的年收入为用户划定等级。比如,要检查用户的年收入是否位于 40100 元和 120400 元之间,您可以使用以下语句:

annual_income >= 40100 AND annual_income <= 120400

这种情况下,您可以使用 BETWEEN 运算符改写上面的语句,如下:

annual_income BETWEEN 40100 AND 120400

BETWEEN 语法

要检查一个值是否位于一个值区间之内,请按照以下语法使用 BETWEEN 运算符:

expr BETWEEN low_value AND high_value;
  • expr 是一个表达式或者列名。

  • low_value 是值区间的开始, high_value 是值区间的结束。

  • 如果 expr 的值大于或等于 low_value 值并且小于或等于 high_value 值, BETWEEN 运算符返回真,否则返回假。

BETWEEN 运算符相当于以下使用了 大于等于 (>=) 和 小于等于 (<=) 运算符的语句:

expr >= low_value AND expr <= high_value

通常情况下 WHERE子句中使用 的BETWEEN 运算符语法, 如下所示:

SELECT * FROM table_name WHERE expr >= low_value AND expr <= high_value

当然,除了 INSERT语句外, WHERE 子句同样可以用于 UPDATE或者 DELETE语句。

您可以使用结合 NOT 运算符和 BETWEEN 运算符以检查一个值是否不在一个指定的值区间之内:

expr NOT BETWEEN low_value AND high_value;

这相当于以下使用了 大于 (>)  小于 (<) 运算符的语句:

expr < low_value OR expr > high_value

要从pay表中检索薪资在 7000 到 9000 之间的员工,请使用以下语句:

SELECT name,age FROM pay WHERE age BETWEEN 7000 AND 10000;
   name   |  age
----------+-------
 Fengfeng | 7000
 Xiaoxiao | 8000
 Jingjing | 8828
 Mingming | 8848
 Ailing   | 7747
 Xiaoming | 9418
 Yingying | 9999
 Haohao   | 7744
 Xiaochen | 7823
 Honghong | 8648

如果要取带有特殊字符的数值请这样使用:

WHERE age BETWEEN '2023-12-1' AND '2024-1-31'; 在数值上附上单引号。

LIKE 运算符用法与实例

LIKE 运算符是一个布尔运算符,用来检查一个文本是否与指定的模式匹配。如果文本与指定的模式匹配,则 LIKE 运算符返回真,否则它返回假。

ILIKE 运算符是 LIKE 运算符的不区分大小写版本,ILIKE 运算符执行不区分大小写的匹配。

LIKE 语法

LIKE 运算符是一个双目比较运算符,需要两个操作数。 LIKE 运算符语法如下:

expr LIKE pattern
expr ILIKE pattern
  • expr 可以是一个字段名、值或其他的表达式(比如函数调用、运算等)。 expr 应该是文本。

  • pattern 是一个字符串模式。它支持两个通配符: %_

    • % 匹配零或多个任意字符。

    • _ 匹配单个任意字符。

    • 如果需要匹配通配符,则需要使用 \ 转义字符,如 \%\_

    • 比如:

      • a% 匹配以字符 a 开头的任意长度的字符串。

      • %a 匹配以字符 a 结尾的任意长度的字符串。

      • %a% 匹配包含字符 a 的任意长度的字符串。

      • %a%b% 匹配同时包含字符 abab 前面的任意长度的字符串。

      • a_ 匹配以字符 a 开头长度为 2 字符串。

      • _a 匹配以字符 a 结尾长度为 2 字符串。

如果 exprpattern 匹配,LIKE 运算符返回真,否则返回假。

NOT LIKELIKE 运算符的否定操作。

NOT ILIKEILIKE 运算符的否定操作。

LIKE 运算规则

LIKE 运算符用于检测一个文本是否指定的模式匹配。它的运算规则如下:

LIKE 运算符左侧的操作数与右侧的模式匹配时,返回 t。否则,返回 f

SELECT
    'a' LIKE 'a'   "'a' LIKE 'a'",
    'a' LIKE 'a%'  "'a' LIKE 'a%'",
    'ab' LIKE 'a%' "'ab' LIKE 'a%'",
    'ab' LIKE '%a' "'ab' LIKE '%a'";
    
 'a' LIKE 'a' | 'a' LIKE 'a%' | 'ab' LIKE 'a%' | 'ab' LIKE '%a'
--------------+---------------+----------------+----------------
 t            | t             | t              | f
SELECT
    'a' LIKE 'a_'   "'a' LIKE 'a_'",
    'ab' LIKE 'a_'  "'ab' LIKE 'a_'",
    'abc' LIKE 'a_' "'abc' LIKE 'a_'";
 'a' LIKE 'a_' | 'ab' LIKE 'a_' | 'abc' LIKE 'a_'
---------------+----------------+-----------------
 f             | t              | f

如果 LIKE 运算符的两个操作数中任意一个为 NULL 时,返回 空。

SELECT
    NULL LIKE 'a%' "NULL LIKE 'a%'",
    'a' LIKE NULL  "'a' LIKE NULL ";
 NULL LIKE 'a%' | 'a' LIKE NULL
----------------+----------------
                |

LIKE 实例

使用 LIKE 匹配以某些字符开头的文本

要从表中查找名字以字符 P 开头的所有人员,请使用以下带有 LIKE 运算符的语句:

SELECT * FROM table_name WHERE first_name LIKE 'P%';
 actor_id | first_name | last_name |     last_update
----------+------------+-----------+---------------------
        1 | PENELOPE   | GUINESS   | 2006-02-15 04:34:33
       46 | PARKER     | GOLDBERG  | 2006-02-15 04:34:33
       54 | PENELOPE   | PINKETT   | 2006-02-15 04:34:33
      104 | PENELOPE   | CRONYN    | 2006-02-15 04:34:33
      120 | PENELOPE   | MONROE    | 2006-02-15 04:34:33
(5 rows)

这里, P% 模式表示以 P 开头的任意长度的文本。为了找到名字以字符 P 开头的所有人员,我们使用了表达式 first_name LIKE 'P%'

使用 LIKE 匹配以某个字符结尾的文本

要从表中查找名字以字符 S 结尾的所有人员,请使用以下带有 LIKE 运算符的语句:

SELECT * FROM table_name WHERE first_name LIKE '%s';
actor_id | first_name | last_name |     last_update
----------+------------+-----------+---------------------
       48 | FRANCES    | DAY-LEWIS | 2006-02-15 04:34:33
       84 | JAMES      | PITT      | 2006-02-15 04:34:33
      126 | FRANCES    | TOMEI     | 2006-02-15 04:34:33
(3 rows)

使用 LIKE 匹配包含某些字符开头的文本

要从表中查找名字中带有字符串 AM 的所有人员,请使用以下带有 LIKE 运算符匹配的语句:

SELECT * FROM table_name WHERE first_name LIKE '%AM%';
 actor_id | first_name | last_name |     last_update
----------+------------+-----------+---------------------
       24 | CAMERON    | STREEP    | 2006-02-15 04:34:33
       63 | CAMERON    | WRAY      | 2006-02-15 04:34:33
       71 | ADAM       | GRANT     | 2006-02-15 04:34:33
       84 | JAMES      | PITT      | 2006-02-15 04:34:33
      111 | CAMERON    | ZELLWEGER | 2006-02-15 04:34:33
      132 | ADAM       | HOPPER    | 2006-02-15 04:34:33
      175 | WILLIAM    | HACKMAN   | 2006-02-15 04:34:33
(7 rows)

使用 LIKE 匹配单个字符

通配符 _ 匹配任意单个字符。以下 SQL 语句使用 LIKE 运算符查找 first_name 以字符串 AY 结尾的且长度为 3 个字符的所有人员。

SELECT * FROM table_name WHERE first_name LIKE '_AY';
 actor_id | first_name | last_name |     last_update
----------+------------+-----------+---------------------
       55 | FAY        | KILMER    | 2006-02-15 04:34:33
       64 | RAY        | JOHANSSON | 2006-02-15 04:34:33
      147 | FAY        | WINSLET   | 2006-02-15 04:34:33
      156 | FAY        | WOOD      | 2006-02-15 04:34:33
(4 rows)

IS NULL 运算符用法与实例

IS NULL 运算符检查一个值是不是 NULL

IS NULL 是一个布尔运算符,它检查一个值是不是 NULLNULL 值是一个特殊的值,它表示什么都没有,它即不是空字符串也不是假(false)。

IS NULL 语法

IS NULL 是一个单目比较运算符,只需要一个操作数。IS NULL 运算符的语法是:

expr IS NULL
expr IS NOT NULL
  • expr 可以是一个字段名、一个值或者一个表达式。

  • IS NOT NULLIS NULL 的否定运算。

IS NULLIS NOT NULL 可以用在 SELECT 语句或者 WHERE子句中。

IS NULL 运算规则

IS NULL 运算符左侧的操作数是 NULL 时,IS NULL 运算符返回 t,否则返回 f

SELECT
    NULL IS NULL "NULL IS NULL",
    0 IS NULL "0 IS NULL",
    1 IS NULL "1 IS NULL";
 NULL IS NULL | 0 IS NULL | 1 IS NULL
--------------+-----------+-----------
 t            | f         | f

IS NOT NULLIS NULL 的否定运算。如果 IS NOT NULL 左侧的操作数不是 NULL 时,IS NOT NULL 运算符返回 t,否则返回 f

SELECT
    NULL IS NOT NULL "NULL IS NOT NULL",
    0 IS NOT NULL "0 IS NOT NULL",
    1 IS NOT NULL "1 IS NOT NULL";
 NULL IS NOT NULL | 0 IS NOT NULL | 1 IS NOT NULL
------------------+---------------+---------------
 f                | t             | t

IS NULL 实例

staff 表中, picture 中存储了职员的照片文件, 要从 staff 表中查询那些没有上传图片的职员, 需要检查 picture 是否为 NULL, 请使用以下带有 IS NULL 的 SQL 语句:

SELECT first_name, last_name, picture FROM staff WHERE picture IS NULL;
 first_name | last_name | picture
------------+-----------+---------
 Mike       | Hillyer   | <null>
 Jon        | Stephens  | <null>

要点如下:

  • IS NULLIS NOT NULL 是单目比较运算符。

  • 使用 IS NULL 运算符检查一个值是否是 NULL

  • IS NOT NULL 运算符是 IS NULL 的否定操作。

  • NULL IS NULL 的结果是真。

EXISTS 运算符用法与实例

EXISTS 运算符检查子查询中是否存在满足条件的行。

EXISTS 运算符用来判断一个子查询是否返回行。如果一个子查询返回了至少一个行,则 EXISTS 返回真,否则返回假。

EXISTS 语法

EXISTS 运算符用在 WHERE 子句中构造判断条件,它的用法如下:

WHERE EXISTS(subquery);
  • EXISTS 一般用在 WHERE 子句中。

  • EXISTS 是一个单目操作符,它需要一个子查询 subquery 作为参数。

  • 如果子查询 subquery 返回了至少一行(不论行中的值是否为 NULL),则 EXISTS 的计算结果为 TRUE,否则计算结果为 FALSE

  • EXISTS 运算时,一旦子查询找到一个匹配的行,EXISTS 运算就会返回。这对提高查询新能很有帮助。

  • EXISTS 不关心子查询中的列的数量或者名称,它只在乎子查询是否返回行。所以在 EXISTS 的子查询中,无论你是使用 SELECT 1 还是 SELECT *,亦或是 SELECT column_list,都不影响 EXISTS 运算的结果。

  • NOT EXISTS 则是 EXISTS 的否定操作。

EXISTS 示例

数据库中, film表存储了所有的影片, inventory 表中存储影片的库存信息。 film 表和 inventory 表是一对多的关系,也就是说,一个影片可能有多个库存信息。

要从 film 表中查找拥有库存记录的影片数量,请使用以下语句:

SELECT film_id,title FROM film f WHERE EXISTS (SELECT 1 FROM inventory i WHERE i.film_id = f.film_id);
film_id |            title
---------+-----------------------------
       1 | ACADEMY DINOSAUR
       2 | ACE GOLDFINGER
       3 | ADAPTATION HOLES
       4 | AFFAIR PREJUDICE
       5 | AFRICAN EGG
       6 | AGENT TRUMAN
       7 | AIRPLANE SIERRA
...
     999 | ZOOLANDER FICTION
    1000 | ZORRO ARK
(958 rows)

这里,对于影片表中的每一个影片(也就是每一行),子查询检查 inventory 以查找该影片是否有库存记录 (i.film_id = f.film_id)。

要从 film 表中查找没有记录的电影,请使用以下语句:

SELECT film_id,title FROM film f WHERE NOT EXISTS ( SELECT 1 FROM inventory i WHERE i.film_id = f.film_id);
 film_id |         title
---------+------------------------
      14 | ALICE FANTASIA
      33 | APOLLO TEEN
      36 | ARGONAUTS TOWN
      38 | ARK RIDGEMONT
      41 | ARSENIC INDEPENDENCE
      87 | BOONDOCK BALLROOM
...
     954 | WAKE JAWS
     955 | WALLS ARTIST
(42 rows)

ALL 运算符的用法与实例

有时候,您需要将一个值与一个列表中的所有值进行比较,比如:

  • 给定一个工资值。您需要确认这个值是否高于所有员工的工资。

  • 给定一个状态值。您需要确定这个状态值是否不等于所有状态值中的任意一个。

您可以使用 unvdbALL 运算符来解决这些问题。

unvdbALL 运算符用于将一个值与一个值列表进行比较,如果值列表中的所有值满足了指定的条件,表达式就返回真,否则表达式返回假。

ALL 语法

将一个值与一个子查询返回的所有的值进行比较,请按下面的语法使用 ALL 运算符:

comparison_operator ALL (array)
  • comparison_operator 是一个比较运算符,例如 =, !=, >, >=, <, <= 等。

  • array 是一个数组或者子查询,它跟在 ALL 运算符后面。子查询必须用括号括起来。

  • 如果 array 中的所有值都满足指定的条件,则表达式返回真,否则表达式返回假。

例如:

  • value = ALL (array): 如果列表中的所有的值等于 value,该表达式返回 true, 否则返回 false

  • value > ALL (array): 如果列表中的所有的值小于 value,该表达式返回 true, 否则返回 false

  • value < ALL (array): 如果列表中的所有的值大于 value,该表达式返回 true, 否则返回 false

  • value <> ALL (array): 如果列表中的所有的值不等于 value,该表达式返回 true, 否则返回 false

您可以在 WHERE 子句中按照如下方式使用 ALL 运算符:

WHERE expr1 > ALL (subquery)

ALL 运算符实例

ALL 与数组

我们经常需要检查一个数组中的所有元素是否都满足指定的条件,比如:

  • 要检查数组 [1, 2, 3] 中的所有元素是否都等于 2,请使用以下语句:

SELECT 2 = ALL(ARRAY[1, 2, 3]);
 ?column?
----------
 f

要检查数组 [1, 2, 3] 中的所有元素是否都大于等于 1,请使用以下语句:

SELECT 1 <= ALL(ARRAY[1, 2, 3]);
?column?
----------
t

要检查数组 [1, 2, 3] 中的所有元素是否都不等于 0,请使用以下语句:

SELECT 0 <> ALL(ARRAY[1, 2, 3]);
?column?
----------
t

ALL 与子查询

下面我们提供了一些实用的例子来展示 ALL 操作符的威力.

要判断一个值 99 是否比所有的影片的租金都要高,请使用下面的语句:

SELECT 99 > ALL (SELECT rental_rate FROM film);
 ?column?
----------
 t

要判断一个值 66 是否比所有的影片的租金都要小,请使用下面的语句:

SELECT 66 < ALL (SELECT rental_rate FROM film);
 ?column?
----------
 f

要查找大于等于所有的租金的影片的数量,请使用下面的语句:

SELECT count(*) FROM film WHERE rental_rate >= ALL (SELECT rental_rate FROM film);
 count
-------
   336

ANY 运算符的用法与实例

ANY 运算符检查一个子查询返回的一组值中是否至少存在一个值满足指定的条件。

有时候,你需要检查一个列表中是否至少有一个满足指定条件的值,比如:

  • 检查一个列表中是否包含了一个指定的值的元素。

  • 检查一个列表中是否有一个大于或者小于一个指定值的元素。

  • 检查一个班级的考试成绩是否有满分。

您可以使用 unvdb ANY 运算符来解决这些问题

ANY 运算符用于将一个值与一个值列表进行比较,只要值列表中有一个值满足指定的条件就返回真,否则返回假。

在 unvdb 中, SOME 操作符是 ANY 的同义词。 您可以使用 SOME 关键字代替 ANY

ANY 语法

要检查一个数组中是否至少存在一个满足条件的值,请按下面的语法使用 ANY 运算符:

comparison_operator ANY (array)

这里:

  • comparison_operator 是一个比较运算符,例如 =, !=, >, >=, <, <= 等。

  • array 是一个数组或者子查询,它跟在 ANY 运算符后面。子查询必须用括号括起来。

  • 如果 array 中至少有一个值满足指定的条件,则表达式返回真,否则表达式返回假。

例如:

  • value = ANY (array): 只要数组中有一个等于 value 的值,该表达式返回 true, 否则返回 false

  • value > ANY (array): 只要数组中有一个小于 value 的值,该表达式返回 true, 否则返回 false

  • value < ANY (array): 只要数组中有一个大于 value 的值,该表达式返回 true, 否则返回 false

  • value <> ANY (array): 只要数组中有一个不等于 value 的值,该表达式返回 true, 否则返回 false

ANY 实例

ANY 与数组

我们经常需要检查一个数组中是否至少包含一个满足条件的元素,比如:

  • 要检查数组 [1, 2, 3] 是否包含一个值为 2 的元素,请使用以下语句:

    SELECT 2 = ANY(ARRAY[1, 2, 3]);
    
    ?column?
    ----------
    t
    
  • 要检查数组 [1, 2, 3] 是否包含一个值大于 2 的元素,请使用以下语句:

    SELECT 2 < ANY(ARRAY[1, 2, 3]);
    
    ?column?
    ----------
    t
    
  • 要检查数组 [1, 2, 3] 是否包含一个值大于 3 的元素,请使用以下语句:

    SELECT 3 < ANY(ARRAY[1, 2, 3]);
    
    ?column?
    ----------
    f
    

    这里,因为 [1, 2, 3] 所有元素都不大于 3,因此表达式返回了 false

  • 要检查数组 [1, 2, 3] 是否包含一个值不等于 3 的元素,请使用以下语句:

    SELECT 3 <> ANY(ARRAY[1, 2, 3]);
    
    ?column?
    ----------
    t
    

ANY 与子查询

ANY 运算符结合子查询会产生更大的威力。

  • 要检查是否存在租金高于 5 美元的影片,请使用下面的 ANY 语句:

    SELECT 5 < ANY (SELECT rental_rate FROM film);
    
    ?column?
    ----------
    f
    

    这里,请注意子查询:

    SELECT rental_rate FROM film
    

    它返回了所有影片你的租金。只要 5 小于所有租金的中的一个,则代表影片表中存在租金大于 5 美元的影片。

    最终,表达式返回了 false,这说明了 film 表中没有租金大于 5 美元的影片。

  • 要检查是否能用 1 美元租到影片,请使用以下语句:

    SELECT 1 >= ANY (SELECT rental_rate FROM film);
    
    ?column?
    ----------
    t
    

    要判断能否用 1 美元租到影片,就是判断是否存在租金小于等于 1 美元。

    表达式返回了 false,这说明了 film 表中有租金小于等于 1 美元的影片。也就是,您可以是使用 1 美元租到影片。

GROUP BY 子句将行根据指定的字段或者表达式进行分组。

有时候,我们需要将结果集按照某个维度进行汇总。这在统计数据的时候经常用到,考虑以下的场景:

  • 按班级求取平均成绩。

  • 按学生汇总总分。

  • 按年或者月份统计销售额。

  • 按国家或者地区统计用户数量。

这些正是 GROUP BY 子句发挥作用的地方

GROUP BY 语法

GROUP BY 子句是 SELECT 语句的可选子句。要对 SELECT 语句中的行进行分组,请按照如下语法使用 GROUP BY 子句:

SELECT column1[, column2, ...], aggregate_function(ci)
FROM table
[WHERE clause]
GROUP BY column1[, column2, ...];
[HAVING clause]
  • column1[, column2, ...] 是分组依据的字段,至少一个字段,可以多个字段。

  • aggregate_function(ci) 是聚合函数,用来汇总。这是可选的。 您可以会用到以下聚合函数:

    • sum(): 计算组内数据的总和

    • avg(): 计算组内数据的平均值

    • max(): 计算组内数据的最大值

    • MIN(): 计算组内数据的最小值

    • count(): 计算组内的行数

  • SELECT 后的字段必须是分组字段中的字段。

  • WHERE子句是可选的,用来过在分组之前过滤行。

  • HAVING子句是可选的,用来过滤分组数据。

GROUP BY 实例

简单的 GROUP BY 实例

我们使用 GROUP BY 子句查看 actor 表中的姓氏列表。

SELECT last_name
FROM actor
GROUP BY last_name;
  last_name
--------------
 AKROYD
 BRIDGES
 HUNT
 GIBSON
 ALLEN
 SUVARI
 HESTON
 MONROE
 ...
 WILSON
(121 rows)

本例中,使用 GROUP BY 子句按照 last_name 字段对数据进行分组。

本例的输出结果与以下使用 DISTINCT 的 SQL 输出结果完全一样:

SELECT DISTINCT last_name FROM actor;
GROUP BY 与聚合函数实例

我们使用 GROUP BY 子句和聚合函数 count() 查看 actor 表中的姓氏列表以及每个姓氏的数量。

SELECT last_name, count(*)
FROM actor
GROUP BY last_name
ORDER BY count(*) DESC;

last_name   | count
--------------+-------
 KILMER       |     5
 TEMPLE       |     4
 NOLTE        |     4
 WILLIAMS     |     3
 PECK         |     3
 HOPKINS      |     3
 DAVIS        |     3
 HARRIS       |     3
 DEGENERES    |     3
 ...
 CLOSE        |     1
(121 rows)

本例中,执行的顺序如下:

  1. 首先使用 GROUP BY 子句按照 last_name 字段对 actor 表中的所有的行进行分组。也就是每个姓氏一组。

  2. 然后使用聚合函数 count(*) 汇总每个姓氏的行数。

  3. 最后使用 ORDER BY 子句按照 count(*) 降序排列。这样,数量最多的姓氏排在最前面。

同样,如果我们想从 film 表中查找每个影片等级的影片数量,请使用如下语句:

SELECT rating, count(*)
FROM film
GROUP BY rating
ORDER BY count(*) DESC;
 
 rating | count
--------+-------
 PG-13  |   223
 NC-17  |   210
 R      |   195
 PG     |   194
 G      |   178
(5 rows)
GROUP BY, LIMIT, 聚合函数实例

以下实例使用 GROUP BY 子句,LIMIT子句和聚合函数 sum()payment 表中查找消费金额排名前 10 位的客户。

SELECT customer_id, sum(amount) total
FROM payment
GROUP BY customer_id
ORDER BY total DESC
LIMIT 10;
 customer_id | total
-------------+--------
         526 | 221.55
         148 | 216.54
         144 | 195.58
         178 | 194.61
         137 | 194.61
         459 | 186.62
         469 | 177.60
         468 | 175.61
         236 | 175.58
         181 | 174.66
(10 rows)

本例中,执行的顺序如下:

  1. 首先使用 GROUP BY 子句按照 customer_id 字段对数据进行分组,也就是按照客户分组。

  2. 然后使用聚合函数 sum(amount) 对每组中的所有行的 amount 字段求和,并使用 total 作为列别名。

  3. 然后使用 ORDER BY 子句按照 total 降序排列。

  4. 最后使用 LIMIT 10 子句返回前 10 个记录行。

GROUP BY 和 HAVING 实例

以下实例使用 GROUP BY 子句,HAVING 子句和聚合函数 sum()payment 表中查找总消费金额在 180 美元以上的客户。

SELECT customer_id, sum(amount) total
FROM payment
GROUP BY customer_id
HAVING sum(amount) > 180
ORDER BY total DESC;

 customer_id | total
-------------+--------
         526 | 221.55
         148 | 216.54
         144 | 195.58
         178 | 194.61
         137 | 194.61
         459 | 186.62
(6 rows)

本例中,执行的顺序如下:

  1. 首先使用 GROUP BY 子句按照 customer_id 字段对数据进行分组,也就是按照客户分组。

  2. 然后使用聚合函数 sum(amount) 对每组中的所有行的 amount 字段求和,并使用 total 作为列别名。

  3. 然后使用 HAVING 子句指定只有 sum(amount) 大于 180 的行才会被返回。

  4. 最后使用 ORDER BY 子句按照 total 降序排列。

HAVING 子句用来过滤 GROUP BY 分组的数据,需要一个逻辑表达式作为条件,其中逻辑表达式中的列名或表达式只能使用分组使用的列,表达式,或者应用于分组列或表达式的聚合函数。

以下是 GROUP BY 子句的要点:

  • GROUP BY 子句用于将结果集根据指定的字段或者表达式进行分组。

  • GROUP BY 子句至少需要一个分组字段或表达式。

  • HAVING 子句是可选的,它用来过滤分组数据。

  • GROUP BY 子句经常用于数据统计汇总,通常使用聚合函数

HAVING 用法与实例

HAVING 子句为分组查询指定过滤条件。

在 unvdb 中, HAVING 子句用于为带有 GROUP BY 子句的分组查询指定过滤条件。

HAVING 看起来与 WHERE 相似,虽然他们都是指定过滤条件,但是他们的区别是: WHERE 子句指定的条件用于过滤表中的行,而 HAVING 子句指定的条件用于过滤分组。

HAVING 语法

unvdb HAVING 子句必须与 GROUP BY 子句一起使用。 以下是unvdbHAVING 子句典型的用法:

SELECT column1[, column2, ...], aggregate_function(ci) FROM table [WHERE clause] GROUP BY column1[, column2, ...]; HAVING clause

这里,

  • GROUP BY 子句用于指定用于分组的列或者表达式。

  • HAVING 子句用来过滤 GROUP BY 分组的数据,需要使用逻辑表达式作为条件,其中逻辑表达式中的列名或表达式只能使用分组使用的列,表达式,或者应用于分组列或表达式的聚合函数。

  • 您不能在 HAVING 子句中使用列别名。

HAVING 子句实例

使用带有 count 函数的 HAVING 子句示例

如果您想从 film 表中查找每个影片评级的影片数量,请使用如下语句:

SELECT rating, count(*) FROM film GROUP BY rating ORDER BY count(*) DESC;

 rating | count
--------+-------
 PG-13  |   223
 NC-17  |   210
 R      |   195
 PG     |   194
 G      |   178
(5 rows)

这个实例返回了所有的影片评级的影片数量。如果您想要查找影片数量大于 200 的影片评级,就要用到 HAVING 子句,如下:

SELECT rating, count(*) FROM film GROUP BY rating HAVING count(*) > 200 ORDER BY count(*) DESC;
 rating | count
--------+-------
 PG-13  |   223
 NC-17  |   210
(2 rows)
使用带有 SUM 函数示例的 HAVING 子句

如果您想从 payment表中查找总消费金额在 180 美元以上的客户,请使用以下带有 GROUP BY 子句,HAVING 子句和聚合函数 sum() 的语句:

SELECT customer_id, sum(amount) total FROM payment GROUP BY customer_id HAVING sum(amount) > 180 ORDER BY total DESC;

 customer_id | total
-------------+--------
         526 | 221.55
         148 | 216.54
         144 | 195.58
         178 | 194.61
         137 | 194.61
         459 | 186.62
(6 rows)

本例中,执行的顺序如下:

  1. 首先使用 GROUP BY 子句按照 customer_id 字段对数据进行分组,也就是按照客户分组。

  2. 然后使用聚合函数 sum(amount) 对每组中的所有行的 amount 字段求和,并使用 total 作为列别名。

  3. 然后使用 HAVING 子句指定只有 sum(amount) 大于 180 的行才会被返回。

  4. 最后使用 ORDER BY 子句按照 total 降序排列。

HAVING 子句用于为带有 GROUP BY 子句的分组查询指定过滤条件。 HAVING 子句需要使用逻辑表达式作为条件,其中逻辑表达式中的列名或表达式只能使用分组使用的列,表达式,或者应用于分组列或表达式的聚合函数。

GROUPING SETS 用法与实例

GROUPING SETS 以在一次查询中生成多个维度的报表。

在 unvdb中,GROUPING SETSGROUP BY 子句的参数,允许您在一次查询中生成多个维度的报表。

比如,要想在一个销售报表中即有每个年份销售额的行,又有每个部门销售额的行,您可以在 GROUP BY 子句中使用 GROUPING SETS 实现它。

GROUPING SETS 语法

下面的语法说明了如何在 GROUP BY 子句中使用 GROUPING SETS

SELECT ... FROM table_name [WHERE ...] GROUP BY GROUPING SETS ((group_expr_list_1)[, (group_expr_list_2), ...]);
  • GROUPING SETSGROUP BY 子句的参数,它必须在 GROUP BY 关键字的后面。

  • (group_expr_list_N) 是一个用于分组的表达式列表,其中可以包含零个或者多个列或表达式,就像在 GROUP BY 子句中直接使用的那些列一样。

  • 您可以为 GROUPING SETS 指定一个或者多个分组表达式,每个分组表达式产生的结果集都将合并到最终的结果集中。

  • (group_expr_list_N) 中没有任何列或者表达式时,即 (),所有符合条件的行都被聚合到一个分组中。

GROUP BY 实例

要从 film 表中查找每个影片评级的影片数量,请使用如下语句:

SELECT rating, count(*) FROM film GROUP BY rating ORDER BY rating;

 rating | count
--------+-------
 G      |   178
 PG     |   194
 PG-13  |   223
 R      |   195
 NC-17  |   210
(5 rows)

要从 film 表中查找每个租金的影片数量,请使用如下语句:

SELECT rental_rate, count(*) FROM film GROUP BY rental_rate ORDER BY rental_rate;

 rental_rate | count
-------------+-------
        0.99 |   341
        2.99 |   323
        4.99 |   336
(3 rows)

要想在一个报表中包含上面的两个报表,您可以使用 UNION ALL 将上面的两个结果集合并起来,如下:

SELECT rating, NULL rental_rate, count(*) FROM film GROUP BY rating

UNION ALL

SELECT NULL rating, rental_rate, count(*) FROM film GROUP BY rental_rate ORDER BY rating, rental_rate;
 
 rating | rental_rate | count
--------+-------------+-------
 G      |             |   178   PG     |             |   194   PG-13  |             |   223   > ranting
 R      |             |   195   NC-17  |             |   210          |        0.99 |   341          |        2.99 |   323   > rental_rate
        |        4.99 |   336  (8 rows)

但是,上面使用 UNION ALL 的语句看起来很复杂。如果能用一个简单的语句做到这一切,就太棒了。unvdb GROUPING SETS 可以帮我们做到这一切:

SELECT rating, rental_rate, count(*) FROM film GROUP BY GROUPING SETS ((rating), (rental_rate)) ORDER BY rating, rental_rate;
 rating | rental_rate | count
--------+-------------+-------
 G      |             |   178   PG     |             |   194   PG-13  |             |   223   > ranting
 R      |             |   195   NC-17  |             |   210          |        0.99 |   341          |        2.99 |   323   > rental_rate
        |        4.99 |   336  (8 rows)

所以,您可以将 GROUPING SETS 简单的理解为对多个分组结果集执行了 UNION ALL 操作。

如果您想在上面的报表中添加一行以显示总影片数量,您可以在 GROUPING SETS 中是一个空的分组表达式 (),如下:

SELECT rating, rental_rate, count(*) FROM film GROUP BY GROUPING SETS ((rating), (rental_rate), ()) ORDER BY rating, rental_rate;
 rating | rental_rate | count
--------+-------------+-------
 G      |             |   178
 PG     |             |   194
 PG-13  |             |   223
 R      |             |   195
 NC-17  |             |   210
        |        0.99 |   341
        |        2.99 |   323
        |        4.99 |   336
        |             |  1000

在unvdb中的 GROUP BY 子句中,您可以使用 GROUPING SETS 以在一次查询中生成多个分组查询的结果集的集合。

为了应对更加复杂的需求,unvdb 还提供了 ROLLUPCUBE 以简化 GROUPING SETS

ROLLUP 用法与实例

在unvdb中, ROLLUPGROUP BY 子句的参数,允许您在一次查询中生成多个维度的报表。 在某些特定的场景下, ROLLUP 可以简化 GROUPING SETS

ROLLUP 语法

下面的语法说明了如何在 GROUP BY 子句中使用 ROLLUP

SELECT ... FROM table_name [WHERE ...] GROUP BY ROLLUP (group_expr_1 [, group_expr_2, ...]);

这里, ROLLUPGROUP BY 子句的参数,它必须在 GROUP BY 关键字的后面。

ROLLUP 子句都可以使用 GROUPING SETS 子句实现,比如:

  • ROLLUP(a, b) 等效于 GROUPING SETS((a,b), (a), ())

  • ROLLUP(a, b, c) 等效于 GROUPING SETS((a,b,c), (a,b), (a), ())

ROLLUP 子句比 GROUPING SETS 子句更加的简单和易读。但是,它的的适应性不如 GROUPING SETS 广泛。

GROUP BY 实例

要从 film 表中查找每个影片评级中每个租金的影片的数量,以及每个影片评级中的影片的数量,以及所有影片的总数量,您可以使用如下带有 GROUPING SETS 的语句:

SELECT rating,rental_rate,count(*) FROM film GROUP BY GROUPING SETS ((rating, rental_rate),(rating),()) ORDER BY rating, rental_rate;
 
 rating | rental_rate | count
--------+-------------+-------
 G      |        0.99 |    64   G      |        2.99 |    59  │───┐
 G      |        4.99 |    55     │───┐
 G      |             |   178  <───┘    PG     |        0.99 |    62          PG     |        2.99 |    64  │───┐    PG     |        4.99 |    68     │───│
 PG     |             |   194  <───┘    PG-13  |        0.99 |    72          PG-13  |        2.99 |    74  │───┐    PG-13  |        4.99 |    77     │───│
 PG-13  |             |   223  <───┘    R      |        0.99 |    70          R      |        2.99 |    60  │───┐    R      |        4.99 |    65     │───│
 R      |             |   195  <───┘    NC-17  |        0.99 |    73          NC-17  |        2.99 |    66  │───┐    NC-17  |        4.99 |    71     │───│
 NC-17  |             |   210  <───┘           |             |  1000  <───────┘
(21 rows)

这里,我们在 GROUPING SETS 中使用了 3 个表达式,如下:

  • (rating, rental_rate): 按照影片评级和租金统计影片的数量。

  • (rating): 按照影片评级统计数量。

  • (): 统计所有影片的数量。

在这种情况下, 这个 GROUPING SETS 可以使用 ROLLUP 来简化,如下

SELECT rating,rental_rate,count(*) FROM film GROUP BY ROLLUP (rating, rental_rate) ORDER BY rating, rental_rate;
 rating | rental_rate | count
--------+-------------+-------
 G      |        0.99 |    64
 G      |        2.99 |    59
 G      |        4.99 |    55
 G      |             |   178
 PG     |        0.99 |    62
 PG     |        2.99 |    64
 PG     |        4.99 |    68
 PG     |             |   194
 PG-13  |        0.99 |    72
 PG-13  |        2.99 |    74
 PG-13  |        4.99 |    77
 PG-13  |             |   223
 R      |        0.99 |    70
 R      |        2.99 |    60
 R      |        4.99 |    65
 R      |             |   195
 NC-17  |        0.99 |    73
 NC-17  |        2.99 |    66
 NC-17  |        4.99 |    71
 NC-17  |             |   210
        |             |  1000
(21 rows)

在上面的语句中,我们使用 ROLLUP (rating, rental_rate) 代替了 GROUPING SETS ((rating, rental_rate), (rating), ())。这让语句更加简单,可读性更好。

GROUP BY 子句中,您可以在特定的场景下使用 ROLLUP 简化 GROUPING SETS

CUBE 用法与实例

在 unvdb 中,CUBEGROUP BY 子句的参数,允许您在一次查询中生成多个维度的报表。和 ROLLUP 一样, 在某些特定的场景下, CUBE 可以简化 GROUPING SETS

CUBE 语法

下面的语法说明了如何在 GROUP BY 子句中使用 CUBE

SELECT ... FROM table_name [WHERE ...] GROUP BY CUBE (group_expr_1 [, group_expr_2, ...]);

这里, CUBEGROUP BY 子句的参数,它必须在 GROUP BY 关键字的后面。

CUBE 子句都可以使用 GROUPING SETS 子句实现,比如:

  • CUBE(a, b) 等效于 GROUPING SETS((a,b), (a), (b), ())

  • CUBE(a, b, c) 等效于 GROUPING SETS((a,b,c), (a,b), (a,c), (a), (b,c), (b), (c), ())

CUBE 子句比 GROUPING SETS 子句更加的简单和易读。但是,它的的适应性不如 GROUPING SETS 广泛。

GROUP BY 实例

要从 film 表中查找每个影片评级中每个租金的影片的数量,每个影片评级中的影片的数量,每个租金的影片的数量,以及所有影片的总数量,您可以使用如下带有 GROUPING SETS 的语句:

SELECT rating,rental_rate,count(*) FROM film GROUP BY GROUPING SETS ((rating, rental_rate),(rating),(rental_rate),()) ORDER BY rating, rental_rate;
 
 rating | rental_rate | count 
--------+-------------+-------
 G      |        0.99 |    64
 G      |        2.99 |    59
 G      |        4.99 |    55
 G      |             |   178
 PG     |        0.99 |    62
 PG     |        2.99 |    64
 PG     |        4.99 |    68
 PG     |             |   194
 PG-13  |        0.99 |    72
 PG-13  |        2.99 |    74
 PG-13  |        4.99 |    77
 PG-13  |             |   223
 R      |        0.99 |    70
 R      |        2.99 |    60
 R      |        4.99 |    65
 R      |             |   195
 NC-17  |        0.99 |    73
 NC-17  |        2.99 |    66
 NC-17  |        4.99 |    71
 NC-17  |             |   210
        |        0.99 |   341
        |        2.99 |   323
        |        4.99 |   336
        |             |  1000
(24 rows)

这里,我们 GROUPING SETS 使用了 3 个表达式,如下:

  • (rating, rental_rate): 按照影片评级和租金统计影片的数量。

  • (rating): 按照影片评级统计数量。

  • (rental_rate): 按照租金统计数量。

  • (): 统计所有影片的数量。

在这种情况下, 这个 GROUPING SETS 可以使用 CUBE 来简化,如下:

SELECT rating,rental_rate,count(*) FROM film GROUP BY CUBE (rating, rental_rate) ORDER BY rating, rental_rate;
 
 rating | rental_rate | count 
--------+-------------+-------
 G      |        0.99 |    64
 G      |        2.99 |    59
 G      |        4.99 |    55
 G      |             |   178
 PG     |        0.99 |    62
 PG     |        2.99 |    64
 PG     |        4.99 |    68
 PG     |             |   194
 PG-13  |        0.99 |    72
 PG-13  |        2.99 |    74
 PG-13  |        4.99 |    77
 PG-13  |             |   223
 R      |        0.99 |    70
 R      |        2.99 |    60
 R      |        4.99 |    65
 R      |             |   195
 NC-17  |        0.99 |    73
 NC-17  |        2.99 |    66
 NC-17  |        4.99 |    71
 NC-17  |             |   210
        |        0.99 |   341
        |        2.99 |   323
        |        4.99 |   336
        |             |  1000
(24 rows)

JOIN 连接类型和实例

本文介绍了 unvdb 中的连接语句,包括交叉连接、内连接、自然连接、左连接、右连接、全连接。

在 unvdb 中,JOIN 语句用于将数据库中的两个表或者多个表连接起来。

比如在一个学校系统中,有一个学生信息表和一个学生成绩表。这两个表通过学生 ID 字段关联起来。当我们要查询学生的成绩的时候,就需要连接两个表以查询学生信息和成绩。

unvdb 连接类型

unvdb 支持以下类型的连接:

  • 交叉连接 (CROSS JOIN)

  • 内联接 (INNER JOIN)

  • 自然连接 (NATURAL JOIN)

  • 左连接/左外连接 (LEFT [OUTER] JOIN)

  • 右连接/右外连接 (RIGHT [OUTER] JOIN)

  • 全连接/全外连接 (RIGHT [OUTER] JOIN)

创建实例表和数据

本教程中关于表连接的实例都使用 studentstudent_score 两个表来完成。

首先,使用下面的 SQL 语句创建表 studentstudent_score

CREATE TABLE student (student_id INTEGER NOT NULL,name varchar(45) NOT NULL,PRIMARY KEY (student_id));

CREATE TABLE student_score (student_id INTEGER NOT NULL,subject varchar(45) NOT NULL,score INTEGER NOT NULL);

然后,分别在两个表中插入数据:

INSERT INTO student (student_id, name) VALUES (1,'Tim'),(2,'Jim'),(3,'Lucy');

INSERT INTO student_score (student_id, subject, score) VALUES (1,'English',90),(1,'Math',80),(2,'English',85),(5,'English',92);

第三,以下语句使用 SELECT 检查表中的数据:

SELECT * FROM student;
 
 student_id | name
------------+------
          1 | Tim
          2 | Jim
          3 | Lucy
(3 rows)

SELECT * FROM student_score;
 
 student_id | subject | score
------------+---------+-------
          1 | English |    90
          1 | Math    |    80
          2 | English |    85
          5 | English |    92
(4 rows)

注意,为了演示,我们特意使用了特殊的数据行:

  • student 表中 student_id 为 3 的学生没有成绩。

  • student_score 表中的最后一行的 student_id5,而 student 表中不存在 student_id5 的学生。

交叉连接

交叉连接返回两个集合的笛卡尔积。也就是两个表中的所有的行的所有可能的组合。这相当于内连接没有连接条件或者连接条件永远为真。

如果一个有 m 行的表和另一个有 n 行的表,它们交叉连接将返回 m * n 行。

在大多数场景下,交叉连接的结果没有意义,你需要使用 WHERE子句过滤自己所需的数据行。

显式的交叉连接 studentstudent_score 表:

SELECT student.*,student_score.* FROM student CROSS JOIN student_score;
这两种方式的输出一样。


 student_id | name | student_id | subject | score
------------+------+------------+---------+-------
          1 | Tim  |          1 | English |    90
          1 | Tim  |          1 | Math    |    80
          1 | Tim  |          2 | English |    85
          1 | Tim  |          5 | English |    92
          2 | Jim  |          1 | English |    90
          2 | Jim  |          1 | Math    |    80
          2 | Jim  |          2 | English |    85
          2 | Jim  |          5 | English |    92
          3 | Lucy |          1 | English |    90
          3 | Lucy |          1 | Math    |    80
          3 | Lucy |          2 | English |    85
          3 | Lucy |          5 | English |    92
(12 rows)

内连接

内连接基于连接条件组合两个表中的行。内连接相当于加了过滤条件的交叉连接。

内连接将第一个表的每一行与第二个表的每一行进行比较,如果满足给定的连接条件,则将两个表的行组合在一起作为结果集中的一行。

以下 SQL 语句将 student 表和 student_score 表内连接,以查找有效的学生成绩信息:

SELECT student.*,student_score.* FROM student INNER JOIN student_score ON student.student_id = student_score.student_id;

等于:

SELECT student.*,student_score.* FROM student, student_score WHERE student.student_id = student_score.student_id;
 student_id | name | student_id | subject | score
------------+------+------------+---------+-------
          1 | Tim  |          1 | English |    90
          1 | Tim  |          1 | Math    |    80
          2 | Jim  |          2 | English |    85
(3 rows)

注意输出结果中,student 表中 student_id3 的行和 student_score 表中 student_id5 的行没有出现在输出结果中,这是因为他们没有满足连接条件:student.student_id = student_score.student_id

自然连接

自然连接同样是基于条件的连接,它是一种特殊的内连接。两个表做自然连接时,两个表中所有同名的列都将做等值比较。这些连接条件都是隐式创建的。

以下 SQL 语句对 student 表和 student_score 做自然连接,等效于上面的内连接语句:

SELECT * FROM student NATURAL JOIN student_score;
 student_id | name | subject | score
------------+------+---------+-------
          1 | Tim  | English |    90
          1 | Tim  | Math    |    80
          2 | Jim  | English |    85
(3 rows)

注意,自然连接不需要使用 ON 创建连接条件,它的连接条件是隐式创建的。 自然连接的结果集中,两个表中同名的列只出现一次。

左连接

左连接是左外连接的简称,左连接需要连接条件。

两个表左连接时,第一个表称为左表,第二表称为右表。例如 A LEFT JOIN BA 是左表,B 是右表。

左连接以左表的数据行为基础,根据连接条件匹配右表的每一行,如果匹配成功则将左表和右表的行组合成新的数据行返回;如果匹配不成功则将左表的行和 NULL 值组合成新的数据行返回。

以下 SQL 语句将 student 表和 student_score 表左连接:

SELECT student.*,student_score.* FROM student LEFT JOIN student_score ON student.student_id = student_score.student_id;
student_id | name | student_id | subject | score
------------+------+------------+---------+--------
          1 | Tim  |          1 | English |     90
          1 | Tim  |          1 | Math    |     80
          2 | Jim  |          2 | English |     85
          3 | Lucy |     <null> | <null>  | <null>
(4 rows)

注意:

  1. 结果集中包含了 student 表的所有记录行。

  2. student_score 表中不包含 student_id = 3 的记录行,因此结果集中最后一行中来自 student_score 的列的内容为 NULL

  3. student_score 表存在多个 student_id1 的行,因此结果集中也产生了多个来自 student 表对应的行。

由于两个表都使用相同的字段进行等值比较,因此您可以使用 USING 以下查询中所示的子句:

SELECT student.*,student_score.* FROM student LEFT JOIN student_score USING(student_id);

右连接

右连接是右外连接的简称,右连接需要连接条件。

右连接与左连接处理逻辑相反,右连接以右表的数据行为基础,根据条件匹配左表中的数据。如果匹配不到左表中的数据,则左表中的列为 NULL 值。

以下 SQL 语句将 student 表和 student_score 表右连接:

SELECT student.*,student_score.* FROM student RIGHT JOIN student_score ON student.student_id = student_score.student_id;
 student_id |  name  | student_id | subject | score
------------+--------+------------+---------+-------
          1 | Tim    |          1 | English |    90
          1 | Tim    |          1 | Math    |    80
          2 | Jim    |          2 | English |    85
     <null> | <null> |          5 | English |    92

从结果集可以看出,由于左表中不存在到与右表 student_id = 5 匹配的记录,因此最后一行左表的列的值为 NULL

右连接其实是左右表交换位置的左连接,即 A RIGHT JOIN B 就是 B LEFT JOIN A,因此右连接很少使用。

上面例子中的右连接可以转换为下面的左连接:

SELECT student.*,student_score.* FROM student_score LEFT JOIN student ON student.student_id = student_score.student_id;

全连接

全连接是全外连接的简称,它是左连接和右连接的并集。全连接需要连接条件。

以下 SQL 语句将 student 表和 student_score 表全连接:

SELECT student.*,student_score.*FROM student FULL JOIN student_score ON student.student_id = student_score.student_id;
 
 student_id |  name  | student_id | subject | score
------------+--------+------------+---------+--------
          1 | Tim    |          1 | English |     90
          1 | Tim    |          1 | Math    |     80
          2 | Jim    |          2 | English |     85
     <null> | <null> |          5 | English |     92
          3 | Lucy   |     <null> | <null>  | <null>
(5 rows)

由于两个表都使用相同的字段进行等值比较,因此您可以使用 USING 以下查询中所示的子句:

SELECT student.*,student_score.* FROM student FULL JOIN student_score USING(student_id);

全连接是左连接和右连接的并集。 上面的全连接可以使用 LEFT JOIN, RIGHT JOIN, 和 UNION 改写:

SELECT student.*,student_score.* FROM student LEFT JOIN student_score USING(student_id)

UNION

SELECT student.*,student_score.* FROM student RIGHT JOIN student_score  USING(student_id);

本章节介绍了unvdb中的连接语句,包括交叉连接、内连接、左连接、右连接、全连接、自然连接

子查询用法与实例

子查询是嵌套是在另一个查询中的查询,也被称为内部查询。

通常情况下,我们会使用子查询构建更复杂的 SQL 语句。比如,您可以将子查询使用在 EXISTS, IN, ANY, ALL 等表达式中,也可以将子查询的结果直接和值比较。

EXISTS 运算符中使用子查询

unvdb EXISTS 运算符需要一个子查询作为操作数,以检查此子查询是否返回行。

比如,要从 Sakila 数据库中的 language 表查找那些在 film表中用到的语言,您可以使用如下语句:

SELECT * FROM language WHERE EXISTS( SELECT * FROM film WHERE film.language_id = language.language_id);
 language_id |         name         |     last_update
-------------+----------------------+---------------------
           1 | English              | 2006-02-15 05:02:19

在上面的语句中,下面的查询就是一个子查询:

SELECT * FROM film WHERE film.language_id = language.language_id

IN 运算符中使用子查询

IN 运算符也需要一个集合作为操作数,因此您也可以使用 子查询 作为 IN 运算符右侧的操作数。

比如,您可以使用如下带有 IN 运算符和子查询的语句达到上面的目的:

SELECT * FROM language WHERE language_id in(SELECT DISTINCT language_id FROM film);
 language_id |         name         |     last_update
-------------+----------------------+---------------------
           1 | English              | 2006-02-15 05:02:19
(1 row)

这里,下面的语句就是一个子查询,以查询出所有的影片用到的 language_id:

SELECT DISTINCT language_id FROM film;

将子查询直接和值进行比较

比如,要计算租金高于平均租金的影片的数量,您可以使用一下语句实现:

SELECT count(*) FROM film WHERE rental_rate > (SELECT avg(rental_rate) FROM film);
 count
-------
   659

这里,我们使用了下面的子查询计算影片的平均租金:

SELECT avg(rental_rate) FROM film;

然后,将租金 rental_rate 和上面子查询的结果进行比较,从而得到租金比平均租金大的影片的数量。

子查询可以帮助您构建更复杂的 SQL 语句。

通用表表达式与递归查询

通用表表达式,简称为 CTE,它提供了一种语句级别的临时表的功能,以帮助你构建复杂但是清晰的 SQL 语句。

CTE 语法

unvdb 通用表表达式使用 WITH 关键字定义,下面是unvdb CTE 的语法:

WITH [RECURSIVE] cte_name [(cte_column_list)] AS (cte_definition) primary_statement;

这里:

  • 通用表表达式以 WITH 关键字开始。

  • RECURSIVE 关键字表示此通用表表达式是可递归查询的。它是可选的。

  • cte_name 是通用表表达式是的名称,相当于临时表的表名。

  • cte_column_list 是通用表表达式的列名的列表,多个列名使用逗号分隔。 它是可选的。

  • cte_definition 是通用表表达式的辅助语句,它可以是 SELECT, INSERT, UPDATE, 或者 DELETE 语句。

  • primary_statement 是主要语句,它会用到上面 WITH 中定义的通用表表达式。 它可以是 SELECT, INSERT, UPDATE, 或者 DELETE

通用表表达式基本实例

假设,您想要确定每个影片的租金是否比它坐在的评级的平均租金高。 您可以使用如下带有 CTE 的语句:

WITH file_rating_avg AS (SELECT rating,avg(rental_rate) avg_rental_rate FROM film GROUP BY rating) SELECT f.film_id,f.title,f.rental_rate,a.avg_rental_rate,f.rental_rate > avg_rental_rate "Greater?" FROM film f,file_rating_avg a WHERE f.rating = a.rating LIMIT 10;
 film_id |      title       | rental_rate |  avg_rental_rate   | Greater?
---------+------------------+-------------+--------------------+----------
       1 | ACADEMY DINOSAUR |        0.99 | 3.0518556701030928 | f
       2 | ACE GOLDFINGER   |        4.99 | 2.8888764044943820 | t
       3 | ADAPTATION HOLES |        2.99 | 2.9709523809523810 | t
       4 | AFFAIR PREJUDICE |        2.99 | 2.8888764044943820 | t
       5 | AFRICAN EGG      |        2.99 | 2.8888764044943820 | t
       6 | AGENT TRUMAN     |        2.99 | 3.0518556701030928 | f
       7 | AIRPLANE SIERRA  |        4.99 | 3.0348430493273543 | t
       8 | AIRPORT POLLOCK  |        4.99 | 2.9387179487179487 | t
       9 | ALABAMA DEVIL    |        2.99 | 3.0348430493273543 | f
      10 | ALADDIN CALENDAR |        4.99 | 2.9709523809523810 | t
(10 rows)

首先,我们使用如下语句定义了一个通用表表达式,名称为 file_rating_avg:

WITH file_rating_avg AS ( SELECT rating, avg(rental_rate) avg_rental_rate FROM film GROUP BY rating)

然后,我们在后面的主语句中使用了 file_rating_avg 作为一个临时表。

SELECT
  f.film_id,
  f.title,
  f.rental_rate,
  a.avg_rental_rate,
  f.rental_rate > avg_rental_rate "Greater?"
FROM
  film f,
  file_rating_avg a
WHERE f.rating = a.rating
LIMIT 10;

通用表表达式递归查询

unvdb 通用表表达式支持递归查询,这很适合应用在一些存储树形数据的方案中,比如产品的分类、系统的导航菜单等。

接下来,我们设计一个用于保存产品分类的表,然后使用 unvdb 通用表表达式获取一个指定分类以及该分类下的所有的下级分类。

我们要在 testdb 数据库中演示这个示例。请先使用下面的语句创建数据库testdb

CREATE DATABASE testdb;

选择 testdb 数据库为当前数据库:

\c testdb;

使用下面语句创建产品分类表 category:

DROP TABLE IF EXISTS category;
CREATE TABLE category (id SERIAL PRIMARY KEY,name VARCHAR NOT NULL,parent_id INT,CONSTRAINT fk_category FOREIGN KEY(parent_id) REFERENCES category(id));

我们创建了一个 category 表,它有 id, name, parent_id 三个列,并且 parent_id 列存储上级分类的 id。 注意,这里我们使用了 外键约束。

使用下面的语句插入一些包含了产品分类信息的行:

INSERT INTO category (id, name, parent_id)
VALUES
  (1, 'ROOT', NULL),
  (2, 'Baby', 1),
  (3, 'Home And Kitchen', 1),
  (4, 'Baby Care', 2),
  (5, 'Feeding', 2),
  (6, 'Gifts', 2),
  (7, 'Safety', 2),
  (8, 'Bedding', 3),
  (9, 'Bath', 3),
  (10, 'Furniture', 3),
  (11, 'Grooming', 4),
  (12, 'Hair Care', 4),
  (13, 'Baby Foods', 5),
  (14, 'Food Mills', 5),
  (15, 'Solid Feeding', 5),
  (16, 'Bed Pillows', 8),
  (17, 'Bed Skirts', 8);

假设,你想要查询 id 为 2 的分类以及它的所有的下级分类,请使用下面的语句:

WITH RECURSIVE cte_categories AS (
  SELECT
    id,
    name,
    parent_id
  FROM category
  WHERE id = 2
  UNION
  SELECT
    c.id,
    c.name,
    c.parent_id
  FROM category c, cte_categories cs
  WHERE cs.id = c.parent_id
)
SELECT *
FROM cte_categories;
 id |     name      | parent_id
----+---------------+-----------
  2 | Baby          |         1
  4 | Baby Care     |         2
  5 | Feeding       |         2
  6 | Gifts         |         2
  7 | Safety        |         2
 11 | Grooming      |         4
 12 | Hair Care     |         4
 13 | Baby Foods    |         5
 14 | Food Mills    |         5
 15 | Solid Feeding |         5
(10 rows)

我们通过 WITH RECURSIVE 定义了一个 CTE cte_categories, 然后,我们在主语句中从 cte_categories 查找数据。

通过使用通用表表达式,您可以简化你的复杂的 SQL 语句,还可以使用递归查询。

UNION 用法与实例

UNION 是一个集合运算符,它返回两个集合的并集,它用于合并两个结果集。

其他的集合操作运算符还有: EXCEPINTERSECT

UNION 语法

要合并两个结果集,请按照以下语法使用使用 UNION 运算符:

SELECT_statement_1
UNION [ALL]
SELECT_statement_2
[ORDER BY ...];

或者您可以同时合并多个结果集,如下:

SELECT_statement_1
UNION [ALL]
SELECT_statement_2
UNION [ALL]
SELECT_statement_3
UNION [ALL]
...
[ORDER BY ...];

这里:

  • SELECT_statement_N 都是个独立 SELECT 语句。

  • 参与 UNION 运算的所有的结果集应该具有相同的列,并且列的数据类型和顺序应该相同。

  • UNIONUNION ALL 具有不同的逻辑:

    • UNION 合并两个结果集,并删除重复的行。

    • UNION ALL 合并两个结果,保留所有的行。

  • ORDER BY 子句用于对最终结果进行排序,它是可选的

UNION 实例

在本示例中,我们将使用 generate_series() 函数用来生成结果集。

我们需要用到两个结果集,先看第一个结果集:

SELECT generate_series(1, 5);
 
 generate_series
-----------------
               1
               2
               3
               4
               5

再看一下第二个结果集:

SELECT generate_series(3, 6);
 
 generate_series
-----------------
               3
               4
               5
               6

现在,我们对两个结果集进行 UNION 运算:

SELECT generate_series(1, 5)
UNION
SELECT generate_series(3, 6) ORDER BY generate_series;
 generate_series
-----------------
               1
               2
               3
               4
               5
               6
(6 rows)

从结果看出,UNION 运算符合并了第一个结果集和第二个结果集后删除了其中的重复的行,并返回了合并后的所有行。

如果你不想要删除重复的行,请使用 UNION ALL 运算符,如下:

SELECT generate_series(1, 5)
UNION ALL
SELECT generate_series(3, 6) ORDER BY generate_series;
 generate_series
-----------------
               1
               2
               3
               3
               4
               4
               5
               5
               6
(9 rows)

UNIONUNION ALL 用来合并两个或者结果集合并为一个结果集。不同的地方在于, UNION 会删除其中的重复的行。

INTERSECT 用法与实例

INTERSECT 是一个集合运算符,它返回两个集合的交集。也就是说,它返回那些同时位于两个结果集中的行。

其他的集合操作运算符还有: UNIONEXCEPT

INTERSECT 语法

要计算两个结果集的交集,请按照以下语法使用使用 INTERSECT 运算符:

SELECT_statement_1
INTERSECT
SELECT_statement_2
[ORDER BY ...];

或者您可以同时计算多个结果集的交集,如下:

SELECT_statement_1
INTERSECT
SELECT_statement_2
INTERSECT
SELECT_statement_3
INTERSECT
...
[ORDER BY ...];
  • SELECT_statement_N 都是个独立 SELECT 语句。

  • 参与 INTERSECT 运算的所有的结果集应该具有相同的列,并且列的数据类型和顺序应该相同。

  • ORDER BY 子句用于对最终结果进行排序,它是可选的。

INTERSECT 实例

在本示例中,我们将使用 generate_series()函数用来生成结果集。

我们需要用到两个结果集,先看第一个结果集:

SELECT generate_series(1, 5);
 generate_series
-----------------
               1
               2
               3
               4
               5

再看一下第二个结果集:

SELECT generate_series(3, 6);
 generate_series
-----------------
               3
               4
               5
               6

现在,我们对两个结果集进行 INTERSECT 运算:

SELECT generate_series(1, 5)
INTERSECT
SELECT generate_series(3, 6) ORDER BY generate_series;
 generate_series
-----------------
               3
               4
               5
(3 rows)

从结果看出,INTERSECT 运算符返回了第一个集合和第二个集合的共有的行。

INTERSECT 用来计算两个结果集的交集。

EXCEPT 用法与实例

EXCEPT 是一个集合运算符,它用于从一个集合中减去出现在另外一个集合中的行。

其他的集合操作运算符还有: UNIONINTERSECT

EXCEPT 语法

要从一个结果集中减去另一个结果集,请按照以下语法使用 EXCEPT 运算符:

SELECT_statement_1
EXCEPT
SELECT_statement_2
[ORDER BY ...];

或者您可以减去多个结果集,如下:

SELECT_statement_1
EXCEPT
SELECT_statement_2
EXCEPT
SELECT_statement_3
EXCEPT
...
[ORDER BY ...];

这里:

  • SELECT_statement_N 都是个独立 SELECT 语句。

  • 参与 EXCEPT 运算的所有的结果集应该具有相同的列,并且列的数据类型和顺序应该相同。

  • ORDER BY 子句用于对最终结果进行排序,它是可选的。

比如,下面的语句:

SELECT generate_series(1, 5)
EXCEPT
SELECT generate_series(3, 6);
 generate_series
-----------------
               1
               2

这里, generate_series() 函数用来生成结果集。

先看一下第一个结果集:

SELECT generate_series(1, 5);
 generate_series
-----------------
               1
               2
               3
               4
               5

再看一下第二个结果集:

SELECT generate_series(3, 6);
 generate_series
-----------------
               3
               4
               5
               6

EXCEPT 运算符从第一个结果集中的所有的行中删除同时位于第二个结果集中的行,并返回了第一个结果集中剩下的行。

EXCEPT 实例

要从影片表 film 中获取评级为 G 的影片,请使用下面的语句:

SELECT film_id, title, rating, length, rental_rate FROM film WHERE rating = 'G';
 
 film_id |           title           | rating | length | rental_rate
---------+---------------------------+--------+--------+-------------
       2 | ACE GOLDFINGER            | G      |     48 |        4.99
       4 | AFFAIR PREJUDICE          | G      |    117 |        2.99
       5 | AFRICAN EGG               | G      |    130 |        2.99
      11 | ALAMO VIDEOTAPE           | G      |    126 |        0.99
      22 | AMISTAD MIDSUMMER         | G      |     85 |        2.99
      25 | ANGELS LIFE               | G      |     74 |        2.99
      26 | ANNIE IDENTITY            | G      |     86 |        0.99
      39 | ARMAGEDDON LOST           | G      |     99 |        0.99
      43 | ATLANTIS CAUSE            | G      |    170 |        2.99
...
     996 | YOUNG LANGUAGE            | G      |    183 |        0.99
(178 rows)

如果您想要从影片表 film 中获取评级为 G 的影片,但是不包括那些影片长度大于 55 分钟的影片,请使用如下带有 EXCEPT 运算符的语句:

SELECT film_id, title, rating, length, rental_rate FROM film WHERE rating = 'G'
EXCEPT
SELECT film_id, title, rating, length, rental_rate FROM film WHERE length > 55;
 film_id |        title        | rating | length | rental_rate
---------+---------------------+--------+--------+-------------
     292 | EXCITEMENT EVE      | G      |     51 |        0.99
       2 | ACE GOLDFINGER      | G      |     48 |        4.99
     247 | DOWNHILL ENOUGH     | G      |     47 |        0.99
     430 | HOOK CHARIOTS       | G      |     49 |        0.99
     542 | LUST LOCK           | G      |     52 |        2.99
     497 | KILL BROTHERHOOD    | G      |     54 |        0.99
     402 | HARPER DYING        | G      |     52 |        0.99
     575 | MIDSUMMER GROUNDHOG | G      |     48 |        4.99
     237 | DIVORCE SHINING     | G      |     47 |        2.99
      83 | BLUES INSTINCT      | G      |     50 |        2.99
     862 | SUMMER SCARFACE     | G      |     53 |        0.99
     697 | PRIMARY GLASS       | G      |     53 |        0.99
     794 | SIDE ARK            | G      |     52 |        0.99
(13 rows)

如果您想要从影片表 film 中获取评级为 G 的影片,但是不包括那些影片长度大于 55 分钟的影片,还不包括那些租金大于 2.99 美元的影片,请使用如下带有 EXCEPT 运算符的语句:

SELECT film_id, title, rating, length, rental_rate FROM film WHERE rating = 'G'
EXCEPT
SELECT film_id, title, rating, length, rental_rate FROM film WHERE length > 55
EXCEPT
SELECT film_id, title, rating, length, rental_rate FROM film WHERE rental_rate >= 2.99;
 film_id |      title       | rating | length | rental_rate
---------+------------------+--------+--------+-------------
     292 | EXCITEMENT EVE   | G      |     51 |        0.99
     247 | DOWNHILL ENOUGH  | G      |     47 |        0.99
     430 | HOOK CHARIOTS    | G      |     49 |        0.99
     497 | KILL BROTHERHOOD | G      |     54 |        0.99
     402 | HARPER DYING     | G      |     52 |        0.99
     862 | SUMMER SCARFACE  | G      |     53 |        0.99
     697 | PRIMARY GLASS    | G      |     53 |        0.99
     794 | SIDE ARK         | G      |     52 |        0.99
(8 rows)

请注意,我们在语句的末尾放置了ORDER BY子句以按标题对电影进行排序。

INSERT 用法与实例

INSERT 语句用于向数据表中插入一个或多个新行。

INSERT 语法

要使用 unvdb INSERT 向表中插入新行,请使用如下的语法:

INSERT INTO table_name(column1, column2, ) VALUES (value11, value12, ) [, (value21, value22, ), ...] [ON CONFLICT conflict_target conflict_action] [RETURNING expr];
  • INSERT INTOVALUES 是关键字

  • table_name 是要插入数据行的表名。

  • (column1, column2, …) 是列列表,其中是通过逗号分隔的各个列。

  • (value11, value12, …) 是值列表,其中是通过逗号分隔的各个列的值。值列表中的值于列列表中的列一一对应。

  • 要一次插入多个数据行,请使用多个使用逗号分隔的值列表。

  • ON CONFLICT 用来在 unvdb 中实现 upsert 操作。

  • RETURNING 子句是可选的。它用于返回插入的行的信息。 expr 可以是列名,或表达式等。

RETURNING 子句

INSERT 语句有一个可选 RETURNING 子句,用于返回插入行的信息。如果具有 RETURNING 子句时,INSERT 语句按照 RETURNING 子句返回,否则它返回成功插入的行数。

RETURNING 子句可采用以下几种形式:

  • 返回指定的列,请使用列表。多个列使用逗号分隔。

    RETURNING column1
    RETURNING column1, column2
    

    您还可以使用 AS 对列名指定别名:

    RETURNING column1 AS column1_new_1
    RETURNING column1 AS column1_new_1, column2 AS column1_new_2
    
  • 返回新行的所有的列,请使用星号 (*)。

    RETURNING *
    
  • 返回一个表达式计算的值

    RETURNING expr
    

不带 RETURNING 子句的返回值

没有指定 RETURNING 子句的 INSERT 语句的返回值具有一下形式:

INSERT oid count

这里:

  • oid 是一个对象标识符。unvdb 在内部将 oid 用作其系统表的主键。通常, INSERT 语句返回 oid 值为 0。

  • countINSERT 语句成功插入的行数。

INSERT 语句示例

我们要在 testdb 数据库中演示下面的示例。请先使用下面的语句创建 testdb 数据库:

CREATE DATABASE testdb;

选择 testdb 数据库为当前数据库:

\c testdb;

为了演示,我们需要创建一个新表,命名为 student

DROP TABLE IF EXISTS student;
CREATE TABLE student (id SERIAL PRIMARY KEY,name VARCHAR(50) NOT NULL,gender CHAR(1) NOT NULL,birthday DATE,notes VARCHAR(255));
向表中插入单行

以下语句向 student 表中插入一个新行:

INSERT INTO student(name, gender)
VALUES ('Tom', 'M');

语句返回以下输出:

INSERT 0 1

这里, INSERT 0 1 中的 1 表示已经成功插入一行。

让我们使用 以下 SELECT 语句显示 student 表的内容:

SELECT * FROM student;
 id | name | gender | birthday | notes
----+------+--------+----------+-------
  1 | Tom  | M      |          |

我们看到:

  • id 列的值为 1。这是因为 id 列为 SERIAL 列, unvdb 会自动生成一个序列值。

  • birthdaynotes 列的值为 NULL。因为他们没有 NOT NULL 约束,unvdb 使用 NULL 插入到这些列中。

向表中插入单行并指定返回值

以下语句向 student 表中插入一个新行并返回插入的行:

INSERT INTO student(name, gender) VALUES ('Lucy', 'F') RETURNING *;

语句返回以下输出:

 id | name | gender | birthday | notes
----+------+--------+----------+-------
  2 | Lucy | F      |          |

这里, 由于 INSERT 语句带有 RETURNING * 子句,因此语句返回了插入的新行中的所有列。如果我们只想返回其中的一列或者几列,请在 RETURNING 子句指定具体的列。如下:

INSERT INTO student(name, gender) VALUES ('Jack', 'M') RETURNING id AS "Student ID", name, gender;

语句返回以下输出:

 Student ID | name | gender
------------+------+--------
          3 | Jack | M

这里,我们在 RETURNING 子句指定了 id, namegender 三列,并且为 id 指定了别名 Student ID

向表中插入多行

我们可以使用一个 INSERT 语句插入多行,如下:

INSERT INTO student(name, gender) VALUES ('Jim', 'M'), ('Kobe', 'M'), ('Linda', 'F') RETURNING *;

语句返回以下输出:

 id | name  | gender | birthday | notes
----+-------+--------+----------+-------
  4 | Jim   | M      |          |
  5 | Kobe  | M      |          |
  6 | Linda | F      |          |

这里,我们使用一个 INSERT 语句向 student 表中插入了 3 行。

INSERT – 插入日期值

要将日期值插入具有 DATE类型的列中,请使用格式为 'YYYY-MM-DD' 的日期字符串。

要向 student 表中插入一行带有生日的数据,请使用下面的语句:

INSERT INTO student (name, gender, birthday) VALUES('Alice', 'F', '2023-07-25') RETURNING *;

输出:

 id | name  | gender |  birthday  | notes
----+-------+--------+------------+-------
  7 | Alice | F      | 2023-07-25 |

INSERT 语句用来向一个表中插入一个或者多个新行。如果带有 RETURNING 子句, INSERT 语句将返回插入的新行的信息,否则它将返回成功插入的行数。

NSERT ON CONFLICT 语句用法与实例

INSERT ON CONFLICT 语句允许您在插入数据时处理一些数据冲突的情况,如果不存在冲突,则正常插入,如果存在冲突,可以更新已有的行。也就是说 INSERT ON CONFLICT 语句实现了 upsert 功能。

INSERT ON CONFLICT 语法

要在 unvdb 实现 upsert 功能,请按照如下语法使用 INSERT ON CONFLICT 语句

INSERT INTO table_name(column_list) VALUES(value_list) ON CONFLICT conflict_target conflict_action [RETURNING {* | column_names}];;

相比较于 INSERT 语句来说,INSERT ON CONFLICT 只是多了 ON CONFLICT 子句。

  • conflict_target 是存在冲突的对象,它可以是以下之一:

    • 一个列名。该列必须是主键或者唯一索引。

    • ON CONSTRAINT constraint_name, 并且 constraint_name 必须是唯一约束的名称。

    • WHERE子句。

  • conflict_action 是存在冲突时要采取的动作,它可以是以下之一:

    • DO NOTHING: 如果存在冲突,不采取任何动作。

    • DO UPDATE: 如果存在冲突,使用 DO UPDATE SET column_1 = value_1, .. WHERE condition 更新表中的字段。

INSERT ON CONFLICT 实例

我们要在 testdb 数据库中演示下面的示例。请先使用下面的语句创建 testdb 数据库:

CREATE DATABASE testdb;

选择 testdb 数据库为当前数据库:

\c testdb;

为了演示,我们需要创建一个新表,命名为 users

CREATE TABLE users (ID SERIAL PRIMARY KEY,NICKNAME VARCHAR(50) NOT NULL,LOGIN_NAME VARCHAR(50) UNIQUE,NOTES VARCHAR(255));

这里,我们创建了一个 users 表,它有 id, nickname, login_name, 和 notes 四列, 其中 login_name 是一个唯一索引列。

让我们再使用 INSERT语句插入一些行到 users 表:

INSERT INTO users (nickname, login_name, notes) VALUES ('Tim', 'tim', 'This is Tim'),('Tom', 'tom', 'This is Tom');

让我们再插入一个新行,其中带有和已有行重复的 login_name:

INSERT INTO users (nickname, login_name, notes) VALUES ('Tim2', 'tim', 'This is Tim2');
ERROR:  duplicate key value violates unique constraint "users_login_name_key"
DETAIL:  Key (login_name)=(tim) already exists.

让我们使用 INSERT ON CONFLICT 语句重试一次,以便在存在重复 login_name 时采取一些动作。我们可以采取两种动作:

  • 使用 DO NOTHING 表示不做任何事情:

    INSERT INTO users (nickname, login_name, notes) VALUES ('Tim2', 'tim', 'This is Tim2') ON CONFLICT (login_name) DO NOTHING;
    
    INSERT 0 0
    

    这里,我们使用 DO NOTHING 选项。然后,unvdb没有返回错误。

  • 使用 DO UPDATE 更新其他的字段:

    INSERT INTO users (nickname, login_name, notes) VALUES ('Tim2', 'tim', 'This is Tim2') ON CONFLICT (login_name) DO UPDATE SET nickname = 'Tim2', notes = 'This is Tim2' RETURNING *;
    
    id | nickname | login_name |    notes
    ----+----------+------------+--------------
      1 | Tim2     | tim        | This is Tim2
    (1 row)
    

    DO UPDATE 子句中,您还可以使用 EXCLUDED 对象引用引发冲突的数据,上面的语句可以使用 EXCLUDED 修改为如下语句:

    INSERT INTO users (nickname, login_name, notes) VALUES ('Tim2', 'tim', 'This is Tim2') ON CONFLICT (login_name) DO UPDATE SET nickname = EXCLUDED.nickname,notes = EXCLUDED.notes RETURNING *;
    

    在冲突对象中,除了字段名称,您还可以使用约束名称。上面的语句可以使用约束名称 users_login_name_key 代替列名 login_name:

    INSERT INTO users (nickname, login_name, notes) VALUES ('Tim3', 'tim', 'This is Tim3') ON CONFLICT ON CONSTRAINT users_login_name_key DO UPDATE SET nickname = EXCLUDED.nickname,notes = EXCLUDED.notes RETURNING *;
    
    id | nickname | login_name |    notes
    ----+----------+------------+--------------
      1 | Tim3     | tim        | This is Tim3
    (1 row)
    

UPDATE 用法与实例

UPDATE 语句允许你更新表中的已有的数据行。

UPDATE 语法

UPDATE 语句可以更新表中的一行或者多行数据,可以更新表中的一个或者多个列的值。 以下是 UPDATE 语句的基本语法:

UPDATE [IGNORE] table_name SET column_name1 = value1,column_name2 = value2,... [WHERE clause] [RETURNING expr];

用法说明:

  • UPDATE 关键字后指定要更新数据的表名。

  • 使用 SET 子句设置列的新值。多个列使用逗号分隔。列的值可以是普通的字面值,也可以是表达式运算,还可以是子查询。

  • 使用 WHERE 子句指定要更新的行的条件。只有符合 WHERE 条件的行才会被更新。

  • WHERE 子句是可选的。如果不指定 WHERE 子句,则更新表中的所有行。

  • RETURNING 子句是可选的。它用于返回更新的行的信息。

    expr 可以是列名或者表达式,多个列或者表达式请使用逗号分隔。 您还可以使用 * 表示表中的所有的列。

    如果不指定 RETURNING 子句, UPDATE 语句将返回更新的行数。

UPDATE 语句中的 WHERE 子句非常重要。除非您特意,否则不要省略 WHERE 子句。

UPDATE 实例

下面我们将通过几个实例来展示 UPDATE 的具体用法。

在以下实例中,customer表中存储了客户的信息。

使用 UPDATE 修改单列值

在这个例子中,我们将把 customer_id 等于 1 的客户的电子邮件修改为 NEW.MARY.SMITH@sakilacustomer.org

  1. 使用以下 SELECT 语句查看更新前的数据。

SELECT first_name, last_name, email FROM customer WHERE customer_id = 1;
 first_name | last_name |             email
------------+-----------+-------------------------------
 MARY       | SMITH     | MARY.SMITH@sakilacustomer.org
  1. 使用以下 UPDATE 语句更新 email 列的值。

UPDATE customer SET email = 'NEW.MARY.SMITH@sakilacustomer.org' WHERE customer_id = 1;
UPDATE 1

在此 UPDATE 语句中:

  • 通过 WHERE子句指定更新的条件为 customer_id = 1

  • 通过 SET 子句将 email 列的值设置为新电子邮件。

UPDATE 返回了 UPDATE 1,它表示更新了一行。

  1. 或者,您可以使用以下 RETURNING 子句直接查看更新后的数据:

UPDATE customer SET email = 'NEW.MARY.SMITH@sakilacustomer.org' WHERE customer_id = 1 RETURNING first_name, last_name, email;
 first_name | last_name |               email
------------+-----------+-----------------------------------
 MARY       | SMITH     | NEW.MARY.SMITH@sakilacustomer.org

使用 UPDATE 修改多列值

在这个例子中,我们将同时更新 customer_id 等于 1 的客户的 first_name, last_name, email 列。

UPDATE customer SET first_name = 'Tim',last_name = 'Duncan',email = 'Tim.Duncan@sakilacustomer.org' WHERE customer_id = 1 RETURNING first_name, last_name, email;
 first_name | last_name |             email
------------+-----------+-------------------------------
 Tim        | Duncan    | Tim.Duncan@sakilacustomer.org

使用表达式更新

使用 UPDATE 更新时,列的值可以设置为表达式的运算结果,比如函数或其他的运算。

下面的 UPDATE 更新所有客户的电子邮件的域名部分:

UPDATE customer SET email = REPLACE(email, 'sakilacustomer.org', 'sjkjc.com') RETURNING first_name, last_name, email;
 
 first_name  |  last_name   |              email
-------------+--------------+---------------------------------
 PATRICIA    | JOHNSON      | PATRICIA.JOHNSON@sjkjc.com
 LINDA       | WILLIAMS     | LINDA.WILLIAMS@sjkjc.com
 BARBARA     | JONES        | BARBARA.JONES@sjkjc.com
 ELIZABETH   | BROWN        | ELIZABETH.BROWN@sjkjc.com
 JENNIFER    | DAVIS        | JENNIFER.DAVIS@sjkjc.com
 MARIA       | MILLER       | MARIA.MILLER@sjkjc.com
 SUSAN       | WILSON       | SUSAN.WILSON@sjkjc.com
 MARGARET    | MOORE        | MARGARET.MOORE@sjkjc.com
 DOROTHY     | TAYLOR       | DOROTHY.TAYLOR@sjkjc.com
 LISA        | ANDERSON     | LISA.ANDERSON@sjkjc.com
...
(599 rows)

注意,本例中没有使用 WHERE 子句,所以表中所有的数据都进行了更新。

使用子查询更新

下面实例展示了如何为没有绑定商店的客户绑定一个随机商店。

UPDATE customer SET store_id = (SELECT store_id FROM store ORDER BY random() LIMIT 1) WHERE store_id IS NULL;

在本例中,我们通过以下 SELECT 语句返回一个随机的商店 id:

SELECT store_id FROM store ORDER BY random() LIMIT 1

SET 子句中,将 store_id 的值设置为上面的子查询的结果。

UPDATE 语句用来更新一个表中的一个或者多个已有的行。如果带有 RETURNING 子句, UPDATE 语句将返回更新后的行,否则它将返回被更新的行数。

UPDATE … FROM 用法与实例

有时候,你需要根据另一个表中的数据更新一个表中的数据。比如:根据产品销量明细表更新销量统计表。

在 unvdb,您可以使用子查询做到一点,也可以更加方便地使用 UPDATE...FROM 语句完成它。

UPDATE…FROM 语法

要使用 UPDATE...FROM 语句根据另一个表中的数据更新一个表中的数据,请遵循以下语法:

UPDATE [IGNORE] table_name SET column_name1 = value1,column_name2 = value2,... FROM another_table[, ...] WHERE clause [RETURNING expr];
  • 和普通的 UPDATE 语句相比,此语句多了 FROM 子句,并且 WHERE 子句是必须的。

  • 您需要在 WHERE 子句中指定两个表连接的条件。

比如下面的语句:

UPDATE a
SET a.c1 = b.c2
FROM b
WHERE a.b_id = b.id;

对于 a 表的每一行,该 UPDATE 语句都检查 b 表的每一行。如果值 a 表的 b_id 列的值等于在 b 表的 id 列,该 UPDATE 语句将更新 b 的表 c2 列的值到 a 表的 c1 列。

UPDATE…FROM 实例

这里的实例用到了以下表:

  • city表中存储了城市的信息。

  • country表中存储了国家的信息。

假设现在有一个需求,需要更新 city 表中的城市名称,在其后添加 @ 和国家名,请运行以下语句:

UPDATE city_copy a SET city = city || '@' || b.country FROM country b WHERE b.country_id = 1 or b.country_id = 2 RETURNING city_id, city;
 city_id |                       city
---------+--------------------------------------------------
       1 | A Corua (La Corua)@Spain
       2 | Abha@Saudi Arabia
       3 | Abu Dhabi@United Arab Emirates
       4 | Acua@Mexico
       5 | Adana@Turkey
       6 | Addis Abeba@Ethiopia
       7 | Aden@Yemen
       8 | Adoni@India
       9 | Ahmadnagar@India
      10 | Akishima@Japan
      ...
      
 (600 rows)

这里,我们在 UPDATE 语句中使用 FROM country b 子句从 country 表中获取满足条件 a.country_id = b.country_id 的行,并将该行中的 country 列的值应用到表达式 city || '@' || b.country 中。

您也可以使用子查询实现上面的需求,如下:

UPDATE city_copy a SET city = ( SELECT a.city || '@' || b.country FROM country b WHERE a.country_id = b.country_id) RETURNING city_id, city;

DELETE 语句用法与实例

DELETE 语句用于从表中删除符合指定条件的行或者所有行。

DELETE 语法

要从 unvdb 数据库中的表中删除行,请按照如下的语法形式使用 DELETE 语句。

DELETE FROM table_name [where_clause] [RETURNING expr];
  • DELETE FROM 关键字后的 table_name 是要从中删除数据的表的名称。

  • where_clauseWHERE 子句。 您可以在其中使用条件来指定要删除表中的哪些行。

  • WHERE 子句是可选的。如果不指定 WHERE 子句,表中的所有行将被删除。

  • RETURNING 子句是可选的。它用于返回删除的行的信息。

    expr 可以是列名或者表达式,多个列或者表达式请使用逗号分隔。 您还可以使用 * 表示表中的所有的列。

    如果不指定 RETURNING 子句, DELETE 语句将返回删除的行数。

如果您要更加高效率的清空一个表,请优先使用 TRUNCATE TABLE 语句。

DELETE 语句示例

为了防止数据丢失,我们将创建一个表 film_copy,它是 film 表的拷贝。

CREATE TABLE film_copy AS SELECT * FROM film;

film_copyfilm 有相同的列和行。

使用 unvdb DELETE 从表中删除满足条件的行

要从 film_copy 表中删除 film_id 为 1 的 1 个影片,请使用如下语句:

DELETE FROM film_copy
WHERE film_id = 1;
DELETE 1

该语句返回 1 表示已经删除了 1 行。

要从 film_copy 表中删除 film_id 为 2, 3 或者 4 的 3 个影片,请使用如下语句:

DELETE FROM film_copy
WHERE film_id in (2, 3, 4);
DELETE 3

该语句返回 3 表示已经删除了 3 行。

使用 unvdb DELETE 删除并返回删除的行

要从 film_copy 表中删除 film_id 为 10 或者 11 的 2 个影片,并返回删除的影片的标题,请使用如下语句:

DELETE FROM film_copy
WHERE film_id in (10, 11)
RETURNING film_id, title;
 film_id |      title
---------+------------------
      10 | ALADDIN CALENDAR
      11 | ALAMO VIDEOTAPE
(2 rows)

这里, DELETE 删除了 2 行,并返回了删除的 2 行的中的部分列。

使用 unvdb DELETE 从表中删除所有行

要从 film_copy 表中删除所有的影片, 请使用如下语句:

DELETE FROM film_copy;

DELETE 语句用来从一个表中删除一个或多个行。 如果不带有 WHERE 子句, 则删除所有的行。 如果带有 RETURNING 子句, DELETE 语句将返回删除的行的信息,否则它返回删除的行数。

TRUNCATE TABLE 清空表

当我们需要清空一个表中的所有行时,除了使用 DELETE * FROM table 还可以使用 TRUNCATE TABLE 语句。

TRUNCATE 语句和不带任何条件的 DELETE 语句的效果相同。但是 TRUNCATE 速度更快,因为它不扫描表。并且,TRUNCATE 会立即回收磁盘空间。 TRUNCATE 在清空大表时很有用。

TRUNCATE 语法

要清空一个或者多个表中的所有的行,请按照以下语法使用使用 TRUNCATE 语句:

TRUNCATE [TABLE] [ONLY] table_name [ * ] [, ... ] [RESTART IDENTITY | CONTINUE IDENTITY] [ CASCADE | RESTRICT ]
  • TABLE 关键字是可选的。

  • ONLY 关键字是可选的。如果在表名前指定了 ONLY,则只清空此表,不包含它的子表。否则,此表和它的子表都将被清空。

  • table_name 是要清空的表的名字。 表名后的 * 明确的指示子表也将被清空。

  • 您可以在一个 TRUNCATE 语句中清空多个表。 多个表名使用逗号分隔。

  • RESTART IDENTITY 选项指示自动重置表中的列拥有的序列。 CONTINUE IDENTITY 选项指示不改变表中的序列的值,它是默认的。

  • CASCADE 选项指示同时清空那些通过外键引用 table_name 的表。 RESTRICT 选项指示如果有外键引用要清空的表,则拒绝操作。

TRUNCATE 实例

我们通过以下实例比较 TRUNCATEDELETE 在清空一个表的效率。

首先,我们使用下面的语句创建一个 test_truncate 表用于测试:

CREATE TABLE test_truncate (v INTEGER);

然后,使用下面的 sql 语句插入 1000 万条数据:

INSERT INTO test_truncate (v) SELECT generate_series(1, 10000000) v;

为了观察查询语句消耗的时间,请先使用下面的命令打开计时:

\timing

通过以下 DELETE 语句删除表中的所有行:

DELETE FROM test_truncate;
DELETE 10000000
Time: 6566.458 ms (00:06.566)

上面是 DELETE 语句的返回结果。它显示了从表中删除 10000000 行,总共耗费 6566.458 毫秒。

再使用上面的 INSERT 语句重新插入 1000 万行后,通过以下 TRUNCATE TABLE 语句清空表:

TRUNCATE TABLE test_truncate;

TRUNCATE TABLE
Time: 31.785 ms

上面是 TRUNCATE TABLE 语句的返回结果。它显示了清空此表共耗费 31.785 毫秒。所以 TRUNCATEDELETE 快的多。

CASE 条件表达式

CASE 表达式是一个条件表达式,它与其他编程语言中的 if-else 语句作用相同。

您可以在 SELECT语句以及 WHERE, GROUP BY, 和 HAVING 子句中使用 CASE 表达式。

CASE 语法

这里是 unvdb CASE 表达式的语法:

CASE
    WHEN condition1 THEN result1
    WHEN condition2 THEN result2
    ELSE value3
END
  • condition1, condition2 是一些布尔表达式,返回值为 truefalse

  • 这个表达式可以理解为:如果 condition1 为真返回 result1,如果 condition2 为真返回 result2,否则返回 result3

  • 您可以指定一个或者多个 WHEN ... THEN 判断。

  • ELSE 部分是可以省略的,您只能指定一个 ELSE 语句。

  • 所有的判断从上向下依次判断,直到遇到条件为真,并返回此条件对应的值。如果没有条件为真,则返回 ELSE 指定的值。如果没有指定 ELSE,则返回 NULL

如果所有的条件都是针对同一个字段或者表达式进行等值判断,则可以将 CASE 表达式简写为如下形式:

CASE expr
    WHEN value1 THEN result1
    WHEN value2 THEN result2
    ELSE result3
END

这个表达式的含义是:如果 expr 等于 value1,则返回 result1,如果 expr 等于 value2,则返回 result2,否则返回 result3

CASE 实例

简单示例

先看一个简单的示例。

比如,您想要获取当前日期是星期几的名称

SELECT
  current_date "Today",
  CASE
    WHEN extract(DOW FROM current_date) = 1 THEN 'Monday'
    WHEN extract(DOW FROM current_date) = 2 THEN 'Tuesday'
    WHEN extract(DOW FROM current_date) = 3 THEN 'Wednesday'
    WHEN extract(DOW FROM current_date) = 4 THEN 'Thursday'
    WHEN extract(DOW FROM current_date) = 5 THEN 'Friday'
    WHEN extract(DOW FROM current_date) = 6 THEN 'Saturday'
    ELSE 'Sunday'
  END "Day of Week";
   Today    | Day of Week 
------------+-------------
 2023-07-25 | Tuesday

这里,我们使用 current_date 获得当前的日期,并使用 extract()函数获取当前日期的工作日编号。

由于上面的 CASE 表达式中的条件都是等值判断,因此我们可以将 CASE 表达式简化为:

SELECT
  current_date "Today",
  CASE extract(DOW FROM current_date)
    WHEN 1 THEN 'Monday'
    WHEN 2 THEN 'Tuesday'
    WHEN 3 THEN 'Wednesday'
    WHEN 4 THEN 'Thursday'
    WHEN 5 THEN 'Friday'
    WHEN 6 THEN 'Saturday'
    ELSE 'Sunday'
  END "Day of Week";
   Today    | Day of Week 
------------+-------------
 2023-07-25 | Tuesday

使用 CASE 表达式实现自定义排序

有时候单纯的按照字段的值排序并不能满足要求,我们需要按照自定义的顺序的排序。比如,我们需要按照电影分级 'G', 'PG', 'PG-13', 'R', 'NC-17' 的顺序对影片进行排序。

对于这样的需求,它可以理解为按照列表中元素的索引位置进行排序。我们使用 CASE 子句函数实现它。

假设您要根据影片的分级按照的 'G', 'PG', 'PG-13', 'R', 'NC-17' 顺序对影片进行排序。 下面使用 CASE 表达式实现自定义排序:

SELECT
    film_id, title, rating
FROM
    film
ORDER BY CASE rating
    WHEN 'G' THEN 1
    WHEN 'PG' THEN 2
    WHEN 'PG-13' THEN 3
    WHEN 'R' THEN 4
    WHEN 'NC-17' THEN 5
END;
     357 | GILBERT PELICAN             | G
     597 | MOONWALKER FOOL             | G
     354 | GHOST GROUNDHOG             | G
...
     595 | MOON BUNCH                  | PG
       6 | AGENT TRUMAN                | PG
     600 | MOTIONS DETAILS             | PG
...
       9 | ALABAMA DEVIL               | PG-13
     657 | PARADISE SABRINA            | PG-13
     956 | WANDA CHAMBER               | PG-13
...
     749 | RULES HUMAN                 | R
       8 | AIRPORT POLLOCK             | R
      17 | ALONE TRIP                  | R
...
     520 | LICENSE WEEKEND             | NC-17
     517 | LESSON CLEOPATRA            | NC-17
     114 | CAMELOT VACATION            | NC-17
...
(1000 rows)

在这个例子中,我们使用 CASE 子句将电影的分级转换为一个数字。然后使用 ORDER BY 按照这个数字进行排序。