🐬 第17章 组合查询
📄 一句话概括

组合查询就是把几条 SELECT 的结果上下摞成一份,用 UNION 或 UNION ALL 拼。 这一章的重点只有一个:去重与不去重的区别。 Demo 里两组条件各给 4 行和 6 行, UNION 只留 9 行(FU1 两边都满足,去掉一份),UNION ALL 给 10 行; 一条 OR 也能得到同样的 9 行——所以这一章还得讲清楚什么时候不该用 UNION。

📝 本章和前面几章的关系

前面所有查询都是「一张结果集」,这一章开始把多份结果拼起来。 它和联结不是一回事:联结是横向拼列(表变宽), 组合查询是纵向摞行(结果变长)。 这两个词新手最容易混。

一、组合查询:把几条 SELECT 并起来

两条 SELECT 各查各的,结果要一份合并的——这就是组合查询要解决的事。 它不改变每条 SELECT 的逻辑,只是把结果摞起来。 和联结的区别记住一句话:联结让结果变宽(多列),组合让结果变长(多行)。 所以列数必须一致,行数才会叠加。 教材给的两个典型场景:一是几个条件组各自已经能单独跑,想拼成一份; 二是数据来自不同的表,但想要的列一样。

二、UNION 与 UNION ALL

UNION 默认去重:两条 SELECT 的结果摞在一起,重复的行只留一份。 UNION ALL 不去重,来几行算几行。 Demo 里 4 加 6 本该是 10,UNION 只给 9——FU1 同时满足两个条件,被去掉了一份。 去重要付代价:数据库得先把结果排序或建哈希才能判重, 数据量大时这一步很贵。 确知不会重复就用 UNION ALL,而且它不会把「真重复」的数据悄悄吃掉。 这一点在业务上很要命:两条查询要的是「两组记录」,重复本来就该保留, 用 UNION 反而丢了信息。

三、规则与限制

三条硬规则:每条 SELECT 的列数必须相同;对应列的类型要能兼容;结果列名取第一条 SELECT 的。 所以别名只写在第一条上就够,后面的写不写都不影响表头。 列数不一致会直接报错,这是最容易犯的错。 ORDER BY 只能写在最后一条 SELECT 后面,它排的是合并后的整份结果。 每条各写一个 ORDER BY 是语法错——想先各自排序再合并,得用子查询包一层。

四、和 OR 怎么选

同一个表内换条件,OR 更短。 Demo 里那条 OR 和 UNION 给的九行完全一样。能一条写完就别拆。 UNION 真正有价值的地方是两种:条件跨了不同的表; 或者几段查询各自已经写好、各自能单独跑,拼起来比改写成一句更省事。 还有一个隐性好处:可读性。 三条各管一件事的 SELECT 摞在一起, 比一句塞了五个 OR 的大条件好维护——尤其半年后回来看的人是你自己。

五、小结

教材小结:UNION 组合多条 SELECT,默认去重,UNION ALL 不去重,列数要一致,ORDER BY 只写最后。 本课补三条:一是联结变宽、组合变长;二是确知不重复就用 UNION ALL,省一次去重; 三是同表换条件优先用 OR。

六、动手 Demo:9 行还是 10 行,就看有没有 ALL

Demo 六段:先把两条 SELECT 各跑一遍,再 UNION 一次、UNION ALL 一次,然后看列名取哪条、 故意写错列数看报错,最后和 OR 对一下。 盯着 9 与 10 这两个数:差的那一行就是被去重掉的 FU1。

import sqlite3
con = sqlite3.connect(":memory:")

# 两条 SELECT,各查各的
con.execute("SELECT prod_id, prod_price FROM products WHERE prod_price <= 5")
con.execute("SELECT prod_id, prod_price FROM products "
            "WHERE vend_id IN (1001, 1002)")
# UNION:合并并去重,FU1 只留一份
con.execute("SELECT prod_id, prod_price FROM products WHERE prod_price <= 5 "
            "UNION SELECT prod_id, prod_price FROM products "
            "WHERE vend_id IN (1001, 1002) ORDER BY prod_price")
# UNION ALL:不去重,FU1 出现两次
con.execute("SELECT prod_id, prod_price FROM products WHERE prod_price <= 5 "
            "UNION ALL SELECT prod_id, prod_price FROM products "
            "WHERE vend_id IN (1001, 1002) ORDER BY prod_price")
# 列名取第一条 SELECT 的
con.execute("SELECT prod_id AS id, prod_price AS price FROM products "
            "WHERE prod_price <= 5 UNION SELECT prod_id, prod_price "
            "FROM products WHERE vend_id = 1005")
# 列数不一致会报错
try:
    con.execute("SELECT prod_id, prod_price FROM products WHERE prod_price <= 5 "
                "UNION SELECT prod_id FROM products WHERE vend_id = 1005")
except sqlite3.OperationalError as e:
    print("报错:", e)
# 同一个表内换条件,OR 一条就够
con.execute("SELECT prod_id, prod_price FROM products "
            "WHERE prod_price <= 5 OR vend_id IN (1001, 1002)")

实跑输出:

