mysql学习—外连接的用法

外连接的用法

用外连接进行行列转换(行->列):制作交叉表

三种方法:
(1)外连接
C0为主表,依次对C1~C3进行外连接。如果某员工学习过某课程,课程出现姓名,否则为null。最后使用case表达式转换为’o’。

/* 水平展开求交叉表(1):使用外连接 */
SELECT C0.name,
       CASE WHEN C1.name IS NOT NULL THEN '○' ELSE NULL END AS "SQL入门",
       CASE WHEN C2.name IS NOT NULL THEN '○' ELSE NULL END AS "UNIX基础",
       CASE WHEN C3.name IS NOT NULL THEN '○' ELSE NULL END AS "Java中级"
  FROM  (SELECT DISTINCT name FROM  Courses) C0
    LEFT OUTER JOIN
    (SELECT name FROM Courses WHERE course = 'SQL入门' ) C1
    ON  C0.name = C1.name
      LEFT OUTER JOIN
        (SELECT name FROM Courses WHERE course = 'UNIX基础' ) C2
        ON  C0.name = C2.name
          LEFT OUTER JOIN
            (SELECT name FROM Courses WHERE course = 'Java中级' ) C3
            ON  C0.name = C3.name;

(2)标量子查询
一般情况,外连接都可以用标量子查询替代。

/* 水平展开(2):使用标量子查询 */
SELECT  C0.name,
  (SELECT '○'
     FROM Courses C1
    WHERE course = 'SQL入门'
      AND C1.name = C0.name) AS "SQL入门",
  (SELECT '○'
     FROM Courses C2
    WHERE course = 'UNIX基础'
      AND C2.name = C0.name) AS "UNIX基础",
  (SELECT '○'
     FROM Courses C3
    WHERE course = 'Java中级'
      AND C3.name = C0.name) AS "Java中级"
  FROM (SELECT DISTINCT name FROM Courses) C0;

(3)嵌套使用case表达式

/* 水平展开(3):嵌套使用CASE表达式 */
SELECT  name,
        CASE WHEN SUM(CASE WHEN course = 'SQL入门' THEN 1 ELSE NULL END) >= 1
             THEN '○' ELSE NULL END AS "SQL入门",
        CASE WHEN SUM(CASE WHEN course = 'UNIX基础' THEN 1 ELSE NULL END) >= 1
             THEN '○' ELSE NULL END AS "UNIX基础",
        CASE WHEN SUM(CASE WHEN course = 'Java中级' THEN 1 ELSE NULL END) >= 1
             THEN '○' ELSE NULL END AS "Java中级"
  FROM Courses
 GROUP BY name;

用外连接进行行列转换(2)

(列—>行):汇总重复项与一行
(1)使用union all
但是union all 不会排除重复的行,所以即使某行(employee)没有child数据也会出现三行数据。

/* 列数据转换成行数据:使用UNION ALL */
SELECT employee, child_1 AS child FROM Personnel
UNION ALL
SELECT employee, child_2 AS child FROM Personnel
UNION ALL
SELECT employee, child_3 AS child FROM Personnel;

(2)
a.先生成一个存储子女列表的视图(孩子主表)

/* 孩子主表 */
CREATE VIEW Children(child)
AS SELECT child_1 FROM Personnel
   UNION
   SELECT child_2 FROM Personnel
   UNION
   SELECT child_3 FROM Personnel;

b.以员工列表为主表进行外连接操作

/* 获取员工子女列表的SQL语句(没有孩子的员工也输出) */
SELECT EMP.employee, CHILDREN.child
  FROM Personnel EMP
       LEFT OUTER JOIN Children
    ON CHILDREN.child IN (EMP.child_1, EMP.child_2, EMP.child_3);

在交叉表里制作嵌套式表侧栏

表 TblPop 是一张按照县、年龄层级和性别统计的人口分布表,要求根据表TblPop生成交叉表"包含嵌套式表侧栏的统计表"。(年龄层级主表、性别主表、人口分布表blPop)
思路:目标表的侧栏是年龄层级和性别,所以TblAge和TblSex作为主表进行外连接。

/* 使用外连接生成嵌套式表侧栏:错误的SQL语句 */
SELECT MASTER1.age_class AS age_class,
       MASTER2.sex_cd AS sex_cd,
       DATA.pop_tohoku AS pop_tohoku,
       DATA.pop_kanto AS pop_kanto
  FROM (SELECT age_class, sex_cd,
               SUM(CASE WHEN pref_name IN ('青森', '秋田')
                        THEN population ELSE NULL END) AS pop_tohoku,
               SUM(CASE WHEN pref_name IN ('东京', '千叶')
                        THEN population ELSE NULL END) AS pop_kanto
          FROM TblPop
         GROUP BY age_class, sex_cd) DATA
        RIGHT OUTER JOIN TblAge MASTER1 /* 外连接1:和年龄层级主表进行外连接 */
           ON MASTER1.age_class = DATA.age_class
              RIGHT OUTER JOIN TblSex MASTER2 /* 外连接2:和性别主表进行外连接 */
                 ON MASTER2.sex_cd = DATA.sex_cd;

错误:返回结果没有出现年龄层级为2的行。
原因:表TblPop没有年龄层级为2的数据。与年龄层级主表外连接之后,结果包含年龄层级为2的数据。但是在表TblPop里,没有与之相应的性别信息(NULL)。因此与性别主表进行外连接,连接条件变为ON MASTER2.sex_cd=NULL,结果是unknow。所以,最终结果不会出现年龄层级为2的数据。

正确的SQL:调整成一次外连接

/* 使用外连接生成嵌套式表侧栏:正确的SQL语句 */
SELECT
  MASTER.age_class AS age_class,
  MASTER.sex_cd    AS sex_cd,
  DATA.pop_tohoku  AS pop_tohoku,
  DATA.pop_kanto   AS pop_kanto
