MYSQL|集合运算(下)

学习链接:http://datawhale.club/t/topic/473

4.2.1.3 结合 GROUP BY 子句使用内连结

结合 GROUP BY 子句使用内连结, 需要根据分组列位于哪个表区别对待.最简单的情形, 是在内连结之前就使用 GROUP BY 子句.但是如果分组列和被聚合的列不在同一张表, 且二者都未被用于连结两张表, 则只能先连结, 再聚合.

练习题:

每个商店中, 售价最高的商品的售价分别是多少?

mysql> select sp.shop_id,sp.shop_name, max(p.sale_price) as max_price
    -> from shopproduct as sp
    -> inner join product as p
    -> on sp.product_id = p.product_id
    -> group by sp.shop_id,sp.shop_name;
image.png

4.2.1.4 自连结(SELF JOIN)

之前的内连结, 连结的都是不一样的两个表. 但实际上一张表也可以与自身作连结, 这种连接称之为自连结. 需要注意, 自连结并不是区分于内连结和外连结的第三种连结, 自连结可以是外连结也可以是内连结, 它是不同于内连结外连结的另一个连结的分类方法.
4.2.1.5 内连结与关联子查询
回忆一下关联子查询中的问题:找出每个商品种类当中售价高于该类商品的平均售价的商品。当时我们是使用关联子查询来实现的。

mysql> select product_type,product_name,sale_price
    -> from product as p1
    -> where sale_price > (select avg(sale_price)
    -> from product as p2
    -> where p1.product_type = p2.product_type
    -> group by product_type);
image.png

使用内连结同样可以解决这个问题:
首先, 使用 GROUP BY 按商品类别分类计算每类商品的平均价格.

mysql> select product_type, avg(sale_price) as avg_price
    -> from product
    -> group by product_type;
image.png

接下来, 将上述查询与表 product 按照 product_type (商品种类)进行内连结.

mysql> select p1.product_id, p1.product_name, p1.product_type, p1.sale_price, p2.avg_price
    -> from product as p1
    -> inner join
    -> (select product_type, avg(sale_price) as avg_price
    -> from product
    -> group by product_type) as p2
    -> on p1.product_type = p2.product_type;
image.png

最后, 增加 WHERE 子句, 找出那些售价高于该类商品平均价格的商品.完整的代码如下:

mysql> select p1.product_id, p1.product_name, p1.product_type, p1.sale_price, p2.avg_price
    -> from product as p1
    -> inner join
    -> (select product_type, avg(sale_price) as avg_price
    -> from product
    -> group by product_type) as p2
    -> on p1.product_type = p2.product_type
    -> where p1.sale_price > p2.avg_price;
image.png

仅仅从代码量上来看, 上述方法似乎比关联子查询更加复杂, 但这并不意味着这些代码更难理解. 通过上述分析, 很容易发现上述代码的逻辑实际上更符合我们的思路, 因此尽管看起来复杂, 但思路实际上更加清晰。

4.2.1.6 自然连结(NATURAL JOIN)

自然连结并不是区别于内连结和外连结的第三种连结, 它其实是内连结的一种特例–当两个表进行自然连结时, 会按照两个表中都包含的列名来进行等值内连结, 此时无需使用 ON 来指定连接条件.

mysql> select * from shopproduct natural join product;
image.png

上述查询得到的结果, 会把两个表的公共列(这里是 product_id, 可以有多个公共列)放在第一列, 然后按照两个表的顺序和表中列的顺序, 将两个表中的其他列都罗列出来。

练习题:

试写出与上述自然连结等价的内连结.

mysql> select sp.product_id, sp.shop_id, sp.shop_name, sp.quantity,
    -> p.product_name, p.product_type, p.sale_price, p.purchase_price, p.regist_date
    -> from shopproduct as sp
    -> inner join product as p
    -> on sp.product_id = p.product_id;

使用自然连结还可以求出两张表或子查询的公共部分, 例如教材中 7-1 选取表中公共部分–INTERSECT 一节中的问题: 求表 product 和表 product2 中的公共部分, 也可以用自然连结来实现:

mysql> select * from product natural join product2;
image.png