--------------------------------------------------------------------
第十七课:组合查询
--------------------------------------------------------------------

  【一】两个条件组,两条 SELECT
  SQL> SELECT prod_id, prod_price FROM products WHERE prod_price <= 5 ORDER BY prod_price
  prod_id     prod_price
  -----------------------
  FC          2.50
  TNT1        2.50
  FU1         3.42
  SLING       4.49
  (4 行)
  SQL> SELECT prod_id, prod_price FROM products WHERE vend_id IN (1001,
       1002) ORDER BY prod_price
  prod_id     prod_price
  -----------------------
  FU1         3.42
  ANV01       5.99
  OL1T        8.99
  ANV02       9.99
  ANV03       14.99
  SAFE        50.00
  (6 行)
  **上面四条(便宜货),下面六条(这两家的货),中间有重叠。** 想把两份并成一份,就得用 UNION。

  【二】UNION:合并并去掉重复行
  SQL> SELECT prod_id, prod_price FROM products WHERE prod_price <= 5 UNION SELECT prod_id,
       prod_price FROM products WHERE vend_id IN (1001, 1002) ORDER BY prod_price
  prod_id     prod_price
  -----------------------
  FC          2.50
  TNT1        2.50
  FU1         3.42
  SLING       4.49
  ANV01       5.99
  OL1T        8.99
  ANV02       9.99
  ANV03       14.99
  SAFE        50.00
  (9 行)
  **4 加 6 本该是 10,结果只有 9 行**——FU1 两边都满足,被去掉了一份。
  **UNION 默认去重**,这是它和 UNION ALL 唯一但关键的差别。

  【三】UNION ALL:不去重,快
  SQL> SELECT prod_id, prod_price FROM products WHERE prod_price <= 5 UNION ALL SELECT prod_id,
       prod_price FROM products WHERE vend_id IN (1001, 1002) ORDER BY prod_price
  prod_id     prod_price
  -----------------------
  FC          2.50
  TNT1        2.50
  FU1         3.42
  FU1         3.42
  SLING       4.49
  ANV01       5.99
  OL1T        8.99
  ANV02       9.99
  ANV03       14.99
  SAFE        50.00
  (10 行)
  **10 行,FU1 出现了两次。** 去重要额外排一遍序或建哈希,**确知不重复时用 UNION ALL 更快**,
  而且不会把「真重复」的数据悄悄吃掉。

  【四】UNION 的硬规则
  **三条**:每条 SELECT 的列数必须相同;对应列的类型要能兼容;**列名取第一条 SELECT 的**。
  SQL> SELECT prod_id AS id,
       prod_price AS price FROM products WHERE prod_price <= 5 UNION SELECT prod_id,
       prod_price FROM products WHERE vend_id = 1005
  id         price
  -----------------
  FC         2.50
  FU1        3.42
  JP2000     55.00
  SLING      4.49
  TNT1       2.50
  (5 行)
  **表头是 id、price**,来自第一条;第二条没写别名也不影响。
  **列数不一样会报错:SELECTs to the left and right of UNION do not have the same number of result columns**

  【五】ORDER BY 只能写在最后
  **别在每条 SELECT 里各写一个 ORDER BY**,只能在整个 UNION 后面写一个,
  **它排的是合并后的整份结果。**
  **上面第【二】段那句就只有一个 ORDER BY,在最后一条 SELECT 后面。** 写成两个会直接语法错。

  【六】和 OR 比,什么时候该用 UNION
  SQL> SELECT prod_id, prod_price FROM products WHERE prod_price <= 5 OR vend_id IN (1001,
       1002) ORDER BY prod_price
  prod_id     prod_price
  -----------------------
  FC          2.50
  TNT1        2.50
  FU1         3.42
  SLING       4.49
  ANV01       5.99
  OL1T        8.99
  ANV02       9.99
  ANV03       14.99
  SAFE        50.00
  (9 行)
  **一条 OR 也是九行**,和 UNION 一样。**同一个表内换条件,OR 更短。**
  **UNION 的价值在两种情况**:条件跨了不同的表;
  或者你想把几段「各自已经写好、各自能单独跑」的查询拼起来。
  **能一条写完就别拆**,拆了只是多打几行字。

第一段分开跑。 4 行与 6 行,FU1 两边都在。 第二段 UNION。 9 行,FU1 只剩一份。 第三段 UNION ALL。 10 行,FU1 出现两次。 第四段别名。 表头是 id 与 price,来自第一条 SELECT。 第五段故意写错列数。 引擎报错:UNION 两边列数不一致——这条错误信息写得很清楚,照它改就行。 第六段 OR。 也是九行,说明这个场景不需要 UNION。

30 秒实验

  1. 把第二段的 UNION 改成 UNION ALL,行数从 9 变几?
  2. 给第二条 SELECT 也写 AS id, AS price,表头会变吗?
  3. 用 UNION 把「价格最高的三个」和「价格最低的三个」拼成一份,会重复吗?

常见误区

  1. 以为 UNION 和 UNION ALL 一样。前者去重(有代价),后者原样摞。
  2. 在每条 SELECT 里各写 ORDER BY。只能在整个组合查询最后写一个。
  3. 用 UNION 拼本来一行 OR 能写完的条件。多打几行字,还多一次去重。
自测三问(点开对答案) 1. UNION 和 UNION ALL 的区别是什么? —— UNION 会去掉重复行(要额外排序或哈希),UNION ALL 原样摞起来、更快。 2. 组合查询和联结有什么区别? —— 组合查询纵向摞行(结果变长,列数必须一致),联结横向拼列(结果变宽)。 3. 为什么 ORDER BY 只能写在最后一条 SELECT 后面? —— 因为它排的是合并后的整份结果;写在中间会被当成那条 SELECT 的一部分,语法上不允许。

到这一章,取数的手段基本齐了。 但 LIKE 只能找「这几个字符」,想要「按词找、按相关度排」,得换一套机制——下一章的全文本搜索。

相关笔记