订阅更新

2016 年的投资:两个「唯一性」条件用窗口计数

SQL 实战 123 题 · 第 27 题 中等 约 2 分钟读完

LeetCode 585 · 窗口计数、多条件过滤

本文目录
  1. 面试怎么回答
  2. 题意
  3. 表结构
  4. 思路
  5. 参考解法
  6. 易错点

面试怎么回答

两个条件都是和其他人比较,我用两个窗口 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 两位小数。
刷题进度保存在当前浏览器