复盘(马后炮)一下,给自己以后写复杂SQL
时留点案例。
以下案例均为虚构,如有雷同皆为巧合~
以下解法仅考虑实现,未涉及性能~
信息
store_revenue_ranking
门店营收排行表
id
主键,revenue
营收额,date_day
时间,store_name
门店名称
需求
列出门店营收排行榜连续三天
及以上营收额
大于或等于 100万
的门店
。 返回结果按date_day
升序排列展示。
现有数据示例
id | revenue | date_day | store_name |
---|---|---|---|
1 | 120 | 20160101 | 江南皮革城 |
2 | 89 | 20160102 | 江南皮革城 |
3 | 181 | 20160103 | 江南皮革城 |
4 | 150 | 20160104 | 车迟国皮革城 |
5 | 99 | 20160105 | 梁山皮革城 |
6 | 145 | 20160106 | 车迟国皮革城 |
7 | 1231 | 20160107 | 车迟国皮革城 |
8 | 129 | 20160108 | 车迟国皮革城 |
9 | 300 | 20160109 | 车迟国皮革城 |
输出数据示例
id | revenue | date_day | store_name |
---|---|---|---|
6 | 145 | 20160106 | 车迟国皮革城 |
7 | 1231 | 20160107 | 车迟国皮革城 |
8 | 129 | 20160108 | 车迟国皮革城 |
9 | 300 | 20160109 | 车迟国皮革城 |
解题思路
- 首先,从排行表中筛选出营收额大于等于100万的记录。
- 然后,对筛选结果按照门店和日期进行分组,并根据日期进行升序排序。在每个门店分组内,使用行号进行标记。
- 接下来,再次对得到的结果进行嵌套查询,并根据门店和日期减去行号的组合进行分组。在每个组中找到最早的日期。继续在当前结果上筛选出拥有至少3个记录的组。
- 将筛选的结果与排行表进行内连接,使用门店和日期进行关联。关联条件是门店相等并且日期大于等于最早的日期。最后返回与连接结果匹配排行表中的所有列,并按照日期进行升序排序。
完整SQL
vbnet
SELECT s1.*
FROM store_revenue_ranking s1
INNER JOIN (
SELECT store_name, MIN(date_day) AS start_day
FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY store_name ORDER BY date_day) AS rn
FROM store_revenue_ranking
WHERE revenue >= 100
) AS t
GROUP BY store_name, date_day - rn
HAVING COUNT(*) >= 3
) AS s2 ON s1.store_name = s2.store_name AND s1.date_day >= s2.start_day
ORDER BY s1.date_day;
步骤拆解
片段一
sql
SELECT *, ROW_NUMBER() OVER (PARTITION BY store_name ORDER BY date_day) AS rn
FROM store_revenue_ranking
WHERE revenue >= 100
SELECT *
: 选择所有列。
ROW_NUMBER() OVER (PARTITION BY store_name ORDER BY date_day) AS rn
: 创建名为rn
的新列,用来为每个不同的store_name
按照date_day
进行排序并分配一个唯一的序号。
WHERE revenue >= 100
: 添加一个条件,仅选择revenue
值大于等于100
的行。 这段SQL的目的是选取store_revenue_ranking
表中revenue
大于等于100
的行,并为每个store_name
按照date_day
排序分配一个唯一的序号。
片段二
sql
SELECT store_name, MIN(date_day) AS start_day
FROM (
片段一
) AS t
GROUP BY store_name, date_day - rn
HAVING COUNT(*) >= 3
SELECT store_name, MIN(date_day) AS start_day
: 选择store_name
列。date_day
列的最小值,并将其命名为start_day
的结果列。
GROUP BY store_name, date_day - rn
: 按照store_name
和(date_day - rn)
的组合进行分组,其中rn
是前面查询结果中的序号列。
HAVING COUNT(*) >= 3
: 对分组结果进行筛选,只保留满足条件的分组,其中具有相同store_name
和(date_day - rn)
值的分组的行数大于等于3。
最后
vbnet
SELECT s1.*
FROM store_revenue_ranking s1
INNER JOIN (
片段二
) AS s2 ON s1.store_name = s2.store_name AND s1.date_day >= s2.start_day
ORDER BY s1.date_day;
INNER JOIN s2 ON s1.store_name = s2.store_name AND s1.date_day >= s2.start_day
: 进行内连接操作,将表s1
和表s2
根据store_name
进行连接,并且要求s1
的date_day
大于等于s2
的start_day
。最后按照date_day
进行升序排序。
涉及知识点
MIN()
[1] 是一个聚合函数,用于返回一组值中的最小值。它可以应用于数字、日期、字符串等不同类型的数据。 使用MIN()
函数的一般语法如下:
sql
SELECT MIN(column_name) FROM table_name;
ROW_NUMBER() OVER()
[1] 是在MySQL
中用于为结果集中的每一行分配一个唯一的行号。ROW_NUMBER()
通常与 OVER()
子句一起使用,其中可以指定排序方式和分组条件。 一般语法如下:
sql
SELECT ROW_NUMBER() OVER (ORDER BY column_name) AS row_number, column_name FROM table_name;
HAVING
[2]在MySQL
中,HAVING
子句用于在GROUP BY
子句之后过滤结果。它允许你根据聚合函数的计算结果对结果集进行筛选。 通常,在使用GROUP BY
子句对结果进行分组之后,你可以使用HAVING
子句根据聚合函数的结果来筛选分组后的数据。与WHERE
子句不同,HAVING
子句可以使用聚合函数和列名来进行过滤。 以下是HAVING子句的基本语法:
sql
SELECT 列1, 列2, ...
FROM 表名
GROUP BY 列1, 列2, ...
HAVING 条件;
参考文献
csharp
[1] . Mysql函数 . 菜鸟教程 . https://www.runoob.com/mysql/mysql-functions.html
[2] . MySQL 处理重复数据 . 菜鸟教程 . https://www.runoob.com/mysql/mysql-handling-duplicates.html