目录
SQL集合运算
一、表的加减法
表以及查询结果可以视为集合,可以使用集合的运算
- UNION:并
- INTERSECT:交
- EXCEPT:补
1.1 表的加法-UNION
1.1.1 UNION
进行并集处理,会除去重复的记录
注意事项:
- 名称要一一对应,保持位置和字段名的一致性
- UNION会除去重复的记录
SELECT product_id, product_name
FROM product
UNION
SELECT product_id,product_name
FROM product_copy1
练习1:假设连锁店想要增加毛利率超过50%或者售价低于800的货物的存货量,使用UNION对满足上述条件的商品的查询结果求并集
思路:先按照两个条件分别查询出数据,之后再将两张表使用UNION拼接起来,因为没有限制具体的字段,可以使用*来
SELECT *
FROM product
WHERE sale_price>1.5*purchase_price
UNION
SELECT *
FROM product_copy1
WHERE sale_price<800
不使用UNION可以使用OR将两个条件组合
SELECT *
FROM product
WHERE sale_price>1.5*purchase_price or sale_price<800
对于同一张表的UNION和OR基本等价,但是在两张表的拼接中,需要使用UNION
练习1:使用UNION或者OR找出毛利率不足30%或毛利率未知的商品
思考:参考两个不同语句的结果,我们会发现OR谓语的查询结果的顺序是按照原表的顺序去查询出符合条件的值,会发现product_id是顺序的;但是UNION的使用是将两个查询结果拼接出来的,上面是第一次查询的结果,下面的第二次查询的结果。
SELECT *
FROM product
WHERE sale_price / purchase_price < 1.3
OR sale_price / purchase_price IS NULL;
SELECT *
FROM product
WHERE sale_price / purchase_price < 1.3
UNION
SELECT *
FROM product_copy1
WHERE sale_price / purchase_price IS NULL
练习2:找出product和product_copy1中售价高于500的商品的基本信息
思考:两张表中查询的结果一致,UNION会去除重复的数据,因此我们会发现只有一组的结果展示
SELECT *
FROM product
WHERE sale_price>500
UNION
SELECT *
FROM product_copy1
WHERE sale_price>500
1.1.2 UNION ALL
不去重的并集
练习1:找到product表中利润低于50%或者售价低于1000的商品提价
SELECT *
FROM product
WHERE sale_price < 1000
UNION ALL
SELECT *
FROM product
WHERE sale_price < 1.5*purchase_price
1.1.3 bag模型和set模型
set:无序、不可重复的集合
bag:无序、可重复的集合,与set类似的数学结构,允许存在重复元素,如果同一元素被加入多次,则袋子里就有多个元素
两个bag的并运算:1. 元素是否至少在一个bag里出现过;2.该元素在两个bag中的最大出现次数。A={1,1,1,2,3,5,7},B={1,1,2,2,4,6,8},A和B的并等于{1,1,1,2,2,3,4,5,6,7,8}
1.1.4 隐式类型转换
对于数据类型不完全相同的列,可以通过隐式类型转换将两个不同类型的列在同一列显示
SELECT product_id,
product_name,
'1'
FROM product
UNION
SELECT product_id,
product_name,
sale_price
FROM product_copy1
练习1:使用SYSDATE()函数可以返回当前日期时间,试测试该数据类型和数值,字符串等类型的兼容性
SELECT SYSDATE(),SYSDATE(),SYSDATE()
UNION
SELECT 'chars',123,null
时间日期类型和字符串,数值和缺失值均能兼容
1.2 MySQL不支持交运算INTERSECT
bag的交运算:
1. 该元素是否同时属于两个bag;2. 该元素在两个bag中的最小出现次数
A={1,1,1,2,3,5,7}, B={1,1,2,2,4,6,8} A和B交运算的结果:{1,1,2}
INTERSECT和AND
对于同一个表的两个查询结果,交等价于使用AND
1.3 差集,补集与表的减法
A-B:将A中同时属于B的元素减掉
MySQL还不支持EXCEPT运算,可以使用NOT IN
练习1:找出只存在product,不存在product2的商品
SELECT *
FROM product
WHERE product_id NOT IN (SELECT product_id
FROM product_copy1)
练习2:使用NOT谓语进行减法运算,求出product表中,售价高于2000,但利润低于30%的商品,
思路:先求出售价高于2000的,再将里利润高于30%的排除,等价于使用AND
SELECT *
FROM product
WHERE sale_price >2000
AND product_id NOT IN (SELECT product_id
FROM product_copy1
WHERE sale_price>1.3*purchase_price)
SELECT *
FROM product
WHERE sale_price >2000
AND sale_price<1.3*purchase_price
EXCEPT ALL 与bag的差
差运算:
1. 该元素是否属于作为被减数的bag
2. 该元素在两个bag中的出现次数,差为0或者负数则不出现
A ={1,1,1,2,3,5,7} B={1,1,2,2,4,6,8} A-B为{1,3,5,7}
1.4 对称差
A和B的对称差表示仅属于A或者金属与B的元素构成的集合。对称差的运算:并集减去交集或者(A-B)并(B-A)
练习:使用product和product2的对称差查询哪些商品只在其中一张表
SELECT *
FROM product
WHERE product_id NOT IN (SELECT product_id FROM product_copy1)
UNION
SELECT *
FROM product_copy1
WHERE product_id NOT IN (SELECT product_id FROM product)
1.5 助并集和差集实现交集运算
两个集合的并去掉两个集合的对称差就是交,我们可以理解为在A并且在B
SELECT *
FROM product
WHERE product_id IN (SELECT product_id
FROM product_copy1
)
二、连接(JOIN)
为表增加列,使用某种关联条件,一般使用相等判断谓语‘=’,将其他表中的列添加古来。
1. 内连接(INNER JOIN)
FROM <tb_1> INNER JOIN <tb_2> ON <conditions>
找出东京商店里的衣服类商品的商品名称,商品价格,商品种类,商品数量信息。
思路:在product表中我们有商品编码,商品名称,商品价格,商品种类,在product表中我们有商店名称,商品编码,商品数量。我们发现两张表都有商品编码,所以我们可以使用商品编码作为条件,将两张表连结起来,因为我们要的是东京商店的商品,因此product_shop的值是我们首要的,使用product_shop去内连接product,之后再筛选出我们需要的数据
SELECT sp.quantity,
p.product_name,
p.sale_price
FROM shop_product sp
INNER JOIN product p
ON sp.product_id = p.product_id
WHERE p.product_type='衣服'
AND sp.shop_id='000A'
要点:
1. 进行连结时需要在FROM子句中使用多张表
2. 使用ON子句指定连结调节,多条件使用AND或者OR拼接
3. SELECT子句最好使用 <表名.列名>,当出现重复列名,没有表名前缀会报错;如果不是重复的字段,也可以不写
4. WHERE的顺序:FROM子句>WHERE子句>SELECT子句
5. 我们可以对每一张表使用WHERE,然后再将这几张表拼接起来
6. 使用INNER JOIN连接可以求交集
练习:分别使用连接两个子查询和不使用子查询的方式,找出东京商店里,售价低于2000的商品信息(一般不建议使用过多的子查询,影响性能)
---不使用子查询
SELECT *
FROM shop_product sp
INNER JOIN product p
ON sp.product_id = p.product_id
WHERE shop_name = '东京'
AND sale_price < 2000
---使用子查询
SELECT * FROM
(SELECT *
FROM shop_product
WHERE shop_name = '东京'
)sp
INNER JOIN
(SELECT *
FROM product
WHERE sale_price < 2000
)p
ON sp.product_id = p.product_id
结合GROUP BY
练习:每个商店,售价最高的商品的售价分别是多少
SELECT sp.shop_id,
sp.shop_name,
max(p.sale_price) max_price
FROM shop_product sp
INNER JOIN product p
ON sp.product_id = p.product_id
GROUP BY sp.shop_id,
sp.shop_name
思考题:在上面的基础上找出售价最高的商品
思路:我们需要查找到在每个商店的售价最高的商品,然后作为筛选条件,将我们需要的字段查询出来,此处使用了CONCAT,将shop_id和sale_price拼接为一个字段,在设置WHERE条件时,才可以一次使用
SELECT sp.shop_id,
sp.shop_name,
p.product_id,
p.product_name,
p.sale_price
FROM shop_product sp
INNER JOIN product p
ON sp.product_id = p.product_id
WHERE CONCAT(sp.shop_id,p.sale_price) IN
( SELECT CONCAT(sp.shop_id,MAX(p.sale_price))
FROM shop_product sp
INNER JOIN product p
ON sp.product_id = p.product_id
GROUP BY shop_id
) ---查找出每个商店的最高价
练习:每类商品中售价最高的商品在哪些商店有售?
思路:与上面是一样的思路,先找到各类商品中售价最高的商品和价格的组合,然后再在拼接条件中,筛选出想要的字段
SELECT p.product_id,
p.product_name,
sp.shop_id,
sp.shop_name,
p.sale_price
FROM shop_product sp
INNER JOIN product p
ON sp.product_id = p.product_id
WHERE CONCAT(p.product_type,p.sale_price) IN
( SELECT CONCAT(product_type,MAX(sale_price))
FROM product
GROUP BY product_type
)
内连接与关联子查询
练习:分别使用内连接和关联子查询查询每一类商品中售价最高的商品
---关联子查询
SELECT product_type,
product_name,
sale_price
FROM product p1
WHERE sale_price = (SELECT MAX(sale_price)
FROM product p2
WHERE p1.product_type = p2.product_type
GROUP BY product_type)
---内连接
SELECT p1.product_type,
p1.product_name,
p1.sale_price
FROM product p1
INNER JOIN
(SELECT product_type,
MAX(sale_price) sale_price
FROM product
GROUP BY product_type) p2
ON p1.product_type = p2.product_type
AND p1.sale_price = p2.sale_price
2. 自连接(SELF JOIN)
一张表与自身连接,称为自连接。自连接可以是内连接,也可以是外连接
3. 自然连接(NATURAL JOIN)
是内连接的特例---当两张表进行自然连接时,会按照两个表中都包含的列名来等值内连接,此时无需使用ON来指定
SELECT *
FROM shop_product
NATURAL JOIN product
使用INNER JOIN 模拟NATURAL JOIN
SELECT *
FROM shop_product sp
INNER JOIN product p
ON sp.product_id = p.product_id
注意:使用NATURAL JOIN 时,如果相同列名中包含NULL值,这不会被获取出来,需要将含有NULL值的列排除后再使用NATURAL JOIN
2.4 外连接(OUTER JOIN)
内连接会丢弃两张表中不满足ON条件的行,外连接会根据外连接的种类有选择地保留无法匹配的行
外连接形式:左连接、右连接、全外连接
---左连接
FROM <tb_1> LEFT OUTER JOIN <tb_2> ON <conditions>
---右连接
FROM <tb_1> RIGHT OUTER JOIN <tb_2> ON <conditions>
---全连接
FROM <tb_1> FULL OUTER JOIN <tb_2> ON <conditions>
2.4.1 左连接和右连接
左连接和右连接本质上是一样的
注意:
1. 选取出单张表中的全部内容,谁为主表选取谁的全部内容
2. 使用LEFT、RIGHT指定主表;LEFT前的为主表,RIGHT后的为主表
练习 :使用外连接从shop_product表和product表中找出某个商店库存少于50的商品及对应的商店
SELECT *
FROM product p
LEFT JOIN shop_product sp
ON p.product_id = sp.product_id
WHERE quantity < 50
希望的结果为:
SELECT *
FROM product p
LEFT JOIN shop_product sp
ON p.product_id = sp.product_id
WHERE quantity < 50
上面得到的结果没有将数量为NULL的值显示出来,因为NULL值无法通过运算符显示,因此我们可以先将小于50的产品找到,接着再进行LEFT JOIN
SELECT *
FROM product p
LEFT JOIN
(SELECT * FROM shop_product sp WHERE quantity < 50) sp
ON p.product_id = sp.product_id
2.4.2 全外连接
Mysql不支持使用全外连接,我们可以通过左连接和右连接,最后通过UNION来实现
3. 多表连接
创建inventory_product表
CREATE TABLE inventory_product
(inventory_id VARCHAR(4) NOT NULL,
product_id VARCHAR(4) NOT NULL,
inventory_quantity INT NOT NULL,
PRIMARY KEY(inventory_id,product_id));
INSERT INTO inventory_product VALUES('P001','0001',0),
('P001','0002',120),
('P001','0003',200),
('P001','0004',3),
('P001','0005',0),
('P001','0006',99),
('P001','0007',999),
('P001','0008',200),
('P002','0001',10),
('P002','0002',25),
('P002','0003',34),
('P002','0004',19),
('P002','0005',99),
('P002','0006',0),
('P002','0007',0),
('P002','0008',18);
使用内连接查找每个商店都有哪些商品,每种商品的库存总量分别是多少
SELECT * FROM
shop_product sp
INNER JOIN product p
ON sp.product_id = p.product_id
INNER JOIN inventory_product ip
ON sp.product_id = ip.product_id
4. ON子句进阶-非等值连接
包含比较运算符(<,<=,>,>=,BETWEEN)和谓语运算(LIKE,IN,NOT)
练习:对每一种商品找出售价不低于它的所有商品,然后对售价不低于它的商品使用COUNT函数计数。
SELECT product_id,
product_name,
sale_price,
COUNT(p2_id) sum_p2
FROM (SELECT p1.product_id,
p1.product_name,
p1.sale_price,
p2.product_id p2_id,
p2.product_name p2_name,
p2.sale_price p2_price
FROM product p1
LEFT JOIN product p2
ON p1.sale_price <= p2.sale_price) p12
GROUP BY product_id,
product_name,
sale_price
ORDER BY sum_p2 ASC
- COUNT函数的参数是列名,会忽略列中的缺失值,参数为*时不忽略缺失值
练习:按照商品的售价从低到高,对售价进行累计求和
思路:首先需要找到比该商品价格低的所有产品,然后再使用分组求和的方法拿掉低商品维度,实现该商品的汇总求和,则得到我们想要的结果
SELECT product_id,
product_name,
sale_price,
SUM(sale_price)
FROM (SELECT p1.product_id,
p1.product_name,
p1.sale_price,
p2.sale_price p2_price
FROM product p1
LEFT JOIN product p2
ON p1.sale_price >= p2.sale_price ) p12
GROUP BY product_id,
product_name,
sale_price
ORDER BY sale_price
观察结果会发现出现两个500时,数据重复了,可以将情况拆分为两种,一种是p1.sale_price小于p2.sale_price时,不需要处理,直接累加;一种是p1.sale_price与p2.sale_price相等时,需要使用product_id来辅助排序,将重复出现的值进行排序,只获取到一次
SELECT product_id,
product_name,
sale_price,
SUM(p2_price) cumsum_price
FROM (SELECT p1.product_id,
p1.product_name,
p1.sale_price,
p2.sale_price p2_price
FROM product p1
LEFT JOIN product 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) p12
GROUP BY product_id,
product_name,
sale_price
ORDER BY sale_price,cumsum_price
练习:将上述查询改为关联子查询
思路:根据上面的查询,我们基本可以限制查询条件,然后考虑通过子查询将计算出来的结果拼接在product表上。
SELECT p1.product_id,
p1.product_name,
sale_price,
(SELECT SUM(sale_price) FROM product p2 WHERE 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) sale_sum
FROM product p1
ORDER BY sale_sum
5. 交叉连接-CROSS JOIN(笛卡尔积)
去掉ON子句,就是交叉连接
SELECT *
FROM shop_product
CROSS JOIN product
SELECT *
FROM shop_product , product
笛卡尔积的结果是两个表的记录数相乘后得到的结果就是最终的记录数
5.1 连接和笛卡尔积
笛卡尔积限制条件可以变为内连接和外连接
SELECT *
FROM shop_product sp , product p
WHERE sp.product_id = p.product_id
上面语句得到的就是sp.product_id与p.product_id相等的结果
注意:虽然可以使用where来等价于内连接和外连接,但是不建议使用,因为:1. 无法马上判断出是内连接还是外连接;2. 连接条件写在where之后,无法立刻判断出限制条件和连接条件;3.此语法是过时的语法,建议使用连接条件
总结
1. 子查询放在SELECT语句中会有惊喜的结果
2. 注意UNION、UNION ALL 、JOIN的使用区别
3. 注意LEFT JOIN 、RIGHT JOIN 的顺序和结果
4. 使用NATURAL JOIN有的时候也会有意外的收获