【代码备忘录】复杂SQL写法案例(一)

复盘(马后炮)一下,给自己以后写复杂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进行连接,并且要求s1date_day大于等于s2start_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
相关推荐
组合缺一2 小时前
Solon Cloud Gateway 开发:熟悉 ExContext 及相关接口
java·后端·gateway·solon
幸好我会魔法4 小时前
人格分裂(交互问答)-小白想懂Elasticsearch
大数据·spring boot·后端·elasticsearch·搜索引擎·全文检索
SomeB1oody5 小时前
【Rust自学】15.2. Deref trait Pt.1:什么是Deref、解引用运算符*与实现Deref trait
开发语言·后端·rust
何中应5 小时前
从管道符到Java编程
java·spring boot·后端
组合缺一6 小时前
Solon Cloud Gateway 开发:Route 的过滤器与定制
java·后端·gateway·reactor·solon
SomeB1oody6 小时前
【Rust自学】15.4. Drop trait:告别手动清理,释放即安全
开发语言·后端·rust
customer086 小时前
【开源免费】基于SpringBoot+Vue.JS贸易行业crm系统(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·开源
花心蝴蝶.8 小时前
Spring IoC & DI
java·后端·spring
半夏知半秋8 小时前
rust学习-所有权
开发语言·后端·学习·rust
Ciderw9 小时前
TCP三次握手和四次挥手
开发语言·网络·c++·后端·网络协议·tcp/ip·golang