FROM
  (SELECT
     age_class,
     sex_cd,
     SUM(CASE WHEN pref_name IN ('青森', '秋田')
              THEN population ELSE NULL END) AS pop_tohoku,
     SUM(CASE WHEN pref_name IN ('东京', '千叶')
              THEN population ELSE NULL END) AS pop_kanto
   FROM TblPop
   GROUP BY age_class, sex_cd) DATA
     RIGHT OUTER JOIN
       (SELECT age_class, sex_cd
          FROM TblAge 
                CROSS JOIN
               TblSex ) MASTER
     ON  MASTER.age_class = DATA.age_class
    AND  MASTER.sex_cd    = DATA.sex_cd;

技巧:a.对表TblAge 和表TblSex 进行交叉连接运算(CROSS JOIN),笛卡尔积。行数3*2=6;主表MASTER
b.对上表主表进行外连接
即 需要生成嵌套式表侧栏时,事先按照需要格式准备好主表。

作为乘法运算的连接

SQL里,交叉连接相当于乘法运算。

以商品主表和商品销售历史管理表为例
(1)通过在连接前聚合来创建一对一的关系
a.连接前按商品编号对销售记录表进行聚合,生成一张以
item_no为主键的临时视图
b.通过item_no列对商品主表和这个视图进行连接操作。成为了在主键上进行的一对一连接。

/* 解答(1):通过在连接前聚合来创建一对一的关系 */
SELECT I.item_no, SH.total_qty
  FROM Items I LEFT OUTER JOIN
       (SELECT item_no, SUM(quantity) AS total_qty
          FROM SalesHistory
         GROUP BY item_no) SH
    ON I.item_no = SH.item_no;

问题:
a.性能上,临时视图SH的数据需要临时存储在内存里。
b.SH不存在主键索引
改善:“把连接看作乘法运算”
主表Items和表SalesHistory 一对多的关系,连接操作之后行数不会增加。(像普通乘法里任意数乘以1后,结果不会变化)
(2)先进行一对多的连接再聚合

/* 解答(2):先进行一对多的连接再聚合 */
SELECT I.item_no, SUM(SH.quantity) AS total_qty
  FROM Items I LEFT OUTER JOIN SalesHistory SH
    ON I.item_no = SH.item_no /* 一对多的连接 */
 GROUP BY I.item_no;

全外连接

外连接三种类型
a.左外连接(left outer join)
b.右外连接 (right outer join)
c.全外连接(使用的数据库不支持,相当于union all)

/* 全外连接保留全部信息 */
SELECT COALESCE(A.id, B.id) AS id,
       A.name AS A_name,
       B.name AS B_name
FROM Class_A  A  FULL OUTER JOIN Class_B  B
  ON A.id = B.id;

COALESCE 函数功能:返回第一个非NULL的参数

用外连接进行集合运算

内连接相当于交集;外连接相当于并集
(1)差集的求法:
通过判断连接后的相关字段是否为NULL来求得差集
a.用外连接求差集:A-B

/* 用外连接求差集:A-B */
SELECT A.id AS id,  A.name AS A_name
  FROM Class_A  A LEFT OUTER JOIN Class_B B
    ON A.id = B.id
 WHERE B.name IS NULL;

b.用外连接求差集:B-A

/* 用外连接求差集:B-A */
SELECT B.id AS id, B.name AS B_name
  FROM Class_A  A  RIGHT OUTER JOIN Class_B B
    ON A.id = B.id
 WHERE A.name IS NULL;

注:not in 和 not exists 也可以达到效果

(2)用全外连接求异或集
三种方法:
1.(A UNION B ) EXCEPT (A INTERSECT B)
2.(A EXCEPT B) UNION (B EXCEPT A)
3.全外连接

/* 用全外连接求异或集 */
SELECT COALESCE(A.id, B.id) AS id,
       COALESCE(A.name , B.name ) AS name
  FROM Class_A  A  FULL OUTER JOIN Class_B  B
    ON A.id = B.id
 WHERE A.name IS NULL 
    OR B.name IS NULL;

(3)求集合的商
思路:用表Items减去表ShopItems里各个商铺的商品,如果结果为空集,则说明该店铺有表Items里的全部商品。
其中,“各个店铺”这一条件通过ON子句里的SI1.shop=SI2.shop这个关联子查询来表述的。

/* 用外连接进行关系除法运算:差集的应用 */
SELECT DISTINCT shop
  FROM ShopItems SI1
WHERE NOT EXISTS
      (SELECT I.item 
         FROM Items I LEFT OUTER JOIN ShopItems SI2
           ON SI1.shop = SI2.shop
          AND I.item   = SI2.item 
        WHERE SI2.item IS NULL) ;

注:MySQL 5.0不支持在关联条件里使用子查询的表明,所以该SQL不能正常执行
其他方法如上一节学习:
相比 having 好理解多了。。

/* 查询啤酒、纸尿裤和自行车同时在库的店铺:正确的SQL语句 */
SELECT SI.shop
  FROM ShopItems SI, Items I
 WHERE SI.item = I.item
 GROUP BY SI.shop
HAVING COUNT(SI.item) = (SELECT COUNT(item) FROM Items);

或者大大指导版本:

SELECT 
    shop
FROM
    items i
        JOIN
    ShopItems s
WHERE
    i.item = s.item
GROUP BY shop
HAVING COUNT(DISTINCT s.item) = 3

心得:
对于SQL求差集这节还是有点吃力。大大指导,心中有表,注重数据的样子才能融会贯通,没太搞懂,体会到具体说的什么意思。。。。

猜你喜欢

转载自blog.csdn.net/weixin_43387060/article/details/86029899