2016 年的投资:两个「唯一性」条件用窗口计数
LeetCode 585 · 窗口计数、多条件过滤
面试怎么回答
两个条件都是和其他人比较,我用两个窗口 COUNT:COUNT() OVER (PARTITION BY TIV_2015) > 1 表示投保额有重复,COUNT() OVER (PARTITION BY LAT, LON) = 1 表示位置唯一,过滤后对 TIV_2016 求和并保留两位。比写两个关联子查询更清楚,也只扫一遍表。
先把这段讲顺;面试官追问时,再展开下面的原理和细节。
详细讲解
题意
保险表有每个投保人 2015、2016 年的投保额和所在城市的经纬度。把满足两个条件的投保人的 2016 年投保额加起来(保留两位小数):一是他 2015 年的投保额至少和另一个人相同;二是他所在的经纬度和其他所有人都不同。
表结构
- insurance:PID(主键)、TIV_2015、TIV_2016、LAT、LON
思路
两个条件都是「和别人比」:条件一是同样 TIV_2015 的人数 > 1;条件二是同样 (LAT, LON) 的人数 = 1。用两个窗口 COUNT 各自分区计数,一次扫描就能把两个计数都挂到每一行上,最后过滤求和。
参考解法
写法 1:用 CONCAT_WS 拼出位置再计数(注意没做 ROUND)
select sum(TIV_2016) TIV_2016
from (
select PID,TIV_2016,cnt,
count(*) over(partition by loc ) lcnt
from (
select PID,TIV_2016,
count(TIV_2015) over(partition by TIV_2015 ) cnt,
concat_ws(",",LAT,LON) loc
from insurance
)t1
)t2
where lcnt=1 and cnt!=1
写法 2:两个窗口计数直接按 (LAT, LON) 分区,更简洁
SELECT
ROUND(SUM(TIV_2016), 2) as TIV_2016
FROM(
SELECT
*,
count(*) over(partition by TIV_2015) as cnt_1,
count(*) over(partition by LAT, LON) as cnt_2
FROM
insurance
) a
WHERE a.cnt_1 > 1 AND a.cnt_2 < 2
易错点
- 先对 TIV_2015 去重再判断位置,会把有效记录丢掉;两个条件要在同一份明细上分别计数。
- 用字符串拼接经纬度要注意分隔符,否则 (1, 23) 和 (12, 3) 会拼成同一个值。
- 别忘了 ROUND 两位小数。