02 数据分析与SQL Lesson2 SQL JOIN
文章目录
1.视频:JOIN简介
在公司运营中产生的公司数据(包括采购数据、员工数据、库存等等)存放在一个Excel中没有太大用处。因为这样的表格包含信息过多,也很难为不同类型的数据建立行列和结构。而多个数据库能在各个表格中有序地组织数据。当需要多种类型数据是,可以整合表格,使用SQL的JOIN方法,按照每个表中的相同标识,轻松整合多个表格中的信息:
2.为何要将数据拆分为不同的表格?
等同于为什么要用JOIN的方式组合数据,原因如下:
- Orders and accounts are different types of objects
- This allows queries to execute more quickly
e.g.1 都在一个表,更新一个用户的address会造成更新多行记录
数据库规范化
数据库如何存储数据其实是数据库规范化的问题,数据库规范化需要考虑以下3个要点:
- 表格存储了逻辑分组的数据吗?
- 我能在一个位置进行更改,而不是在多个表格中对同一信息作出更改吗?
- 我能快速高效地访问和操纵数据吗?
- ![/扩展内容(墙)/]https://www.itprotoday.com/microsoft-sql-server/sql-design-why-you-need-database-normalization
5.解决方案:你的首个JOHN
第2问的答案如下面代码,大家注意以下几点:
- SELECT后面明确要选出的数据,用表.列来制定某个表中的列。如果这里用*则会选出结合之后所有数据列。
- FROM a JOIN b,就是把a和b结合成一个更大的数据。注意默认是INNER JOIN,可以指定其他对其方式。
- 结合的对应规则在 ON 这行指定。
SELECT orders.standard_qty, orders.gloss_qty,
orders.poster_qty, accounts.website,
accounts.primary_poc
FROM orders
JOIN accounts
ON orders.account_id = accounts.id
8.练习:主外键关系
本部分练习2不选择“表格中可能有多个主键”,因为从课程的角度来讲,虽然有这个情况出现,但是这样并不很恰当。
crow-foot(鱼尾纹)标记的是外键。
10.视频:别名
可以这样:
FROM tablename AS t1
JOIN tablename2 AS t2
也可以省略AS,这样:
FROM tablename t1
JOIN tablename2 t2
还可以把一个运算结果赋予别名,比如下面这个 col1 + col2 命名为 total:
SELECT col1 + col2 total, col3
14.练习:JOHN问题(第一部分)
- 为与客户名称 Walmart 相关的所有 web_events 创建一个表格。表格应该包含三列:primary_poc、事件时间和每个事件的渠道。此外,你可以选择添加第四列,确保仅选中了 Walmart 事件。如下代码:
- SELECT 语句根据要求选择了4列,并且使用了别名a,w
- FROM和JOIN 定义了选择范围和别名
- WHERE 定义了过滤方法
SELECT a.primary_poc, w.occurred_at, w.channel, a.name
FROM web_events w
JOIN accounts a
ON w.account_id = a.id
WHERE a.name = 'Walmart';
- 为每个销售代表对应的区域以及相关的客户创建一个表格,最终表格应该包含三列:区域名称、销售代表名称,以及客户名称。根据客户名称按字母顺序 (A-Z) 排序。
SELECT r.name region, s.name rep, a.name account
FROM sales_reps s
JOIN region r
ON s.region_id = r.id
JOIN accounts a
ON a.sales_rep_id = s.id
ORDER BY a.name;
- 提供每个订单的每个区域名称,以及客户名称和订单的单价 (total_amt_usd/total)。最终表格应该包含三列:区域名称、客户名称和单价。少数几个客户的总订单数为 0,因此我除以的是 (total + 0.01) 以确保没有除以 0。
- o.total_amt_usd/o.total 是用订单总价除以订单数量,所以得出了单价,并且别名为 unit_price
- 但是 o.total 有为0的行,所以会报错。为了能够完成,将total加0.01可以保证所有行都能计算出结果:o.total_amt_usd/(o.total + 0.01)
- 这里只是个简化,在处理大量数据的时候,要先处理这些0值或空值,这么处理会有偏差。一旦amt有数量,total为0,会产生100倍amt的异常值(这个后续会逐渐解决)
SELECT r.name region, a.name account,
o.total_amt_usd/(o.total + 0.01) unit_price
FROM region r
JOIN sales_reps s
ON s.region_id = r.id
JOIN accounts a
ON a.sales_rep_id = s.id
JOIN orders o
ON o.account_id = a.id;
13.视频:其他JOIN的目的
当我们的情况不满足于一对一由id把表连接起来时,我们有另外三种JOIN方式:OUTER JOIN,LEFT JOIN,RIGHT JOIN。这种方式适用于解决类似多对多的问题,要知道关系型数据库是不能建立多对多的关系的:/关系型数据库为什么不能多对多/,简单的解释摘录如下:
Think about a simple relationship like the one between Authors and Books. An author can write many books. A book could have many authors. Now, without a bridge table to resolve the many-to-many relationship, what would the alternative be? You’d have to add multiple Author_ID columns to the Books table, one for each author. But how many do you add? 2? 3? 10? However many you choose, you’ll probably end up with a lot of sparse rows where many of the Author_ID values are NULL and there’s a good chance that you’ll run across a case where you need “just one more.” So then you’re either constantly modifying the schema to try to accommodate or you’re imposing some artificial restriction (“no book can have more than 3 authors”) to force things to fit.
14.视频:LEFT JOIN 和 RIGHT JOIN
先是再复习下INNER JOIN(只写JOIN就是INNER JOIN),就是图中那个白色重复的地方,左右两个表在ON的表达上全部都有的才会筛选出来:
接下来看这个LEFT JOIN的例子(其实和INNER JOIN的结果相同):
最后看下RIGHT JOIN的例子,这里就会将右侧有,左侧没有的也筛选出来,筛选和结果如***意结果中totla列为空的是Null数据类型,后续会解释):
最后要提示一点,LEFT JOIN 和 RIGHT JOIN 是可以互换的,如下图:
- 这也是为什么很少见到 RIGHT JOIN的原因
- LEFT/RIGHT OUTER JOIN 和 LEFT/RIGHT JOIN 相同
- FULL OUTER JOIN 和 OUTER JOIN 相同
18.视频:JOIN 和过滤
这节通过了一个例子说明了:
- ON 和 WHERE:ON 和 WHERE 的筛选顺序是先进行ON的过滤在进行WHERE的过滤:logic in the on clause reduces the rows before combining the tables; logic in the where clause occurs after the join occurs.
- ON 和 AND:如果将 WHRERE 变为 AND,则 AND 的内容就变成了 ON 的一部分,这样能在连接前有效地对右边表格进行预筛选。
- !需要注意!这个例子使用的是 LEFT JOIN,这一点非常重要,如果是 JOIN 的话,只用 ON 就会过滤掉大量的数据。
20.解决方案:最后的检测
- 为每个销售代表对应的区域以及相关的客户创建一个表格,这次仅针对 Midwest 区域。最终表格应该包含三列:区域名称、销售代表姓名,以及客户名称。根据客户名称按字母顺序 (A-Z) 排序。
- 输出的3列分别来自3个表
- 所以是 FROM,JOIN,JOIN
- 每个 JOIN 还要定义 ON
- 注意 ON 的表达习惯先是刚刚 JOIN 的,之后是对应同时有对应关系的表(比如例子中的 sales_reps 表,分别和region_id 和 id 做了对应)
SELECT r.name region, s.name rep, a.name account
FROM sales_reps s
JOIN region r
ON s.region_id = r.id
JOIN accounts a
ON a.sales_rep_id = s.id
WHERE r.name = 'Midwest'
ORDER BY a.name;
- 为每个销售代表对应的区域以及相关的客户创建一个表格,这次仅针对 Midwest 区域,并且销售代表的名字以 S 开头。最终表格应该包含三列:区域名称、销售代表姓名,以及客户名称。根据客户名称按字母顺序 (A-Z) 排序。
- 这里区别是对 WHERE 做了符合条件,增加了 AND 过滤一个 LIKE 条件
- S% 是说S开头,后面不限
- 最后增加了个排序 ORDER BY
SELECT r.name region, s.name rep, a.name account
FROM sales_reps s
JOIN region r
ON s.region_id = r.id
JOIN accounts a
ON a.sales_rep_id = s.id
WHERE r.name = 'Midwest' AND s.name LIKE 'S%'
ORDER BY a.name;
- 为每个销售代表对应的区域以及相关的客户创建一个表格,这次仅针对 Midwest 区域,并且销售代表的姓以 K 开头。最终表格应该包含三列:区域名称、销售代表姓名,以及客户名称。根据客户名称按字母顺序 (A-Z) 排序。
- 这里注意后面名和姓的区别,要求是K开头的姓
- 所以第一个%代表任意名字,空格后的K%是代表对姓的要求
SELECT r.name region, s.name rep, a.name account
FROM sales_reps s
JOIN region r
ON s.region_id = r.id
JOIN accounts a
ON a.sales_rep_id = s.id
WHERE r.name = 'Midwest' AND s.name LIKE '% K%'
ORDER BY a.name;
- 提供每个订单的每个区域的名称,以及客户名称和所支付的单价 (total_amt_usd/total)。但是,只针对标准订单数量超过 100 的情况提供结果。最终表格应该包含三列:区域名称、客户名称和单价。为了避免除以 0 个订单,这里可以在分母上加上 0.01,即:(total_amt_usd/(total+0.01))。
- total+0.01这个上面说过了
- 这里 WHERE 的条件是一个判断
SELECT r.name region, a.name account, o.total_amt_usd/(o.total + 0.01) unit_price
FROM region r
JOIN sales_reps s
ON s.region_id = r.id
JOIN accounts a
ON a.sales_rep_id = s.id
JOIN orders o
ON o.account_id = a.id
WHERE o.standard_qty > 100;
- 提供每个订单的每个区域的名称,以及客户名称和所支付的单价 (total_amt_usd/total)。但是,只针对标准订单数量超过 100 且广告纸数量超过 50 的情况提供结果。最终表格应该包含三列:区域名称、客户名称和单价。按照最高的单价在最前排序。为了避免除以 0 个订单,这里可以在分母上加上 0.01,即:(total_amt_usd/(total+0.01))。
- DESC 进行反向排序
SELECT r.name region, a.name account, o.total_amt_usd/(o.total + 0.01) unit_price
FROM region r
JOIN sales_reps s
ON s.region_id = r.id
JOIN accounts a
ON a.sales_rep_id = s.id
JOIN orders o
ON o.account_id = a.id
WHERE o.standard_qty > 100 AND o.poster_qty > 50
ORDER BY unit_price DESC;
- account id 为 1001 的客户使用了哪些不同的渠道。最终表格应该包含 2 列:客户名称和不同的渠道。你可以尝试使用 SELECT DISTINCT 使结果仅显示唯一的值。
- 注意 SELECT DISTINCT 用法
- 这里可以不使用 RIGHT JOIN,因为过滤出来的id是必须存在的。使用 JOIN 的结果相同
SELECT DISTINCT a.id, w.channel
FROM accounts a
RIGHT JOIN web_events w
ON a.id = w.account_id
WHERE a.id = '1001';
- 找出发生在 2015 年的所有订单。最终表格应该包含 4 列:occurred_at、account name、order total 和 order total_amt_usd。
- WHERE 中的 BETWEEN 条件
SELECT w.occurred_at, a.name, o.total, o.total_amt_usd
FROM accounts a
JOIN orders o
ON o.account_id = a.id
JOIN web_events w
ON a.id = w.account_id
WHERE w.occurred_at BETWEEN '01-01-2015' AND '01-01-2016'
ORDER BY w.occurred_at DESC;
Plus Join 小结
在SQL的选修课程中讲了Join,大家可以参考下面这个图方便理解:
如果学完了课程,也可看这个SQlite的Join做复习:/SQLiteJohn/