橫掃SQL面試題
📌 時間序列分組與合并問題
📚 橫掃SQL面試——時間序列分組與合并解析
🌟 核心問題類型
時間序列分組(Sessionization)
處理具有時間維度的連續數據流,根據特定規則(如時間間隔、屬性連續性)將相鄰記錄合并為邏輯組。
話不多說——直接上題:🎈🎈🎈🎈🎈🎈🎈
一、用戶訪問記錄會話劃分🎈🎈🎈
給定用戶的訪問記錄access_records
表,需要按照同一用戶相鄰兩次訪問記錄時間間隔小于等于60秒則認為兩次瀏覽記錄屬于同一會話的規則,為屬于同一會話的訪問記錄增加一個相同的會話id字段。
表名 | 字段名 | 字段含義 | 數據類型 |
---|---|---|---|
用戶訪問記錄表 | user_id | 用戶標識 | VARCHAR |
access_time | 訪問時間 | DATETIME | |
action | 訪問時執行的操作(如頁面瀏覽等) | VARCHAR |
user_id | access_time | action |
---|---|---|
User1 | 2025-03-31 10:00:00 | 瀏覽首頁 |
User1 | 2025-03-31 10:00:30 | 點擊商品詳情 |
User2 | 2025-03-31 10:10:00 | 瀏覽列表頁 |
User1 | 2025-03-31 10:01:00 | 返回首頁 |
User2 | 2025-03-31 10:10:30 | 加入購物車 |
結果預期?
user_id | access_time | action | session_id |
---|---|---|---|
User1 | 2025-03-31 10:00:00 | 瀏覽首頁 | 1 |
User1 | 2025-03-31 10:00:30 | 點擊商品詳情 | 1 |
User1 | 2025-03-31 10:01:00 | 返回首頁 | 1 |
User2 | 2025-03-31 10:10:00 | 瀏覽列表頁 | 2 |
User2 | 2025-03-31 10:10:30 | 加入購物車 | 2 |
???
原始數據 → 計算相鄰時間差 → 標記新會話 → 累加分組 → 分配ID↓ ↓ ↓ ↓
prev_access_time → new_session_start → session_groups → 最終結果
步驟1:獲取相鄰記錄時間差(CTE: prev_access_time
)?
with prev_access_time as (selectuser_id,access_time,lag(access_time) over (partition by user_id order by access_time) as prev_timefrom access_records
)
輸入數據 ?
user_id | access_time |
---|---|
UserA | 2024-01-01 08:00:00 |
UserA | 2024-01-01 08:00:50 |
UserA | 2024-01-01 08:02:00 |
中間表 prev_access_time
user_id | access_time | prev_time |
---|---|---|
UserA | 2024-01-01 08:00:00 | NULL |
UserA | 2024-01-01 08:00:50 | 2024-01-01 08:00:00 |
UserA | 2024-01-01 08:02:00 | 2024-01-01 08:00:50 |
LAG(access_time)
獲取同一用戶前一條記錄的訪問時間PARTITION BY user_id
確保按用戶獨立處理ORDER BY access_time
按時間順序排序
步驟2:標記新會話起點(CTE: new_session_start
)?
new_session_start as (selectuser_id,access_time,casewhen timestampdiff(second, prev_time, access_time) >= 60 or prev_time is nullthen 1else 0end as is_new_sessionfrom prev_access_time
)
中間表 new_session_start
user_id | access_time | is_new_session |
---|---|---|
UserA | 2024-01-01 08:00:00 | 1 (首條記錄) |
UserA | 2024-01-01 08:00:50 | 0 (間隔50秒) |
UserA | 2024-01-01 08:02:00 | 1 (間隔70秒) |
判斷邏輯
- 首條記錄:
prev_time IS NULL
→ 標記為1 - 間隔計算:
08:00:50 - 08:00:00 = 50秒
→<60秒
→ 標記0 - 間隔計算:
08:02:00 - 08:00:50 = 70秒
→>=60秒
→ 標記1
步驟3:生成會話分組(CTE: session_groups
)?
session_groups as (selectuser_id,access_time,sum(is_new_session) over (partition by user_id order by access_time) as session_groupfrom new_session_start
)
中間表 session_groups
user_id | access_time | session_group |
---|---|---|
UserA | 2024-01-01 08:00:00 | 1 |
UserA | 2024-01-01 08:00:50 | 1 (累加0) |
UserA | 2024-01-01 08:02:00 | 2 (累加1) |
SUM(is_new_session)
按用戶和時間順序累加標記- 同一會話組內的記錄具有相同的
session_group
值
步驟4:分配會話ID(主查詢)?
selectuser_id,access_time,dense_rank() over (partition by user_id order by session_group) as session_id
from session_groups;
最終結果
user_id | access_time | session_id |
---|---|---|
UserA | 2024-01-01 08:00:00 | 1 |
UserA | 2024-01-01 08:00:50 | 1 |
UserA | 2024-01-01 08:02:00 | 2 |
DENSE_RANK()
保證會話ID連續(無間隔)- 按
session_group
排序確保同一組ID相同
二、網站頁面瀏覽時長統計🎈🎈🎈
給定網站頁面瀏覽記錄,需要按照用戶在離開頁面后30分鐘內再次訪問同一頁面則認為是連續瀏覽的規則。
計算:
- 合并時長:將連續訪問合并為一個時間段,計算從首次開始時間到最后結束時間的總跨度
- 實際時長:累加每次獨立訪問的實際時長(不合并連續訪問)
- 增加會話id:屬于同一瀏覽的訪問記錄增加一個相同的會話id字段。
表名 | 字段名 | 字段含義 | 數據類型 |
---|---|---|---|
網站頁面瀏覽記錄表 | user_id | 用戶標識 | VARCHAR |
page_url | 頁面URL | VARCHAR | |
view_start_time | 開始瀏覽時間 | DATETIME | |
view_end_time | 結束瀏覽時間 | DATETIME |
user_id | page_url | view_start_time | view_end_time |
---|---|---|---|
User1 | https://example.com | 2025-04-01 09:00:00 | 2025-04-01 09:10:00 |
User1 | https://example.com | 2025-04-01 09:15:00 | 2025-04-01 09:20:00 |
User2 | https://example.net | 2025-04-01 10:00:00 | 2025-04-01 10:15:00 |
User1 | https://example.com | 2025-04-01 09:25:00 | 2025-04-01 09:30:00 |
User2 | https://example.net | 2025-04-01 10:30:00 | 2025-04-01 10:45:00 |
結果預期
user_id | page_url | view_start_time | view_end_time | merged_duration | total_real_duration |
---|---|---|---|---|---|
User1 | https://example.com | 2025-04-01 09:00:00 | 2025-04-01 09:30:00 | 30 | 20 (10+5+5) |
User2 | https://example.net | 2025-04-01 10:00:00 | 2025-04-01 10:15:00 | 15 | 15 |
User2 | https://example.net | 2025-04-01 10:30:00 | 2025-04-01 10:45:00 | 15 | 15 |
還是跟上題同樣的套路~😝😝😝
步驟 | 核心操作 | 關鍵函數/邏輯 |
---|---|---|
時間排序 | 獲取前次結束時間 | LAG() + 窗口排序 |
標記會話起點 | 判斷30分鐘間隔 | TIMESTAMPDIFF() + CASE |
生成會話組 | 累加標記值 | SUM() OVER() |
分配會話ID | 生成唯一連續編號 | DENSE_RANK() |
聚合結果 | 計算時間跨度和累加時長 | MIN() /MAX() /SUM() |
步驟1:排序并獲取前次結束時間?
with ordered_views as(selectuser_id,page_url,view_start_time,view_end_time,lag(view_end_time) over(partition by user_id,page_url order by view_start_time) as prev_end_timefrom table
)
中間表 ordered_views
?
user_id | page_url | view_start_time | view_end_time | prev_end_time |
---|---|---|---|---|
User1 | https://example.com | 2025-04-01 09:00:00 | 2025-04-01 09:10:00 | NULL |
User1 | https://example.com | 2025-04-01 09:15:00 | 2025-04-01 09:20:00 | 2025-04-01 09:10:00 |
User1 | https://example.com | 2025-04-01 09:25:00 | 2025-04-01 09:30:00 | 2025-04-01 09:20:00 |
User2 | https://example.net | 2025-04-01 10:00:00 | 2025-04-01 10:15:00 | NULL |
User2 | https://example.net | 2025-04-01 10:30:00 | 2025-04-01 10:45:00 | 2025-04-01 10:15:00 |
步驟2:標記新會話起點?
session_flags as(select *,casewhen prev_end_time IS NULL OR timestampdiff(minute, prev_end_time, view_start_time) > 30 then 1 else 0 end as is_new_sessionfrom ordered_views
)
中間表 session_flags
?
user_id | page_url | is_new_session |
---|---|---|
User1 | https://example.com | 1 |
User1 | https://example.com | 0 |
User1 | https://example.com | 0 |
User2 | https://example.net | 1 |
User2 | https://example.net | 1 |
步驟3:生成會話分組ID?
session_groups as(select*,sum(is_new_session) over(partition by user_id, page_url order by view_start_time) as session_groupfrom session_flags
)
中間表 session_groups
?
user_id | page_url | session_group |
---|---|---|
User1 | https://example.com | 1 |
User1 | https://example.com | 1 |
User1 | https://example.com | 1 |
User2 | https://example.net | 1 |
User2 | https://example.net | 2 |
步驟4:分配唯一會話ID?
session_ids AS (SELECT *,dense_rank() over (partition by user_id, page_url order by session_group ) as session_idfrom session_groups
)
中間表 session_ids
?
user_id | page_url | session_id |
---|---|---|
User1 | https://example.com | 1 |
User1 | https://example.com | 1 |
User1 | https://example.com | 1 |
User2 | https://example.net | 1 |
User2 | https://example.net | 2 |
步驟5:聚合結果并計算雙指標?
selectuser_id,page_url,session_id,min(view_start_time) AS view_start_time,max(view_end_time) AS view_end_time,TIMESTAMPDIFF(MINUTE, min(view_start_time), max(view_end_time)) AS merged_duration,sum(TIMESTAMPDIFF(MINUTE, view_start_time, view_end_time)) AS total_real_duration
from session_ids
group by user_id, page_url, session_id;
最終結果 ?
user_id | page_url | session_id | view_start_time | view_end_time | merged_duration | total_real_duration |
---|---|---|---|---|---|---|
User1 | https://example.com | 1 | 2025-04-01 09:00:00 | 2025-04-01 09:30:00 | 30 | 20 |
User2 | https://example.net | 1 | 2025-04-01 10:00:00 | 2025-04-01 10:15:00 | 15 | 15 |
User2 | https://example.net | 2 | 2025-04-01 10:30:00 | 2025-04-01 10:45:00 | 15 | 15 |
還是有些難度的哈~
-- 使用CTE(公共表表達式),命名為ordered_views
with ordered_views as (-- 從“網站頁面瀏覽記錄表”中選擇數據select user_id,page_url,view_start_time,view_end_time,-- 使用LAG窗口函數,獲取每個用戶和頁面URL分組下,按瀏覽開始時間排序后的上一條記錄的瀏覽結束時間lag(view_end_time) over (partition by user_id, page_url order by view_start_time) as prev_end_timefrom 網站頁面瀏覽記錄表
)
-- 第二個CTE,命名為session_flags
, session_flags as (-- 選擇ordered_views CTE中的所有列,并添加一個新列is_new_sessionselect *,-- 使用CASE語句判斷是否為新會話case -- 如果上一條記錄的結束時間為空(即當前是該用戶和頁面的第一條記錄)-- 或者當前記錄的開始時間與上一條記錄的結束時間間隔大于30分鐘when prev_end_time is null or timestampdiff(minute, prev_end_time, view_start_time) > 30 then 1 else 0 end as is_new_sessionfrom ordered_views
)
-- 第三個CTE,命名為session_groups
, session_groups as (-- 選擇session_flags CTE中的所有列,并添加一個新列session_groupselect *,-- 使用SUM窗口函數,對每個用戶和頁面URL分組下,按瀏覽開始時間排序后的is_new_session列進行累加sum(is_new_session) over (partition by user_id, page_url order by view_start_time) as session_groupfrom session_flags
)
-- 第四個CTE,命名為session_ids
, session_ids as (-- 選擇session_groups CTE中的所有列,并添加一個新列session_idselect *,-- 使用DENSE_RANK窗口函數,對每個用戶和頁面URL分組下,按session_group進行排名,得到會話IDdense_rank() over (partition by user_id, page_url order by session_group) as session_idfrom session_groups
)-- 主查詢
select user_id,page_url,session_id,-- 對于每個用戶、頁面和會話ID分組,獲取最小的瀏覽開始時間min(view_start_time) as view_start_time,-- 對于每個用戶、頁面和會話ID分組,獲取最大的瀏覽結束時間max(view_end_time) as view_end_time,-- 計算合并后的會話時長(以分鐘為單位)timestampdiff(minute, min(view_start_time), max(view_end_time)) as merged_duration,-- 計算每個會話內實際的瀏覽時長總和(以分鐘為單位)sum(timestampdiff(minute, view_start_time, view_end_time)) as total_real_duration
from session_ids
-- 按照用戶ID、頁面URL和會話ID進行分組
group by user_id, page_url, session_id
-- 按照用戶ID、頁面URL和會話ID進行排序
order by user_id, page_url, session_id;
三、公交乘客乘車時間分析🎈🎈🎈
根據公交乘客乘車記錄,按照 “下車后90分鐘內再次上車視為同一次出行” 的規則,完成以下任務:
- 分配出行ID:為每個乘客的每次連續出行分配唯一ID
- 計算雙指標:
- 合并時長:從首次上車到最后一次下車的總時間跨度
- 實際時長:累加各次乘車的獨立時長
表名 | 字段名 | 字段含義 | 數據類型 |
---|---|---|---|
公交乘車記錄表 | passenger_id | 乘客標識 | VARCHAR |
boarding_time | 上車時間 | DATETIME | |
alighting_time | 下車時間 | DATETIME |
passenger_id | boarding_time | alighting_time |
---|---|---|
P001 | 2025-04-01 08:00:00 | 2025-04-01 08:30:00 |
P001 | 2025-04-01 08:40:00 | 2025-04-01 09:00:00 |
P002 | 2025-04-01 09:30:00 | 2025-04-01 10:00:00 |
P001 | 2025-04-01 09:10:00 | 2025-04-01 09:30:00 |
P002 | 2025-04-01 10:40:00 | 2025-04-01 11:00:00 |
結果預期 ?
passenger_id | trip_id | boarding_time | alighting_time | merged_duration | total_actual_duration |
---|---|---|---|---|---|
P001 | 1 | 2025-04-01 08:00:00 | 2025-04-01 09:30:00 | 90 | 70 (30+20+20) |
P002 | 1 | 2025-04-01 09:30:00 | 2025-04-01 10:00:00 | 30 | 30 |
P002 | 2 | 2025-04-01 10:40:00 | 2025-04-01 11:00:00 | 20 | 20 |
換湯不換藥~😂
步驟1:排序并獲取前次下車時間?
中間表 ordered_records
?
passenger_id | boarding_time | alighting_time | prev_alighting_time |
---|---|---|---|
P001 | 2025-04-01 08:00:00 | 2025-04-01 08:30:00 | NULL |
P001 | 2025-04-01 08:40:00 | 2025-04-01 09:00:00 | 2025-04-01 08:30:00 |
P001 | 2025-04-01 09:10:00 | 2025-04-01 09:30:00 | 2025-04-01 09:00:00 |
P002 | 2025-04-01 09:30:00 | 2025-04-01 10:00:00 | NULL |
P002 | 2025-04-01 10:40:00 | 2025-04-01 11:00:00 | 2025-04-01 10:00:00 |
步驟2:標記新出行起點?
中間表 session_flags
?
passenger_id | is_new_session |
---|---|
P001 | 1 (首條記錄) |
P001 | 0 (間隔10分鐘) |
P001 | 0 (間隔10分鐘) |
P002 | 1 |
P002 | 1 (間隔40分鐘) |
步驟3:生成出行分組ID?
中間表 session_groups
?
passenger_id | session_group |
---|---|
P001 | 1 |
P001 | 1 |
P001 | 1 |
P002 | 1 |
P002 | 2 |
步驟4:分配出行ID并聚合結果?
最終結果 ?
passenger_id | boarding_time | alighting_time | total_riding_duration | total_actual_duration |
---|---|---|---|---|
P001 | 2025-04-01 08:00:00 | 2025-04-01 09:30:00 | 90 | 70 (30+20+20) |
P002 | 2025-04-01 09:30:00 | 2025-04-01 10:00:00 | 30 | 30 |
P002 | 2025-04-01 10:40:00 | 2025-04-01 11:00:00 | 20 | 20 |
-- 使用CTE(公共表表達式),命名為ordered_records
with ordered_records as (-- 從“公交乘車記錄表”中選擇數據select passenger_id,boarding_time,alighting_time,-- 使用LAG窗口函數,獲取每個乘客分組下,按上車時間排序后的上一條記錄的下車時間lag(alighting_time) over (partition by passenger_id order by boarding_time) as prev_alighting_timefrom 公交乘車記錄表
)
-- 第二個CTE,命名為session_flags
, session_flags as (-- 選擇ordered_records CTE中的所有列,并添加一個新列is_new_sessionselect *,-- 使用CASE語句判斷是否為新會話case -- 如果上一條記錄的下車時間為空(即當前是該乘客的第一條記錄)-- 或者當前記錄的上車時間與上一條記錄的下車時間間隔大于90分鐘when prev_alighting_time is null or timestampdiff(minute, prev_alighting_time, boarding_time) > 90 then 1 else 0 end as is_new_sessionfrom ordered_records
)
-- 第三個CTE,命名為session_groups
, session_groups as (-- 選擇session_flags CTE中的所有列,并添加一個新列session_groupselect *,-- 使用SUM窗口函數,對每個乘客分組下,按上車時間排序后的is_new_session列進行累加sum(is_new_session) over (partition by passenger_id order by boarding_time) as session_groupfrom session_flags
)-- 主查詢
select passenger_id,-- 對于每個乘客和會話組分組,獲取最小的上車時間min(boarding_time) as boarding_time,-- 對于每個乘客和會話組分組,獲取最大的下車時間max(alighting_time) as alighting_time,-- 計算總的乘車時長(以分鐘為單位)timestampdiff(minute, min(boarding_time), max(alighting_time)) as total_riding_duration,-- 計算每個會話組內實際的乘車時長總和(以分鐘為單位)sum(timestampdiff(minute, boarding_time, alighting_time)) as total_actual_duration
from session_groups
-- 按照乘客ID和會話組進行分組
group by passenger_id, session_group
-- 按照乘客ID和會話組進行排序
order by passenger_id, session_group;
熟悉的套路😂
步驟 | 核心操作 | 關鍵函數/邏輯 |
---|---|---|
數據排序 | 按乘客和時間排序 | LAG() + 窗口排序 |
標記新出行起點 | 判斷90分鐘間隔 | TIMESTAMPDIFF() + CASE |
生成分組ID | 累加標記值 | SUM() OVER() |
聚合結果 | 計算時間跨度和實際時長 | MIN() /MAX() /SUM() |
后面大家可以自己做一做 博主就不啰嗦啦~ 答案貼在最后啦 做完再看哈🤣
📚 時間序列分組與合并擴展題庫
題目一:用戶登錄會話合并
題干
合并用戶登錄記錄,若兩次登錄間隔 ≤15分鐘 視為同一會話,計算每個會話的持續時間和登錄次數。
表結構
表名 | 字段名 | 字段含義 | 數據類型 |
---|---|---|---|
user_login | user_id | 用戶ID | VARCHAR |
login_time | 登錄時間 | DATETIME |
示例數據
user_id | login_time |
---|---|
U1001 | 2024-03-01 08:00:00 |
U1001 | 2024-03-01 08:05:00 |
U1001 | 2024-03-01 08:25:00 |
期望輸出
user_id | session_start | session_end | duration_min | login_count |
---|---|---|---|---|
U1001 | 2024-03-01 08:00:00 | 2024-03-01 08:05:00 | 5 | 2 |
U1001 | 2024-03-01 08:25:00 | 2024-03-01 08:25:00 | 0 | 1 |
題目二:設備故障時段統計
題干
合并設備的連續故障時段(故障狀態代碼=500),若兩次故障間隔 ≤10分鐘 視為同一故障事件。
表結構
表名 | 字段名 | 字段含義 | 數據類型 |
---|---|---|---|
device_log | device_id | 設備ID | VARCHAR |
log_time | 日志時間 | DATETIME | |
status_code | 狀態碼 | INT |
示例數據
device_id | log_time | status_code |
---|---|---|
D001 | 2024-03-01 10:00:00 | 500 |
D001 | 2024-03-01 10:05:00 | 500 |
D001 | 2024-03-01 10:20:00 | 500 |
期望輸出
device_id | start_time | end_time | error_duration_min |
---|---|---|---|
D001 | 2024-03-01 10:00:00 | 2024-03-01 10:05:00 | 5 |
D001 | 2024-03-01 10:20:00 | 2024-03-01 10:20:00 | 0 |
題目三:訂單支付超時分析
題干
合并訂單的創建和支付操作,若支付時間在創建后 30分鐘內 視為有效支付,統計每個訂單的實際支付處理時長。
表結構
表名 | 字段名 | 字段含義 | 數據類型 |
---|---|---|---|
orders | order_id | 訂單ID | VARCHAR |
create_time | 創建時間 | DATETIME | |
pay_time | 支付時間 | DATETIME |
示例數據
order_id | create_time | pay_time |
---|---|---|
O1001 | 2024-03-01 12:00:00 | 2024-03-01 12:05:00 |
O1001 | 2024-03-01 12:10:00 | 2024-03-01 12:30:00 |
O1002 | 2024-03-01 13:00:00 | NULL |
期望輸出
order_id | valid_pay_duration_min |
---|---|
O1001 | 25 (12:05-12:30) |
O1002 | 0 |
題目四:交通卡口連續通行分析
題干
合并車輛通過相鄰卡口的記錄,若通過時間間隔 ≤5分鐘 且行駛方向相同,視為連續通行,計算平均速度。
表結構
表名 | 字段名 | 字段含義 | 數據類型 |
---|---|---|---|
traffic_log | car_plate | 車牌號 | VARCHAR |
direction | 行駛方向 | VARCHAR | |
pass_time | 通過時間 | DATETIME | |
location | 卡口位置 | VARCHAR |
示例數據
car_plate | direction | pass_time | location |
---|---|---|---|
滬A12345 | 東向 | 2024-03-01 09:00:00 | 卡口A |
滬A12345 | 東向 | 2024-03-01 09:03:00 | 卡口B |
滬A12345 | 東向 | 2024-03-01 09:10:00 | 卡口C |
期望輸出
car_plate | start_time | end_time | avg_speed_kmh |
---|---|---|---|
滬A12345 | 2024-03-01 09:00:00 | 2024-03-01 09:10:00 | 48 |
答案:
題目一:用戶登錄會話合并
-- 使用公共表表達式(CTE),命名為 ordered_logins
with ordered_logins as (-- 從 user_login 表中選擇數據select user_id,login_time,-- 使用 LAG 窗口函數,在每個 user_id 分區內,按照 login_time 排序-- 獲取當前記錄的上一條記錄的 login_time,命名為 prev_loginlag(login_time) over (partition by user_id order by login_time) as prev_loginfrom user_login
)
-- 第二個 CTE,命名為 session_flags
, session_flags as (-- 選擇 ordered_logins CTE 中的所有列,并添加一個新列 is_new_sessionselect *,-- 使用 CASE 語句判斷是否為新的會話case -- 如果上一條記錄的登錄時間為空(即當前是該用戶的第一條登錄記錄)-- 或者當前記錄的登錄時間與上一條記錄的登錄時間間隔大于 15 分鐘when prev_login is null or timestampdiff(minute, prev_login, login_time) > 15 then 1 else 0 end as is_new_sessionfrom ordered_logins
)
-- 第三個 CTE,命名為 session_groups
, session_groups as (-- 選擇 session_flags CTE 中的所有列,并添加一個新列 session_idselect *,-- 使用 SUM 窗口函數,在每個 user_id 分區內,按照 login_time 排序-- 對 is_new_session 列進行累加,得到每個會話的唯一標識 session_idsum(is_new_session) over (partition by user_id order by login_time) as session_idfrom session_flags
)
-- 主查詢
select user_id,-- 對于每個用戶和會話 ID 分組,獲取最小的登錄時間作為會話開始時間min(login_time) as session_start,-- 對于每個用戶和會話 ID 分組,獲取最大的登錄時間作為會話結束時間max(login_time) as session_end,-- 計算會話的持續時間(以分鐘為單位)timestampdiff(minute, min(login_time), max(login_time)) as duration_min,-- 統計每個會話內的登錄次數count(*) as login_count
from session_groups
-- 按照用戶 ID 和會話 ID 進行分組
group by user_id, session_id;
題目二:設備故障時段統計
-- 使用公共表表達式(CTE),命名為 ordered_logs
with ordered_logs as (-- 從 device_log 表中選擇數據,且只選擇狀態碼為 500 的記錄select device_id,log_time,-- 使用 LAG 窗口函數,在每個 device_id 分區內,按照 log_time 排序-- 獲取當前記錄的上一條記錄的 log_time,命名為 prev_loglag(log_time) over (partition by device_id order by log_time) as prev_logfrom device_logwhere status_code = 500
)
-- 第二個 CTE,命名為 session_flags
, session_flags as (-- 選擇 ordered_logs CTE 中的所有列,并添加一個新列 is_new_sessionselect *,-- 使用 CASE 語句判斷是否為新的故障時段case -- 如果上一條記錄的日志時間為空(即當前是該設備的第一條故障記錄)-- 或者當前記錄的日志時間與上一條記錄的日志時間間隔大于 10 分鐘when prev_log is null or timestampdiff(minute, prev_log, log_time) > 10 then 1 else 0 end as is_new_sessionfrom ordered_logs
)
-- 第三個 CTE,命名為 session_groups
, session_groups as (-- 選擇 session_flags CTE 中的所有列,并添加一個新列 session_idselect *,-- 使用 SUM 窗口函數,在每個 device_id 分區內,按照 log_time 排序-- 對 is_new_session 列進行累加,得到每個故障時段的唯一標識 session_idsum(is_new_session) over (partition by device_id order by log_time) as session_idfrom session_flags
)
-- 主查詢
select device_id,-- 對于每個設備和故障時段 ID 分組,獲取最小的日志時間作為故障開始時間min(log_time) as start_time,-- 對于每個設備和故障時段 ID 分組,獲取最大的日志時間作為故障結束時間max(log_time) as end_time,-- 計算故障時段的持續時間(以分鐘為單位)timestampdiff(minute, min(log_time), max(log_time)) as error_duration_min
from session_groups
-- 按照設備 ID 和故障時段 ID 進行分組
group by device_id, session_id;
題目三:訂單支付超時分析
select order_id,-- 使用 COALESCE 函數,若第一個參數不為 NULL,則返回第一個參數的值,否則返回第二個參數的值-- 這里計算有效支付時長(以分鐘為單位)coalesce(-- 計算在創建時間和支付時間間隔不超過 30 分鐘的情況下,最大支付時間和最小支付時間的差值(以分鐘為單位)timestampdiff(minute, min(case when timestampdiff(minute, create_time, pay_time) <= 30 then pay_time end), max(case when timestampdiff(minute, create_time, pay_time) <= 30 then pay_time end)), 0) as valid_pay_duration_min
from orders
-- 按照訂單 ID 進行分組
group by order_id;
題目四:交通卡口連續通行分析
-- 使用公共表表達式(CTE),命名為 ordered_passes
with ordered_passes as (-- 從 traffic_log 表中選擇數據select car_plate,direction,pass_time,-- 使用 LAG 窗口函數,在每個 car_plate 和 direction 分區內,按照 pass_time 排序-- 獲取當前記錄的上一條記錄的 pass_time,命名為 prev_passlag(pass_time) over (partition by car_plate, direction order by pass_time) as prev_passfrom traffic_log
)
-- 第二個 CTE,命名為 session_flags
, session_flags as (-- 選擇 ordered_passes CTE 中的所有列,并添加一個新列 is_new_groupselect *,-- 使用 CASE 語句判斷是否為新的連續通行組case -- 如果上一條記錄的通過時間為空(即當前是該車牌和方向的第一條記錄)-- 或者當前記錄的通過時間與上一條記錄的通過時間間隔大于 5 分鐘when prev_pass is null or timestampdiff(minute, prev_pass, pass_time) > 5 then 1 else 0 end as is_new_groupfrom ordered_passes
)
-- 第三個 CTE,命名為 session_groups
, session_groups as (-- 選擇 session_flags CTE 中的所有列,并添加一個新列 group_idselect *,-- 使用 SUM 窗口函數,在每個 car_plate 和 direction 分區內,按照 pass_time 排序-- 對 is_new_group 列進行累加,得到每個連續通行組的唯一標識 group_idsum(is_new_group) over (partition by car_plate, direction order by pass_time) as group_idfrom session_flags
)
-- 第四個 CTE,命名為 aggregated
, aggregated as (-- 選擇 car_plate、計算每個連續通行組的最小通過時間、最大通過時間和通過次數select car_plate,min(pass_time) as start_time,max(pass_time) as end_time,count(*) as num_passesfrom session_groups-- 按照 car_plate、direction 和 group_id 進行分組group by car_plate, direction, group_id
)
-- 主查詢
select car_plate,start_time,end_time,-- 計算平均速度(單位:km/h),假設每次通過間隔 4 公里((num_passes - 1) * 4)-- 用通過間隔距離除以總時間(以小時為單位),并四舍五入到整數round((num_passes - 1) * 4 / (timestampdiff(minute, start_time, end_time) / 60.0), 0) as avg_speed_kmh
from aggregated
-- 只選擇通過次數大于 1 的記錄
where num_passes > 1;