这个结果和书上给的结果并不一致, 少了运动 T 恤, 这是由于运动 T 恤的 regist_date 字段为空, 在进行自然连结时, 来自于 product 和 product2 的运动 T 恤这一行数据在进行比较时, 实际上是在逐字段进行等值连结, 回忆我们在 6.2ISNULL,IS NOT NULL 这一节学到的缺失值的比较方法就可得知, 两个缺失值用等号进行比较, 结果不为真. 而连结只会返回对连结条件返回为真的那些行.
如果我们将查询语句进行修改:

mysql> select * from
    -> (select product_id,product_name from product) as A
    -> natural join
    -> (select product_id, product_name from product2) as B;
image.png

4.2.1.7使用连结求交集

我们在上一节表的加减法里知道, MySQL 8.0 里没有交集运算, 我们当时是通过并集和差集来实现求交集的. 现在学了连结, 让我们试试使用连结来实现求交集的运算.
练习题: 使用内连结求 product 表和 product2 表的交集.

mysql> select p1.*
    -> from product as p1
    -> inner join product2 as p2
    -> on (p1.product_id = p2.product_id
    -> and p1.product_name = p2.product_name
    -> and p1.product_type = p2.product_type
    -> and p1.sale_price = p2.sale_price
    -> and p1.regist_date = p2.regist_date);
image.png

注意上述结果和 P230 的结果并不一致–少了 product_id='0001' 这一行, 观察源表数据可发现, 少的这行数据的 regist_date 为缺失值,而缺失值是不能用等号进行比较的,所以查询结果中缺少改行数据。
如果我们仅仅用 product_id 来进行连结:

mysql> select p1.* from product as p1
    -> inner join product2 as p2
    -> on p1.product_id = p2.product_id;
image.png

4.2.2 外连结(OUTER JOIN)

内连结会丢弃两张表中不满足 ON 条件的行,和内连结相对的就是外连结. 外连结会根据外连结的种类有选择地保留无法匹配到的行.
按照保留的行位于哪张表,外连结有三种形式: 左连结, 右连结和全外连结.
左连结会保存左表中无法按照 ON 子句匹配到的行, 此时对应右表的行均为缺失值; 右连结则会保存右表中无法按照 ON 子句匹配到的行, 此时对应左表的行均为缺失值; 而全外连结则会同时保存两个表中无法按照 ON子句匹配到的行, 相应的另一张表中的行用缺失值填充.
三种外连结的对应语法分别为:

-- 左连结
FROM LEFT OUTER JOIN ON
-- 右连结
FROM RIGHT OUTER JOIN ON
-- 全外连结
FROM FULL OUTER JOIN ON

4.2.2.1 左连结与右连结

由于连结时可以交换左表和右表的位置, 因此左连结和右连结并没有本质区别.接下来我们先以左连结为例进行学习. 所有的内容在调换两个表的前后位置, 并将左连结改为右连结之后, 都能得到相同的结果. 稍后再介绍全外连结的概念.

4.2.2.2 使用左连结从两个表获取信息

如果你仔细观察过将 shopproduct 和 product 进行内连结前后的结果的话, 你就会发现, product 表中有两种商品并未在内连结的结果里, 就是说, 这两种商品并未在任何商店有售(这通常意味着比较重要的业务信息, 例如, 这两种商品在所有商店都处于缺货状态, 需要及时补货). 现在, 让我们先把之前内连结的 SELECT 语句转换为左连结试试看吧.
练习题: 统计每种商品分别在哪些商店有售, 需要包括那些在每个商店都没货的商品.
使用左连结的代码如下(注意区别于书上的右连结):

mysql> select sp.shop_id, sp.shop_name, sp.product_id,
    -> p.product_name, p.sale_price
    -> from product as p
    -> left outer join shopproduct as sp
    -> on sp.product_id = p.product_id;
image.png

