SQL思路解析:窗口滑动的应用

目录

[🎯 问题目标](#🎯 问题目标)

第一步:从数据中我们能直接得到什么?

第二步:我们想要的"7天窗口"长什么样?

[第三步:SQL 怎么表达"某一天的前六天"?](#第三步:SQL 怎么表达“某一天的前六天”?)

[🔍JOIN 比窗口函数更灵活](#🔍JOIN 比窗口函数更灵活)

第四步:每个窗口要计算什么?

[第五步:怎么避免不满 7 天的窗口?](#第五步:怎么避免不满 7 天的窗口?)

[最终完整 SQL](#最终完整 SQL)


复制代码
表: Customer

+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| customer_id   | int     |
| name          | varchar |
| visited_on    | date    |
| amount        | int     |
+---------------+---------+
在 SQL 中,(customer_id, visited_on) 是该表的主键。
该表包含一家餐馆的顾客交易数据。
visited_on 表示 (customer_id) 的顾客在 visited_on 那天访问了餐馆。
amount 是一个顾客某一天的消费总额。

你是餐馆的老板,现在你想分析一下可能的营业额变化增长(每天至少有一位顾客)。

计算以 7 天(某日期 + 该日期前的 6 天)为一个时间段的顾客消费平均值。average_amount 要 保留两位小数。

结果按 visited_on 升序排序。

返回结果格式的例子如下。

复制代码
示例 1:

输入:
Customer 表:
+-------------+--------------+--------------+-------------+
| customer_id | name         | visited_on   | amount      |
+-------------+--------------+--------------+-------------+
| 1           | Jhon         | 2019-01-01   | 100         |
| 2           | Daniel       | 2019-01-02   | 110         |
| 3           | Jade         | 2019-01-03   | 120         |
| 4           | Khaled       | 2019-01-04   | 130         |
| 5           | Winston      | 2019-01-05   | 110         | 
| 6           | Elvis        | 2019-01-06   | 140         | 
| 7           | Anna         | 2019-01-07   | 150         |
| 8           | Maria        | 2019-01-08   | 80          |
| 9           | Jaze         | 2019-01-09   | 110         | 
| 1           | Jhon         | 2019-01-10   | 130         | 
| 3           | Jade         | 2019-01-10   | 150         | 
+-------------+--------------+--------------+-------------+
输出:
+--------------+--------------+----------------+
| visited_on   | amount       | average_amount |
+--------------+--------------+----------------+
| 2019-01-07   | 860          | 122.86         |
| 2019-01-08   | 840          | 120            |
| 2019-01-09   | 840          | 120            |
| 2019-01-10   | 1000         | 142.86         |
+--------------+--------------+----------------+
解释:
第一个七天消费平均值从 2019-01-01 到 2019-01-07 是restaurant-growth/restaurant-growth/ (100 + 110 + 120 + 130 + 110 + 140 + 150)/7 = 122.86
第二个七天消费平均值从 2019-01-02 到 2019-01-08 是 (110 + 120 + 130 + 110 + 140 + 150 + 80)/7 = 120
第三个七天消费平均值从 2019-01-03 到 2019-01-09 是 (120 + 130 + 110 + 140 + 150 + 80 + 110)/7 = 120
第四个七天消费平均值从 2019-01-04 到 2019-01-10 是 (130 + 110 + 140 + 150 + 80 + 110 + 130 + 150)/7 = 142.86

来源:Leecode


🎯 问题目标

先问自己最本质的问题:

我想得到的到底是什么?

你想得到:

  • 某一天(比如 2019-01-07)为 窗口最后一天

  • 以它为终点往前推 6 天(共 7 天)的所有消费数据

  • 求这 7 天的总消费额和平均消费额(平均保留两位小数)

  • 然后按日期升序列出每个窗口的情况

第一步:从数据中我们能直接得到什么?

我们原始数据是:

复制代码
| customer_id | name  | visited_on | amount |
|-------------|-------|------------|--------|
| 1           | Jhon  | 2019-01-01 | 100    |
| 2           | Daniel| 2019-01-02 | 110    |
| ...         | ...   | ...        | ...    |

这是"按顾客"记录的交易数据。

原始数据是"每个顾客某天消费了多少",而我们不关心顾客是谁,只关心 每一天总共有多少消费。

为了达成这个目标,你最小的可操作单位是:

✅ 每一天的"总营业额"

所以,第一步我们应该做的是:

sql 复制代码
SELECT
  visited_on,
  SUM(amount) AS total_amount
FROM Customer
GROUP BY visited_on

得到了:

sql 复制代码
| visited_on | total_amount |
|------------|--------------|
| 2019-01-01 | 100          |
| 2019-01-02 | 110          |
| 2019-01-03 | 120          |
| ...        | ...          |

第二步:我们想要的"7天窗口"长什么样?

比如你想分析 2019-01-07 这个窗口,它包括:

  • 2019-01-01

  • 2019-01-02

  • 2019-01-03

  • 2019-01-04

  • 2019-01-05

  • 2019-01-06

  • 2019-01-07

我们要把这 7 天的金额加总后求平均。

换句话说,对于每一个日期 D,你要去找所有日期 D',满足:

D' >= D - 6 天 AND D' <= D,然后求 sum(amount)

第三步:SQL 怎么表达"某一天的前六天"?

想象一下,窗口要对比谁和谁?

我们要让每一行(例如日期是 2019-01-10)"看见"自己之前 6 天的数据。但 SQL 是面向集合的语言,每一行默认不能看见其他行。

怎么让一行"看到"它前面的几天?答案是:自连接(JOIN)!

sql 复制代码
SELECT
  c1.visited_on,             -- 作为窗口的当前"右端点"
  c2.visited_on,             -- 被扫描比较的行
FROM (
  SELECT visited_on, SUM(amount) AS daily_total
  FROM Customer
  GROUP BY visited_on
) c1
JOIN (
  SELECT visited_on, SUM(amount) AS daily_total
  FROM Customer
  GROUP BY visited_on
) c2
 ON c2.visited_on BETWEEN DATE_SUB(c1.visited_on, INTERVAL 6 DAY) AND c1.visited_on

这个 JOIN 的意思是:

对于每一行 c1,找出所有 c2,使得 c2.visited_on 落在 c2 之前 6 天之内。

也就是说,每一行 c1 会配对出一个 7 天的"时间窗口"数据集 c2。

就像下面这个例子:

c1.visited_on c2.visited_on(符合条件)
2019-01-07 2019-01-01 ~ 2019-01-07
2019-01-08 2019-01-02 ~ 2019-01-08
2019-01-09 2019-01-03 ~ 2019-01-09
2019-01-10 2019-01-04 ~ 2019-01-10

你可以理解为:"c1 的每一天",都配对了"过去七天的 c2",这就模拟出"滑动窗口"的行为了!

🔍JOIN 比窗口函数更灵活

在"时间窗口"这种分析中,数据可能并不是每天都有,或者每天不止一条记录,比如:

sql 复制代码
| visited_on   | amount |
|--------------|--------|
| 2024-01-01   | 100    |
| 2024-01-01   | 80     |
| 2024-01-03   | 200    |

这种不连续、一天多条的情况,用 OVER (ORDER BY visited_on ROWS ...) 是不靠谱的,因为行数 ≠ 时间!

JOIN 这种方式,直接按时间范围配对,不依赖数据是否连续,每天有多少条都不影响。

第四步:每个窗口要计算什么?

你想要的就是:

  • c1.visited_on:当前窗口的最后一天

  • SUM(c2.amount):这 7 天的总金额

  • ROUND(SUM(c2.amount) / 7, 2):这 7 天的平均值(保留两位小数)

第五步:怎么避免不满 7 天的窗口?

比如当你分析 2019-01-02 时,它前面只有两天的数据(01、02),这是 不满 7 天的窗口,要排除掉。

这时候就要加一条语句:

sql 复制代码
HAVING COUNT(DISTINCT c2.visited_on) = 7

意思是:只有当这 7 天真的有 7 个不同的日期数据,才纳入最终结果。

最终完整 SQL

把上述分析组合起来,完整 SQL 如下:

sql 复制代码
SELECT 
  c1.visited_on,
  SUM(c2.daily_total) AS amount,
  ROUND(SUM(c2.daily_total)/7, 2) AS average_amount
FROM (
  SELECT visited_on, SUM(amount) AS daily_total
  FROM Customer
  GROUP BY visited_on
) c1
JOIN (
  SELECT visited_on, SUM(amount) AS daily_total
  FROM Customer
  GROUP BY visited_on
) c2
  ON c2.visited_on BETWEEN DATE_SUB(c1.visited_on, INTERVAL 6 DAY) AND c1.visited_on
GROUP BY c1.visited_on
HAVING COUNT(c2.visited_on) = 7
ORDER BY c1.visited_on;
问题层级 解释
本质问题 想知道某天 + 前六天的消费总和和平均
可直接获取的数据 每天的顾客消费记录(可汇总)
怎么形成7天窗口 用自连接 + 日期范围:BETWEEN D - 6 AND D
如何计算 汇总 amount,平均除以 7 并 ROUND
如何过滤不满7天窗口 HAVING COUNT(DISTINCT c2.visited_on) = 7
最终排序 按 visited_on 升序展示
相关推荐
秃了也弱了。33 分钟前
DBSyncer:开源数据库同步利器,MySQL/Oracle/ES/SqlServer/PG/
数据库·mysql·开源
玄辰星君1 小时前
PostgreSQL 入门教程
数据库·postgresql
泽韦德1 小时前
【Redis】笔记|第9节|Redis Stack扩展功能
数据库·redis·笔记
喜欢踢足球的老罗1 小时前
使用 Spring Boot 3.3 和 JdbcTemplate 操作 MySQL 数据库
数据库·spring boot·mysql
文牧之1 小时前
PostgreSQL 的扩展pg_prewarm
运维·数据库·postgresql
行星0081 小时前
Postgresql字符串操作函数
数据库·postgresql
清风~徐~来2 小时前
【Redis】类型补充
数据库·redis·缓存
代码探秘者2 小时前
【Redis从入门到精通实战文章汇总】
数据库·redis·缓存
weixin_748877002 小时前
【Redis实战:缓存与消息队列的应用】
数据库·redis·缓存
····懂···3 小时前
PostgreSQL 技术峰会,为您打造深度交流优质平台
数据库·postgresql