目录
- 聚合简介
- NULL简介
- COUNT
- SUM
- MIN和MAX
- AVG
- GROUP BY - 单列
- GROUP BY - 多列
- DISTINCT
- HAVING
- DATE_TRUNC
- DATE_PART
- CASE
- CASE与聚合
正文
聚合简介
通常,JOIN语句和WHERE筛选语句, 对于查询行结果很有用, 比如说我们想要查看给定销售人员的个人订单, 以评估它们在给定月份的绩效, 但有时候, 行数据太多了, 最终效果不如聚合数据. 比如计算每个月每个地区的总订单数, 这时候就需要使用聚合. SQL聚合函数总体和Excel聚合函数类似, 而且针对的是列, 不是行, 比如你可以对纸张的总供应量求和.
NULL简介
NULL 是一种数据类型,表示 SQL 中没有数据, 与零不同. 对于Parch&Posey公司, 零意味着没有卖出任何纸张. 空值意味着根本没有尝试过销售, 空值可能是多种原因, 可能是数据设计的一部分, 也可能仅仅是数据锁定不佳的结果.
为了更深入的了解空值, 选取accounts账户ID在1500与1600之间的记录
假设你是Parch&Posey的销售经理, 你可能想了解哪些账户的主要联系人为Null
要找出这些账户, 你必须在WHERE子句中使用一些特殊语法, IS NULL
, 比如针对primary_poc
一列
SELECT *
FROM accounts
WHERE primary_poc IS NULL
结果显示, 有不少账户现在没有联系人
如果你想找到和现在的结果集相反的结果, 使用IS NOT NULL
在以下两种常见情况下,你可能会遇到 NULL:
在执行 LEFT JOIN 或 RIGHT JOIN 时,NULL 经常会发生。左侧表格中的某些行在做连接时与右侧表格中的行如果不匹配,这些行在结果集中就会包含一些 NULL 值。
NULL 也可能是因为数据库中缺失数据。
COUNT
COUNT函数会返回, 所有包含非空值数据行的计数, 整行都是空值的情况很少见, 所有当执行COUNT(*), 一般和表格行数是相等的
计算表格中的行数
以下是计算 accounts 表格中的行数示例:
SELECT COUNT(*)
FROM accounts;
还可以给count添加别名, 这样易于阅读
SELECT COUNT(*) AS count_accounts
FROM accounts;
也可以轻松地选择某一列来放置聚合函数:
SELECT COUNT(accounts.id)
FROM accounts;
用来计算单个列中非空值记录的数量
为了说明这一点, 我们来看一下账户表
将*
换为id
, 我们可以看到ID列中有多少非空值记录,由于id
列没有空值, 所以它返回与COUNT(*)
相同的结果.
如果使用已知的包含一些空值primary_poc列来尝试, 这一次我们得到了的结果比之前小9.
SUM
假设你是Parch&Posey团队的营运经理, 你在制定库存计划, 想知道每种类型的纸张应生产多少?
一种好的开始方式是, 先合计每种纸张类型的总销量, 再进行相互间的对比, 我们将通过SUM来完成该操作, 它的用法和COUNT类似, 只是你需要说明具体列的名称, 而不是使用*
.
SELECT SUM(standard_qty) AS standard,
SUM(gloss_qty) AS gloss,
SUM(poster_qty) AS poster
FROM orders
结果显示标准纸张的销量比两种非标准纸类销量之和更多.
与COUNT不同, SUM只用于包含值(numerical)的列, 并且你不需要考虑空值的问题. 因为SUM函数将空值当作0来处理
注意事项:聚合函数只能垂直聚合,即聚合列的值。
练习
1.算出 orders 表格中的 poster_qty 纸张总订单量。
SELECT SUM(poster_qty) AS poster
FROM orders;
2.算出 orders 表格中 standard_qty 纸张的总订单量。
SELECT SUM(standard_qty) AS standard
FROM orders;
3.根据 orders 表格中的 total_amt_usd 得出总销售额。
SELECT SUM(total_amt_usd) AS total_usd
FROM orders;
4.算出 orders 表格中每个订单在 standard 和 gloss 纸张上消费的数额。结果应该是表格中每个订单的金额。
SELECT standard_qty + gloss_qty AS total_standard_gloss
FROM orders;
5.每个订单的 price/standard_qty 纸张各不相同。我想得出 orders 表格中标准纸张的平均单价。
SELECT SUM(standard_amt_usd)/SUM(standard_qty) AS standard_price_per_unit
FROM orders;
MIN和MAX
MIN和MAX的语法与SUM和COUNT相似
SELECT MIN(standard_qty) AS standard_min,
MIN(gloss_qty) AS gloss_min,
MIN(poster_qty) AS poster_min,
MAX(standard_qty) AS standard_max,
MAX(gloss_qty) AS gloss_max,
MAX(poster_qty) AS poster_max
FROM orders;
注意,MIN 和 MAX 聚合函数也会忽略 NULL 值。
从功能上来说,MIN 和 MAX 与 COUNT 相似,它们都可以用在非数字列上。MIN 将返回最小的数字、最早的日期或按字母表排序的最之前的非数字值,具体取决于列类型。MAX 则正好相反,返回的是最大的数字、最近的日期,或与“Z”最接近(按字母表顺序排列)的非数字值。
AVG
通过MIN和MAX, 了解到最受欢迎的纸张类型, 以及在某些时间满足的最大订单量, 如果想知道平均订单量, 我们使用AVG函数(average), 它与其他聚合函数的语法相似.
SELECT AVG(standard_qty) AS standard_avg,
AVG(gloss_qty) AS gloss_avg,
AVG(poster_qty) AS poster_avg
FROM orders;
AVG 返回的是数据的平均值,即列中所有的值之和除以列中值的数量。该聚合函数同样会忽略分子和分母中的 NULL 值。
如果你想将 NULL 当做零,则需要使用 SUM 和 COUNT, 先求和,然后用和除以计数。但是,如果 NULL 值真的只是代表单元格的未知值,那么这么做可能不太合适。
练习
1.最早的订单下于何时?
SELECT MIN(occurred_at) AS time
FROM orders;
2.尝试执行和第一个问题一样的查询,但是不使用聚合函数。
SELECT occurred_at AS time
FROM orders
ORDER BY occurred_at
LIMIT 1;
3.最近的 web_event 发生在什么时候?
SELECT MAX(occurred_at) AS time
FROM web_events;
4.尝试以另一种方式执行上个问题的查询,不使用聚合函数。
SELECT occurred_at AS time
FROM web_events
ORDER BY occurred_at DESC
LIMIT 1;
5.算出每个订单在每种纸张上消费的平均 (AVERAGE) 金额,以及每个订单针对每种纸张购买的平均数量。最终答案应该有 6 个值,每个纸张类型平均销量对应一个值,以及平均数量对应一个值。
SELECT AVG(standard_amt_usd) AS standard_usd_avg,
AVG(gloss_amt_usd) AS gloss_usd_avg,
AVG(poster_amt_usd) AS poster_usd_avg,
AVG(standard_qty) AS standard_qty_avg,
AVG(gloss_qty) AS gloss_qty_avg,
AVG(poster_qty) AS poster_qty_avg
FROM orders;
6.看过视频后,你可能对如何计算中位数感兴趣。虽然这已经超出了目前我们所学的范围,但请尝试探索这个问题:对于所有订单(orders)数据,其total_usd字段的中位数是多少?请注意,构建一个此问题的通用解决方案已经超出了目前所学的课程范围,但我们可以硬写出以下这段代码:
SELECT *
FROM (SELECT total_amt_usd
FROM orders
ORDER BY total_amt_usd
LIMIT 3457) AS Table1
ORDER BY total_amt_usd DESC
LIMIT 2;
因为订单一共有6912个,因此我们需要第3456和第3457个订单(按total_amt_usd排序)的total_amt_usd字段的平均值。这样就能得出中位数结果,为2482.855。这显然不是一个好办法。如果我们有了新订单,再次计算时就必须修改LIMIT。SQL实际上并不会为我们计算中位数。以上代码使用了一个子查询(SUBQUERY),但你可以使用任何方法找到需要的两个值,然后再求平均即可得到中位数。
GROUP BY - 单列
作为一名销售经理, 你可能需要计算每个账户每种纸张的总销量, 即依据账户ID创建各自总和的集合.
通过GROUP BY你可以创建分组, 在聚合时相互独立, 也就是说, GROUP BY 可以抓取单个账户的数据之和, 而不是整个数据集.
SELECT account_id,
SUM(standard_qty) AS standard_sum,
SUM(gloss_qty) AS gloss_sum,
SUM(poster_qty) AS poster_sum
FROM orders
GROUP BY account_id
ORDER BY account_id
如果SELECT语句中有字段没有进行聚合, 则应该将其放入GROUP BY子句, 如果有一列内容没有进行聚合且不在GROUP BY语句中, 则会返回错误结果, 这一点很重要, 时正确使用GROUP BY语句的关键
主要知识点概括如下:
GROUP BY 可以用来在数据子集中聚合数据。例如,不同客户、不同区域或不同销售代表分组。
SELECT 语句中的任何一列如果不在聚合函数中,则必须在 GROUP BY 条件中。
GROUP BY 始终在 WHERE 和 ORDER BY 之间。
ORDER BY 有点像电子表格软件中的 SORT。
练习
1.哪个客户(按照名称)下的订单最早?你的答案应该包含订单的客户名称和日期。
SELECT a.name, o.occurred_at
FROM accounts a
JOIN orders o
ON a.id = o.account_id
ORDER BY occurred_at
LIMIT 1;
2.算出每个客户的总销售额(单位是美元)。答案应该包括两列:每个公司的订单总销售额(单位是美元)以及公司名称。
SELECT a.name,
SUM(o.total_amt_usd) AS total_amt_usd
FROM accounts a
JOIN orders o
ON o.account_id = a.id
GROUP BY a.name;
3.最近的 web_event 是通过哪个渠道发生的,与此 web_event 相关的客户是哪个?你的查询应该仅返回三个值:日期、渠道和客户名称。
SELECT w.occurred_at, w.channel, a.name
FROM web_events w
JOIN accounts a
ON w.account_id = a.id
ORDER BY w.occurred_at DESC
LIMIT 1;
4.算出 web_events 中每种渠道的次数。最终表格应该有两列:渠道和渠道的使用次数。
SELECT w.channel,
COUNT(w.channel)
FROM web_events w
GROUP BY w.channel
5.与最早的 web_event 相关的主要联系人是谁?
SELECT a.primary_poc
FROM web_events w
JOIN accounts a
ON a.id = w.account_id
ORDER BY w.occurred_at
LIMIT 1;
6.每个客户所下的最小订单是什么(以总金额(美元)为准)。答案只需两列:客户名称和总金额(美元)。从最小金额到最大金额排序。
SELECT a.name,
MIN(o.total_amt_usd) AS total_amt_usd
FROM aoccounts a
JOIN orders
ON o.account_id = a.id
GROUP BY a.name
ORDER BY total_amt_usd;
7.算出每个区域的销售代表人数。最早表格应该包含两列:区域和 sales_reps 数量。从最少到最多的代表人数排序。
SELECT r.name,
COUNT(*) AS sales_reps
FROM region r
JOIN sales_reps s
ON s.region_id = r.id
GROUP BY r.name
ORDER BY sales_reps;
GROUP BY - 多列
假设你在Parch&Posey担任营销经理, 想要了解每个账户如何与各种广告渠道互动, 哪些渠道在吸引流量并促成交易, 我们是否投资了不值得的渠道上, 每个渠道获得了多少流量?
实现上述问题的方法就是统计每个账户ID的每个渠道事件, 然后突出显示每个账户事件量最高的渠道
SELECT account_id,
channel,
COUNT(id) AS events
FROM web_events
GROUP BY account_id, channel
ORDER BY account_id, events DESC
主要知识点:
- 你可以同时按照多列分组,正如此处所显示的那样。这样经常可以在大量不同的细分中更好地获得聚合结果。
- ORDER BY 条件中列出的列顺序有区别。你是从左到右让列排序。
GROUP BY - 提示
GROUP BY 条件中的列名称顺序并不重要,结果还是一样的。如果运行相同的查询并颠倒 GROUP BY 条件中列名称的顺序,可以看到结果是一样的。
和 ORDER BY 一样,你可以在 GROUP BY 条件中用数字替换列名称。仅当你对大量的列分组时,或者其他原因导致 GROUP BY 条件中的文字过长时,才建议这么做。
提醒下,任何不在聚合函数中的列必须显示 GROUP BY 语句。如果忘记了,可能会遇到错误。但是,即使查询可行,你也可能不会喜欢最后的结果!
问题:GROUP BY(第二部分)
根据以下 SQL 表格信息回答以下问题。如果你遇到问题或想要对比检查你的答案,可以在下一页面的顶部找到我的答案。
1.对于每个客户,确定他们在订单中购买的每种纸张的平均数额。结果应该有四列:客户名称一列,每种纸张类型的平均数额一列。
SELECT a.name,
AVG(standard_qty) AS mean_of_standard,
AVG(gloss_qty) AS mean_of_gloss,
AVG(poster_qty) AS mean_of_poster
FROM orders o
JOIN accounts a
ON o.account_id = a.id
GROUP BY a.name
2.对于每个客户,确定在每个订单中针对每个纸张类型的平均消费数额。结果应该有四列:客户名称一列,每种纸张类型的平均消费数额一列。
SELECT a.name,
AVG(standard_amt_usd) AS mean_of_standard,
AVG(gloss_amt_usd) AS mean_of_gloss,
AVG(poster_amt_usd) AS mean_of_poster
FROM orders o
JOIN accounts a
ON o.account_id = a.id
GROUP BY a.name
3.确定在 web_events 表格中每个销售代表使用特定渠道的次数。最终表格应该有三列:销售代表的名称、渠道和发生次数。按照最高的发生次数在最上面对表格排序。
SELECT s.name,
w.channel,
COUNT(w.id) AS events
FROM web_events w
JOIN accounts a
ON w.account_id = a.id
JOIN sales_reps s
ON a.sales_rep_id = s.id
GROUP BY s.name, w.channel
ORDER BY events DESC
4.确定在 web_events 表格中针对每个地区特定渠道的使用次数。最终表格应该有三列:区域名称、渠道和发生次数。按照最高的发生次数在最上面对表格排序。
SELECT r.name,
w.channel,
COUNT(w.id) AS events
FROM web_events w
JOIN accounts a
ON w.account_id = a.id
JOIN sales_reps s
ON a.sales_rep_id = s.id
JOIN region r
ON s.region_id = r.id
GROUP BY r.name, w.channel
ORDER BY events DESC;
DISTINCT
如果你要对几列内容进行分组, 但不包含任何聚合函数, 你可以使用DISTINCT代替, 可以将 DISTINCT 看做仅返回特定列的唯一值的函数。例如对每个账户每个渠道的时间(events)计数
SELECT account_id,
channel,
COUNT(id) AS events
FROM web_events
GROUP BY account_id, channel
ORDER BY account_id, events DESC
如果我们去除events列, 行数1509没有变化
如果使用DISTINCT, 和上图的结果时一样的.
练习
1.使用 DISTINCT 检查是否有任何客户与多个区域相关联?
下面的两个查询产生了相同的行数(351 行),因此我们知道每个客户仅与一个区域相关联。
如果每个客户与多个区域相关联,则第一个查询返回的行数应该比第二个查询的多。
SELECT DISTINCT a.id, r.id, a.name, r.name
FROM accounts a
JOIN sales_reps s
ON s.id = a.sales_rep_id
JOIN region r
ON r.id = s.region_id;
and
SELECT DISTINCT id, name
FROM accounts;
2.有没有销售代表要处理多个客户?
实际上,所有销售代表都要处理多个客户。
销售代表处理的最少客户数量是 3 个。
有 50 个销售代表,他们都有多个客户。
在第二个查询中使用 DISTINCT 确保包含了第一个查询中的所有销售代表。
SELECT s.id, s.name, COUNT(*) num_accounts
FROM accounts a
JOIN sales_reps s
ON s.id = a.sales_rep_id
GROUP BY s.id, s.name
ORDER BY num_accounts;
and
SELECT DISTINCT id, name
FROM sales_reps;
HAVING
假设你是Parch&Posey的账户经理, 负责的是公司最大的账户, 你可能需要确定销售额超过25万的账户的总销售额, 以便更好地了解, 来自这些大额账户的收入的比例.
要得到这个清单, 首先要知道每个账户的销售总额, 并采用降序排序
SELECT account_id, SUM(total_amt_usd) AS total_amt_usd
FROM orders
GROUP BY 1
ORDER BY 2 DESC;
然后从这些账户中过滤处销售额超过25万的账户
但是当使用WHERE子句进行过滤时, 会出现报错, 因为它不允许过滤聚合后的列, 在这个例子中 就是SUM(total_amt_usd)
, 而聚合列要用到HAVING子句, HAVING子句的顺序总是出现在GROUP BY语句后面.
SELECT account_id, SUM(total_amt_usd) AS total_amt_usd
FROM orders
GROUP BY 1
HAVING SUM(total_amt_usd) > 250000
ORDER BY 2 DESC;
注意
这只有在按一个或多个列分组时才有用, 也就是说没有分组时, 没有必要使用, 本质上,只要你想对通过聚合创建的查询中的元素执行 WHERE 条件,就需要使用 HAVING。
练习
1.有多少位销售代表需要管理超过 5 个客户?
SELECT s.id, s.name, COUNT(a.id) num_accounts
FROM accounts a
JOIN sales_reps s
ON s.id = a.sales_rep_id
GROUP BY s.id, s.name
HAVING COUNT(a.id) > 5
2.有多少个客户具有超过 20 个订单?
SELECT a.name,
COUNT(*) num_orders
FROM orders o
JOIN accounts a
ON o.account_id = a.id
GROUP BY a.name
HAVING COUNT(*) > 20
3.哪个客户的订单最多?
SELECT a.name,
COUNT(*) num_orders
FROM orders o
JOIN accounts a
ON o.account_id = a.id
GROUP BY a.name
ORDER BY 2 DESC
LIMIT 1
4.有多少个客户在所有订单上消费的总额超过了 30,000 美元?
SELECT a.name,
SUM(o.total_amt_usd) AS total_amt
FROM orders o
JOIN accounts a
ON o.account_id = a.id
GROUP BY a.name
HAVING SUM(o.total_amt_usd) > 30000
ORDER BY 2 DESC
5.有多少个客户在所有订单上消费的总额不到 1,000 美元?
SELECT a.name,
SUM(o.total_amt_usd) AS total_amt
FROM orders o
JOIN accounts a
ON o.account_id = a.id
GROUP BY a.name
HAVING SUM(o.total_amt_usd) < 1000
ORDER BY 2 DESC
6.哪个客户消费的最多?
SELECT a.name,
SUM(o.total_amt_usd) AS total_amt
FROM orders o
JOIN accounts a
ON o.account_id = a.id
GROUP BY a.name
ORDER BY 2 DESC
LIMIT 1
7.哪个客户消费的最少?
SELECT a.name,
SUM(o.total_amt_usd) AS total_amt
FROM orders o
JOIN accounts a
ON o.account_id = a.id
GROUP BY a.name
ORDER BY 2
LIMIT 1
8.哪个客户使用 facebook 作为与消费者沟通的渠道超过 6 次?
SELECT a.name, w.channel,
COUNT(w.channel) AS num_channel
FROM web_events w
JOIN accounts a
ON w.account_id = a.id
WHERE w.channel = 'facebook'
GROUP BY a.name, w.channel
HAVING COUNT(w.channel) > 6
ORDER BY num_channel DESC
或者
SELECT a.id, a.name, w.channel, COUNT(*) use_of_channel
FROM accounts a
JOIN web_events w
ON a.id = w.account_id
GROUP BY a.id, a.name, w.channel
HAVING COUNT(*) > 6 AND w.channel = 'facebook'
ORDER BY use_of_channel;
9.哪个客户使用 facebook 作为沟通渠道的次数最多?
SELECT a.name, w.channel,
COUNT(w.channel) AS num_channel
FROM web_events w
JOIN accounts a
ON w.account_id = a.id
WHERE w.channel = 'facebook'
GROUP BY a.name, w.channel
ORDER BY 3 DESC
LIMIT 1
10.哪个渠道是客户最常用的渠道?
SELECT w.channel,
COUNT(w.channel) AS num_channel
FROM web_events w
JOIN accounts a
ON w.account_id = a.id
GROUP BY w.channel
ORDER BY 2 DESC
LIMIT 1
DATE_TRUNC
在 SQL 中,按照日期列分组通常不太实用, 每个时间戳都是独一无二的,因为这些列可能包含小到一秒的交易数据。所以最现实的方法还是将其处理为最近一天, 一个星期或一个月, 并对此期间进行聚合.
首先了解一下数据库中时间的存储方式, 日期排序和文本排序都是一样的, 看到日期存储为年、月、日、小时、分钟、秒,可以帮助我们截取信息。
要按日期分组的话, 我们需要把2017年4月1日这一天所有的时间, 调整为00:00:00, 这样4月1日所有小时,分钟和秒内发生的每一个事件, 它们会被归为一组, 要实现这一点, 可以使用DATE_TRUNC函数来完成.
注意:要按包含在SELECT语句中的相同度量进行分组, 这一点非常重要, 这样可以保证结果的一致.
SELECT DATE_TRUNC('day', occurred_at) AS day,
SUM(standard_qty) AS standard_qty_sum
FROM orders
GROUP BY DATE_TRUNC('day', occurred_at)
ORDER BY DATE_TRUNC('day', occurred_at)
执行结果
大部分时候, 你会使用事件间隔进行聚合, 因为它从业务角度来看很有意义, 比如'day', 'week', 'month', 'quarter', 'year'. 在某些情况下, 你可能只需要提取一个日期的某一部分, 例如如果你想知道, 本周哪一天中Parch&Poise网站的流量最大, 但你不想使用DATE_TRUNC, 要想知道是那一天, 你必须使用DATE_PART
DATE_PART
DATE_PART 可以提取处感兴趣的日期中某个部分
在Parch&Posey公司中, 一周中的哪一天的销售额最多?
首先确认每周每天的订单, dow
代表星期几, 会返回一个从0到6的值, 其中0代表星期日, 6代表星期六.
然后, 对这一天的数据进行汇总总销量
最后对每一周中每一天进行分组, 再排序.
SELECT DATE_PART('dow', occurred_at) AS day_of_week,
SUM(total) AS total_qty
FROM orders
GROUP BY 1
ORDER BY 2 DESC;
从执行结果可知, 周日的订单量最大.
提示: DATE_PART 可以用来获取日期的特定部分,但是注意获取 month 或 dow 意味着无法让年份按顺序排列。而是按照特定的部分分组,无论它们属于哪个年份。
练习
- Parch & Posey 在哪一年的总销售额最高?数据集中的所有年份保持均匀分布吗?
SELECT DATE_PART('year', occurred_at) AS year,
SUM(total_amt_usd) AS total_usd
FROM orders
GROUP BY 1
ORDER BY 2 DESC
结论:
对于 2013 年和 2017 年来说,每一年只有一个月的销量(2013 年为 12,2017 年为 1)。 因此,二者都不是均匀分布。销量一年比一年高,2016 年是到目前为止最高的一年。按照这个速度,我们预计 2017 年可能是最高销量的一年。
- Parch & Posey 在哪一个月的总销售额最高?数据集中的所有月份保持均匀分布吗?
SELECT DATE_PART('month', occurred_at) AS Month,
SUM(total_amt_usd) AS total_usd
FROM orders
WHERE occurred_at BETWEEN '2014-01-01' AND '2017-01-01'
GROUP BY DATE_PART('month', occurred_at)
ORDER BY 2 DESC
结论:
为了保持公平,我们应该删掉 2013 年和 2017 年的销量。原因如上。12 月的销量最高。
- Parch & Posey 在哪一年的总订单量最多?数据集中的所有年份保持均匀分布吗?
SELECT DATE_PART('year', occurred_at) AS year,
COUNT(*) total_sales
FROM orders
GROUP BY DATE_PART('year', occurred_at)
ORDER BY 2 DESC
结论:
同样,到目前为止,2016 年的订单量最多,但是与数据集中的其他年份相比,2013 年和 2017 年的分布不均匀。
- Parch & Posey 在哪一个月的总订单量最多?数据集中的所有月份保均匀分布吗?
SELECT DATE_PART('month', occurred_at) AS Month,
COUNT(*) total_sales
FROM orders
WHERE occurred_at BETWEEN '2014-01-01' AND '2017-01-01'
GROUP BY DATE_PART('month', occurred_at)
ORDER BY 2 DESC
结论:
12 月依然是销量最多的月份,但是有趣的是,11 月是销量第二多的月份。为了保持公平,删掉了 2017 年和 2013 年的数据。
- Walmart 在哪一年的哪一个月在铜版纸上的消费最多?
SELECT DATE_TRUNC('month', o.occurred_at) AS month_of_year,
SUM(o.gloss_amt_usd) AS gloss_usd
FROM orders o
JOIN accounts a
ON a.id = o.account_id
WHERE a.name = 'Walmart'
GROUP BY 1
ORDER BY 2 DESC
LIMIT 1;
结论:
在 2016 年 5 月,Walmart 在铜版纸上的消费做多。
CASE
假设你是Parch&Posey的市场经理, 你想把Facebook与其他市场渠道进行对比, 你知道Facebook是你业务的最重要渠道, 但是, 他是否大过所有其他渠道的总和?
代码如下:
SELECT id,
account_id,
occurred_at,
channel,
CASE WHEN channel = 'facebook' THEN 'yes' END AS is_facebook
FROM web_events
ORDER BY occurred_at
首先要创建一个派生列, 也就说从当前列中提取数据,然后进行修改, 在前面的文中, 使用算术来完成, 在这个例子中用CASE函数, 这是SQL处理IF-THEN逻辑的方式, CASE语句会附有至少一对WHEN-THEN
语句, 在SQL中, 这两者等同于IF-THEN, 完成时必须有END
一词, CASE语句会检查条件语句channel = 'facebook'
是否为真, 如果为真, 其结果yes
就出现在is_facebook列中
如果想让channel = 'facebook'
为假时, 也用相应的词来填充派生列, 那么可以使用ELSE
语句, 代码如下:
SELECT id,
account_id,
occurred_at,
channel,
CASE WHEN channel = 'facebook' THEN 'yes' ELSE 'no' END AS is_facebook
FROM web_events
ORDER BY occurred_at
如果想让新列不仅持续追踪Facebook渠道, 还追踪direct渠道, 可以修改CASE 语句, 用OR
运算符, 代码如下:
SELECT id,
account_id,
occurred_at,
channel,
CASE WHEN channel = 'facebook' OR channel = 'direct' THEN 'yes' ELSE 'no' END AS is_facebook
FROM web_events
ORDER BY occurred_at
提示: WHEN
语句类似于WHERE
语句中的逻辑条件, 因此可以使用AND, LIKE, IN
或者其他逻辑运算符
使用 CASE语句定义多个输出
假设你负责Parch&Posey的运营, 你想根据订单大小将订单进行分组, 以得到更精细的库存规划
实现上述过程, 可以通过多个WHEN-THEN
语句, 代码示例如下:
SELECT account_id,
occurred_at,
total,
CASE WHEN total= '500' THEN 'over 500'
WHEN total= '300' THEN '301 - 500'
WHEN total= '100' THEN '100 - 300'
ELSE '100 or under' END AS total_group
FROM orders
在以上的代码中, 条件语句total= '500'
和total= '300'
范围重叠, 对每个大于500的值就会有些混淆, 更好的方法时, 定义不会于其他组重叠的特殊组, 为此, 可以将多种条件语句串联起来, 和WHERE
子句中方法相同, 使用AND
和OR
. 修改后的代码如下:
SELECT account_id,
occurred_at,
total,
CASE WHEN total > 500 THEN 'over 500'
WHEN total= '300' AND total <= 500 THEN '301 - 500'
WHEN total= '100' AND total <= 300 THEN '100 - 300'
ELSE '100 or under' END AS total_group
FROM orders
示例
在第一节课的练习中,你看到了以下问题:
创建一列用于将 standard_amt_usd 除以 standard_qty,以便计算每个订单的标准纸张的单价,将结果限制到前 10 个订单,并包含 id 和 account_id 字段。注意 - 如果你的答案正确,系统将显示一个错误,这是因为你除以了 0。当你在下个部分学习 CASE 语句时,你将了解如何让此查询不会报错。
我们来看看如何使用 CASE 语句来避免这一错误。
SELECT id, account_id, standard_amt_usd/standard_qty AS unit_price
FROM orders
LIMIT 10;
现在我们使用一个 CASE 语句,这样的话,一旦 standard_qty 为 0,我们将返回 0,否则返回 unit_price。
SELECT account_id,
CASE WHEN standard_qty = 0 OR standard_qty IS NULL THEN 0
ELSE standard_amt_usd/standard_qty END AS unit_price
FROM orders
LIMIT 10;
CASE与聚合
假如你作为Parch&Posey的销售经理, 将订单分进行分组, 并且计算出每一组的所有订单数.
进行组内计数的最简单的办法, 是创建一个按照你期望方式分类的列, 接下来, 创建另外一个列, 进行组的计数, 在此, 我们使用CASE将订单分为 总销售额 超过500和500及以下的分组, 然后对此派生列进行分组.
SELECT CASE WHEN total > 500 THEN 'Over 500'
ELSE '500 or under ' END AS total_group
COUNT(*) AS order_count
FROM orders
GROUP BY 1
思考: 为什么不用WHERE子句过滤掉那些我不想计数的行?
因为WHERE子句只允许一次计算一个条件, 如果有大量的不同案例, 内容会特别冗长.
练习
1.我们想要根据相关的消费量了解三组不同的客户。最高的一组是终身价值(所有订单的总销售额)大于 200,000 美元的客户。第二组是在 200,000 到 100,000 美元之间的客户。最低的一组是低于 under 100,000 美元的客户。请提供一个表格,其中包含与每个客户相关的级别。你应该提供客户的名称、所有订单的总销售额和级别。消费最高的客户列在最上面。
SELECT a.name,
SUM(o.total_amt_usd) AS total_amt,
CASE WHEN SUM(o.total_amt_usd) > 200000 THEN 'top'
WHEN SUM(o.total_amt_usd) > 100000 AND SUM(o.total_amt_usd) <= 200000 THEN 'middle'
ELSE 'low' END AS Class
FROM orders o
JOIN accounts a
ON a.id = o.account_id
GROUP BY 1
ORDER BY 2 DESC
2.现在我们想要执行和第一个问题相似的计算过程,但是我们想要获取在 2016 年和 2017 年客户的总消费数额。级别和上一个问题保持一样。消费最高的客户列在最上面。
SELECT a.name,
SUM(o.total_amt_usd) AS total_amt,
CASE WHEN SUM(o.total_amt_usd) > 200000 THEN 'top'
WHEN SUM(o.total_amt_usd) > 100000 AND SUM(o.total_amt_usd) <= 200000 THEN 'middle'
ELSE 'low' END AS Class
FROM orders o
JOIN accounts a
ON a.id = o.account_id
WHERE o.occurred_at > '2015-12-31'
GROUP BY 1
ORDER BY 2 DESC
3.我们想要找出绩效最高的销售代表,也就是有超过 200 个订单的销售代表。创建一个包含以下列的表格:销售代表名称、订单总量和*标为 top 或 not 的列(取决于是否拥有超过 200 个订单)。销售量最高的销售代表列在最上面。
SELECT s.name,
COUNT(*) total_sales,
CASE WHEN COUNT(*) > 200 THEN 'top'
ELSE 'not' END AS sales_rep_level
FROM orders o
JOIN accounts a
ON o.account_id = a.id
JOIN sales_reps s
ON a.sales_rep_id = s.id
GROUP BY 1
ORDER BY 2 DESC
4.之前的问题没有考虑中间水平的销售代表或销售额。管理层决定也要看看这些数据。我们想要找出绩效很高的销售代表,也就是有超过 200 个订单或总销售额超过 750000 美元的销售代表。中间级别是指有超过 150 个订单或销售额超过 500000 美元的销售代表。创建一个包含以下列的表格:销售代表名称、总订单量、所有订单的总销售额,以及标为 top、middle 或 low 的列(取决于上述条件)。在最终表格中将销售额最高的销售代表列在最上面。根据上述标准,你可能会见到几个表现很差的销售代表!
SELECT s.name,
COUNT(*) total_sales,
SUM(o.total_amt_usd) total_amt,
CASE WHEN COUNT(*) > 200 OR SUM(o.total_amt_usd) > 750000 THEN 'top'
WHEN COUNT(*) > 150 OR SUM(o.total_amt_usd) > 500000 THEN 'middle'
ELSE 'low' END AS sales_rep_level
FROM orders o
JOIN accounts a
ON o.account_id = a.id
JOIN sales_reps s
ON a.sales_rep_id = s.id
GROUP BY 1
ORDER BY 3 DESC;