我们观察上述结果可以发现, 有两种商品: 高压锅和圆珠笔, 在所有商店都没有销售. 由于我们在 SELECT 子句选择列的显示顺序以及未对结果进行排序的原因, 这个事实需要你仔细地进行观察.

  • 外连结要点 1: 选取出单张表中全部的信息
    与内连结的结果相比,不同点显而易见,那就是结果的行数不一样.内连结的结果中有 13 条记录,而外连结的结果中有 15 条记录,增加的 2 条记录到底是什么呢?这正是外连结的关键点. 多出的 2 条记录是高压锅和圆珠笔,这 2 条记录在 shopproduct 表中并不存在,也就是说,这 2 种商品在任何商店中都没有销售.由于内连结只能选取出同时存在于两张表中的数据,因此只在 product 表中存在的 2 种商品并没有出现在结果之中.相反,对于外连结来说,只要数据存在于某一张表当中,就能够读取出来.在实际的业务中,例如想要生成固定行数的单据时,就需要使用外连结.如果使用内连结的话,根据 SELECT 语句执行时商店库存状况的不同,结果的行数也会发生改变,生成的单据的版式也会受到影响,而使用外连结能够得到固定行数的结果.虽说如此,那些表中不存在的信息我们还是无法得到,结果中高压锅和圆珠笔的商店编号和商店名称都是 NULL (具体信息大家都不知道,真是无可奈何).外连结名称的由来也跟 NULL 有关,即“结果中包含原表中不存在(在原表之外)的信息”.相反,只包含表内信息的连结也就被称为内连结了.

  • 外连结要点 2:使用 LEFT、RIGHT 来指定主表.
    外连结还有一点非常重要,那就是要把哪张表作为主表.最终的结果中会包含主表内所有的数据.指定主表的关键字是 LEFT 和 RIGHT.顾名思义,使用 LEFT 时 FROM 子句中写在左侧的表是主表,使用 RIGHT 时右侧的表是主表.代码清单 7-11 中使用了 RIGHT ,因此,右侧的表,也就是 product 表是主表.我们还可以像代码清单 7-12 这样进行改写,意思完全相同.这样你可能会困惑,到底应该使用 LEFT 还是 RIGHT?其实它们的功能没有任何区别,使用哪一个都可以.通常使用 LEFT 的情况会多一些,但也并没有非使用这个不可的理由,使用 RIGHT 也没有问题.
    通过交换两个表的顺序, 同时将 LEFT 更换为 RIGHT(如果原先是 RIGHT,则更换为 LEFT), 两种方式会到完全相同的结果.

4.2.2.3 结合 WHERE 子句使用左连结

上一小节我们学到了外连结的基础用法, 并且在上一节也学习了结合WHERE子句使用内连结的方法, 但在结合WHERE子句使用外连结时, 由于外连结的结果很可能与内连结的结果不一样, 会包含那些主表中无法匹配到的行, 并用缺失值填写另一表中的列, 由于这些行的存在, 因此在外连结时使用WHERE子句, 情况会有些不一样. 我们来看一个例子:

练习题:

使用外连结从 shopproduct 表和 product 表中找出那些在某个商店库存少于50的商品及对应的商店.希望得到如下结果.


image.png

注意:高压锅和圆珠笔两种商品在所有商店都无货,也应该包括在内。

mysql> select p.product_id, p.product_name, p.sale_price
    -> , sp.shop_id, sp.shop_name, sp.quantity
    -> from product as p
    -> left outer join shopproduct as sp
    -> on sp.product_id = p.product_id
    -> where sp.quantity < 50;
image.png

观察发现, 返回结果缺少了在所有商店都无货的高压锅和圆珠笔。聪明的你可能很容易想到,在WHERE过滤条件中增加 OR quantity IS NULL 的判断条件,便可以得到预期结果。然而在实际环境中,由于数据量大且数据质量并非像我们设想的那样"干净",我们并不能容易地意识到缺失值等问题数据的存在,因此,还是让我们想一下如何改写我们的查询以使得它能够适应更复杂的真实数据的情形吧。

联系到我们已经掌握了的SQL查询的执行顺序(FROM->WHERE->SELECT),我们发现, 问题可能出在筛选条件上, 因为在进行完外连结后才会执行WHERE子句, 因此那些主表中无法被匹配到的行就被WHERE条件筛选掉了.

明白了这一点, 我们就可以试着把WHERE子句挪到外连结之前进行: 先写个子查询,用来从shopproduct表中筛选quantity<50的商品, 然后再把这个子查询和主表连结起来.
我们把上述思路写成SQL查询语句:

mysql> select p.product_id, p.product_name, p.sale_price,
    -> sp.shop_id, sp.shop_name, sp.quantity
    -> from product as p
    -> left outer join
    -> (select * from shopproduct where quantity<50) as sp
    -> on p.product_id = sp.product_id;
image.png

4.2.2.4 在 MySQL 中实现全外连结

