一、问题描述
本题给定了一个名为 Customer
的表,记录了餐馆顾客的交易数据,包括顾客 ID、姓名、访问日期和消费金额。作为餐馆老板,我们的任务是分析营业额的变化增长情况,具体来说,就是计算以 7 天(某日期 + 该日期前的 6 天)为一个时间段的顾客消费平均值,并按访问日期升序排序,同时要保留两位小数。
二、表结构分析
Customer
表的结构如下:
列名 | 类型 | 说明 |
---|---|---|
customer_id |
int |
顾客的唯一标识,用于区分不同的顾客。 |
name |
varchar |
顾客的姓名。 |
visited_on |
date |
顾客访问餐馆的日期,与 customer_id 一起构成表的主键,确保每一条记录的唯一性。 |
amount |
int |
顾客在当天的消费总额。 |
三、解题思路
-
确定时间段:要计算以 7 天为一个时间段的消费情况,我们需要对每个日期,找到其前 6 天以及当天的所有消费记录。
-
计算消费总和:对于每个确定的 7 天时间段,计算该时间段内的消费总额。
-
计算平均值:根据消费总和,计算出该 7 天时间段的平均消费金额,并保留两位小数。
-
结果排序:将计算得到的每个 7 天时间段的访问日期、消费总额和平均消费金额按访问日期升序排列输出。
为了实现以上思路,我们可以使用 SQL 中的窗口函数来简化计算过程,窗口函数可以在不改变表结构的情况下,对数据进行分组和聚合操作。
四、SQL 代码实现
-- 选择访问日期、消费总额和平均消费金额
SELECT
visited_on,
amount,
-- 保留两位小数
ROUND(amount / 7, 2) AS average_amount
FROM
(
-- 子查询计算每个 7 天时间段的消费总额
SELECT
visited_on,
SUM(amount) AS amount
FROM
(
-- 使用窗口函数计算每个日期及其前 6 天的消费总和
SELECT
visited_on,
SUM(amount) OVER (ORDER BY visited_on ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS amount
FROM
Customer
) sub1
WHERE
visited_on >= (SELECT MIN(visited_on) FROM Customer + INTERVAL 6 DAY)
GROUP BY
visited_on
) sub2
ORDER BY
visited_on;
五、代码详细解释
1、最内层子查询:
SELECT
visited_on,
SUM(amount) OVER (ORDER BY visited_on ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS amount
FROM
Customer
这里使用了窗口函数 SUM(amount) OVER (ORDER BY visited_on ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)
。OVER
子句指定了窗口的定义,ORDER BY visited_on
表示按照访问日期进行排序,ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
表示窗口的范围是当前行以及其前 6 行。所以这个子查询的作用是计算每个日期及其前 6 天的消费总和,并将结果命名为 amount
。
2、中间子查询:
SELECT
visited_on,
SUM(amount) AS amount
FROM
(最内层子查询) sub1
WHERE
visited_on >= (SELECT MIN(visited_on) FROM Customer + INTERVAL 6 DAY)
GROUP BY
visited_on
这个子查询首先从最内层子查询的结果中选择 visited_on
和 amount
列。WHERE
子句用于筛选出访问日期大于等于最早访问日期加上 6 天的记录,因为在最早访问日期加上 6 天之前的日期无法构成完整的 7 天时间段。然后使用 GROUP BY visited_on
对结果按访问日期进行分组,并再次计算每个分组(即每个 7 天时间段)的消费总额,将结果命名为 amount
。
3、最外层查询:
SELECT
visited_on,
amount,
ROUND(amount / 7, 2) AS average_amount
FROM
(中间子查询) sub2
ORDER BY
visited_on;
最外层查询从中间子查询的结果中选择 visited_on
、amount
列,并使用 ROUND(amount / 7, 2)
计算平均消费金额,保留两位小数,并将结果命名为 average_amount
。最后使用 ORDER BY visited_on
按访问日期升序排列结果。
六、复杂度分析
-
时间复杂度 :假设
Customer
表中有n
条记录。最内层子查询使用窗口函数,对于每一行数据,窗口函数的计算时间复杂度为 (O(1))(因为窗口范围固定为 7 行),所以最内层子查询的时间复杂度为 (O(n))。中间子查询对最内层子查询的结果进行分组和筛选,时间复杂度也为 (O(n))。最外层查询对中间子查询的结果进行简单的选择和排序,排序的时间复杂度为 (O(n log n))。综合来看,总的时间复杂度为 (O(n log n))。 -
空间复杂度:在查询过程中,我们没有使用额外的与数据规模相关的空间,只是对表中的数据进行了读取和处理,因此空间复杂度为 (O(1))。
七、测试用例验证
1、输入数据:
-- 插入示例数据
INSERT INTO Customer (customer_id, name, visited_on, amount) VALUES
(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);
2、预期输出:
+--------------+--------------+----------------+
| 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 |
+--------------+--------------+----------------+
3、验证过程:将上述 SQL 代码在数据库中运行,将得到的结果与预期输出进行对比,如果结果一致,则说明我们的代码实现是正确的。
感谢各位的阅读,后续将持续给大家讲解力扣中的算法题和数据库题,如果觉得这篇内容对你有帮助,别忘了点赞和关注,后续还有更多精彩的算法解析与你分享!