有了对左连结和右连结的了解, 就不难理解全外连结的含义了. 全外连结本质上就是对左表和右表的所有行都予以保留, 能用 ON 关联到的就把左表和右表的内容在一行内显示, 不能被关联到的就分别显示, 然后把多余的列用缺失值填充.
遗憾的是, MySQL8.0 目前还不支持全外连结, 不过我们可以对左连结和右连结的结果进行 UNION 来实现全外连结.

4.2.3 多表连结

通常连结只涉及 2 张表,但有时也会出现必须同时连结 3 张以上的表的情况, 原则上连结表的数量并没有限制.

4.2.3.1 多表进行内连结

首先创建一个用于三表连结的表 Inventoryproduct.首先我们创建一张用来管理库存商品的表, 假设商品都保存在 P001 和 P002 这 2 个仓库之中.

CREATE TABLE Inventoryproduct
( inventory_id       CHAR(4) NOT NULL,
product_id         CHAR(4) NOT NULL,
inventory_quantity INTEGER NOT NULL,
PRIMARY KEY (inventory_id, product_id));
INSERT INTO Inventoryproduct (inventory_id, product_id, inventory_quantity)
VALUES ('P001', '0001', 0);
INSERT INTO Inventoryproduct (inventory_id, product_id, inventory_quantity)
VALUES ('P001', '0002', 120);
INSERT INTO Inventoryproduct (inventory_id, product_id, inventory_quantity)
VALUES ('P001', '0003', 200);
INSERT INTO Inventoryproduct (inventory_id, product_id, inventory_quantity)
VALUES ('P001', '0004', 3);
INSERT INTO Inventoryproduct (inventory_id, product_id, inventory_quantity)
VALUES ('P001', '0005', 0);
INSERT INTO Inventoryproduct (inventory_id, product_id, inventory_quantity)
VALUES ('P001', '0006', 99);
INSERT INTO Inventoryproduct (inventory_id, product_id, inventory_quantity)
VALUES ('P001', '0007', 999);
INSERT INTO Inventoryproduct (inventory_id, product_id, inventory_quantity)
VALUES ('P001', '0008', 200);
INSERT INTO Inventoryproduct (inventory_id, product_id, inventory_quantity)
VALUES ('P002', '0001', 10);
INSERT INTO Inventoryproduct (inventory_id, product_id, inventory_quantity)
VALUES ('P002', '0002', 25);
INSERT INTO Inventoryproduct (inventory_id, product_id, inventory_quantity)
VALUES ('P002', '0003', 34);
INSERT INTO Inventoryproduct (inventory_id, product_id, inventory_quantity)
VALUES ('P002', '0004', 19);
INSERT INTO Inventoryproduct (inventory_id, product_id, inventory_quantity)
VALUES ('P002', '0005', 99);
INSERT INTO Inventoryproduct (inventory_id, product_id, inventory_quantity)
VALUES ('P002', '0006', 0);
INSERT INTO Inventoryproduct (inventory_id, product_id, inventory_quantity)
VALUES ('P002', '0007', 0 );
INSERT INTO Inventoryproduct (inventory_id, product_id, inventory_quantity)
VALUES ('P002', '0008', 18);
image.png

接下来, 我们根据上表及 shopproduct 表和 product 表, 使用内连接找出每个商店都有哪些商品, 每种商品的库存总量分别是多少.

mysql> select sp.shop_id, sp.shop_name, sp.product_id,
    -> p.product_name, p.sale_price, ip.inventory_quantity
    -> from shopproduct as sp
    -> inner join product as p
    -> on sp.product_id = p.product_id
    -> inner join Inventoryproduct as ip
    -> on sp.product_id = ip.product_id
    -> where ip.inventory_id = 'P001';
image.png

我们可以看到, 连结第三张表的时候, 也是通过 ON 子句指定连结条件(这里使用最基础的等号将作为连结条件的 product 表和 shopproduct 表中的商品编号 product _id 连结了起来), 由于 product 表和 shopproduct 表已经进行了连结,因此就无需再对 product 表和 Inventoryproduct 表进行连结了(虽然也可以进行连结,但结果并不会发生改变, 因为本质上并没有增加新的限制条件).

即使想要把连结的表增加到 4 张、5 张……使用 INNER JOIN 进行添加的方式也是完全相同的.

4.2.3.2 多表进行外连结

正如之前所学发现的, 外连结一般能比内连结有更多的行, 从而能够比内连结给出更多关于主表的信息, 多表连结的时候使用外连结也有同样的作用.
例如,

mysql> SELECT p.product_id, p.product_name, p.sale_price,
    -> sp.shop_id, sp.shop_name, ip.inventory_quantity
    -> from product as p
    -> left outer join shopproduct as sp
    -> on sp.product_id = p.product_id
    -> left outer join Inventoryproduct as ip
    -> on sp.product_id = ip.product_id;
image.png

4.2.4 ON 子句进阶–非等值连结

在刚开始介绍连结的时候, 书上提到过, 除了使用相等判断的等值连结, 也可以使用比较运算符来进行连接. 实际上, 包括比较运算符(<,<=,>,>=, BETWEEN)和谓词运算(LIKE, IN, NOT 等等)在内的所有的逻辑运算都可以放在 ON 子句内作为连结条件.

4.2.4.1非等值自左连结(SELF JOIN)

使用非等值自左连结实现排名。
练习题:
请按照商品的售价从低到高,对售价进行累计求和[注:这个案例缺少实际意义, 并且由于有两种商品价格相同导致了不必要的复杂度, 但示例数据库的表结构比较简单, 暂时未想出有实际意义的例题]
首先, 按照题意, 对每种商品使用自左连结, 找出比该商品售价价格更低或相等的商品

mysql> select product_id, product_name, sale_price,
    -> sum(p2_price) as cum_price
    -> from
    -> (select p1.product_id, p1.product_name, p1.sale_price,
    -> p2.product_id as p2_id,
    -> p2.product_name as p2_name,
    -> p2.sale_price as p2_price
    -> from product as p1
    -> left outer join product as p2
    -> on ((p1.sale_price > p2.sale_price)
    -> or (p1.sale_price = p2.sale_price and p1.product_id <= p2.product_id))
    -> order by p1.sale_price, p1.product_id) as x
    -> group by product_id, product_name, sale_price
    -> order by sale_price,cum_price;
image.png

4.2.5 交叉连结—— CROSS JOIN(笛卡尔积)

之前的无论是外连结内连结, 一个共同的必备条件就是连结条件–ON 子句, 用来指定连结的条件. 如果你试过不使用这个连结条件的连结查询, 你可能已经发现, 结果会有很多行. 在连结去掉 ON 子句, 就是所谓的交叉连结(CROSS JOIN), 交叉连结又叫笛卡尔积, 后者是一个数学术语. 两个集合做笛卡尔积, 就是使用集合 A 中的每一个元素与集合 B 中的每一个元素组成一个有序的组合. 数据库表(或者子查询)的并,交和差都是在纵向上对表进行扩张或筛选限制等运算的, 这要求表的列数及对应位置的列的数据类型"相容", 因此这些运算并不会增加新的列, 而交叉连接(笛卡尔积)则是在横向上对表进行扩张, 即增加新的列, 这一点和连结的功能是一致的. 但因为没有了ON子句的限制, 会对左表和右表的每一行进行组合, 这经常会导致很多无意义的行出现在检索结果中. 当然, 在某些查询需求中, 交叉连结也有一些用处.

  • 1.使用关键字 CROSS JOIN 显式地进行交叉连结
mysql> select sp.shop_id, sp.shop_name, sp.product_id,
    -> p.product_name, p.sale_price
    -> from shopproduct as sp
    -> cross join product as p;
  • 2.使用逗号分隔两个表,并省略 ON 子句
mysql> select sp.shop_id, sp.shop_name, sp.product_id,
    -> p.product_name, p.sale_price
    -> from shopproduct as sp, product as p;

请大家试着执行一下以上语句.
可能大家会惊讶于结果的行数, 但我们还是先来介绍一下语法结构吧.对满足相同规则的表进行交叉连结的集合运算符是 CROSS JOIN (笛卡儿积).进行交叉连结时无法使用内连结和外连结中所使用的ON 子句,这是因为交叉连结是对两张表中的全部记录进行交叉组合,因此结果中的记录数通常是两张表中行数的乘积.本例中,因为 shopproduct 表存在 13 条记录,product 表存在 8 条记录,所以结果中就包含了 13 × 8 = 104 条记录.
可能这时会有读者想起前面我们提到过集合运算中的乘法会在本节中进行详细学习,这就是上面介绍的交叉连结.内连结是交叉连结的一部分,“内”也可以理解为“包含在交叉连结结果中的部分”.相反,外连结的“外”可以理解为“交叉连结结果之外的部分”.
交叉连结没有应用到实际业务之中的原因有两个.一是其结果没有实用价值,二是由于其结果行数太多,需要花费大量的运算时间和高性能设备的支持.

4.2.5.1 连结与笛卡儿积的关系

考察笛卡儿积和连结, 不难发现, 笛卡儿积可以视作一种特殊的连结(事实上笛卡儿积的语法也可以写作 CROSS JOIN), 这种连结的 ON 子句是一个恒为真的谓词.
反过来思考, 在对笛卡儿积进行适当的限制之后, 也就得到了内连结和外连结.
例如, 对于 shopproduct 表和 product 表, 首先建立笛卡尔乘积:

mysql> select sp.* , p.*
    -> from shopproduct as sp
    -> cross join product as p;
image.png

然后对上述笛卡尔乘积增加筛选条件 SP.product_id=P.product_id, 就得到了和内连结一致的结果:

mysql> select sp.* , p.*
    -> from shopproduct as sp
    -> cross join product as p
    -> where sp.product_id = p.product_id;
image.png

实际上, 正如书中所说, 上述写法中, 将 CROSS JOIN 改为逗号后, 正是内连结的旧式写法, 但在 ANSI 和 ISO 的 SQL-92 标准中, 已经将使用 INNER JION …ON… 的写法规定为标准写法, 因此极力推荐大家在平时写 SQL 查询时, 使用规范写法.

4.2.6 连结的特定语法和过时语法

在笛卡尔积的基础上, 我们增加一个 WHERE 子句, 将之前的连结条件作为筛选条件加进去, 我们会发现, 得到的结果恰好是直接使用内连接的结果.
试执行以下查询, 并将查询结果与内连结一节第一个例子的结果做对比.

mysql> select sp.shop_id, sp.shop_name, sp.product_id,
    -> p.product_name, p.sale_price
    -> from shopproduct as sp
    -> cross join product as p
    -> where sp.product_id = p.product_id;
image.png

我们发现, 这两个语句得到的结果是相同的.
之前我们学习的内连结和外连结的语法都符合标准 SQL 的规定,可以在所有 DBMS 中执行,因此大家可以放心使用. 但是如果大家之后从事系统开发工作, 或者阅读遗留SQL 查询语句的话,一定会碰到需要阅读他人写的代码并进行维护的情况,而那些使用特定和过时语法的程序就会成为我们的麻烦.

SQL 是一门特定语法及过时语法非常多的语言,虽然之前本书中也多次提及,但连结是其中特定语法的部分,现在还有不少年长的程序员和系统工程师仍在使用这些特定的语法.例如,将本节最初介绍的内连结的 SELECT 语句替换为过时语法的结果如下所示.
使用过时语法的内连结(结果与代码清单 7-9 相同)

SELECT SP.shop_id
       ,SP.shop_name
       ,SP.product_id
       ,P.product_name
       ,P.sale_price
  FROM shopproduct SP, product P
 WHERE SP.product_id = P.product_id
   AND SP.shop_id = '000A';

这样的书写方式所得到的结果与标准语法完全相同,并且这样的语法可以在所有的 DBMS 中执行,并不能算是特定的语法,只是过时了而已.
但是,由于这样的语法不仅过时,而且还存在很多其他的问题,因此不推荐大家使用,理由主要有以下三点:

  • 第一,使用这样的语法无法马上判断出到底是内连结还是外连结(又或者是其他种类的连结.
  • 第二,由于连结条件都写在 WHERE 子句之中,因此无法在短时间内分辨出哪部分是连结条件,哪部分是用来选取记录的限制条件.
  • 第三,我们不知道这样的语法到底还能使用多久.每个 DBMS 的开发者都会考虑放弃过时的语法,转而支持新的语法.虽然并不是马上就不能使用了,但那一天总会到来的.
    虽然这么说,但是现在使用这些过时语法编写的程序还有很多,到目前为止还都能正常执行.我想大家很可能会碰到这样的代码,因此还是希望大家能够了解这些知识.

你可能感兴趣的:(MYSQL|集合运算(下))