1. 题目背景与核心需求
这道LeetCode数据库题目编号585,标题为"2016年的投资",属于典型的商业数据分析类SQL应用题。题目模拟了一个保险公司的投资数据场景,要求我们筛选出2016年满足特定条件的投资记录。
在实际业务中,保险公司需要定期分析投资组合的表现,识别那些在特定年份同时满足"金额达标"和"地理位置独特"条件的优质投资。这类分析能帮助公司评估投资策略有效性,发现潜在的高回报区域或客户群体。
2. 数据模型理解与表结构分析
根据题目描述,我们主要处理两个表:
2.1 保险表(Insurance)
+-------------+-------+ | Column Name | Type | +-------------+-------+ | pid | int | -- 保单ID | tiv_2015 | float | -- 2015年投资金额 | tiv_2016 | float | -- 2016年投资金额 | lat | float | -- 纬度 | lon | float | -- 经度 +-------------+-------+2.2 题目要求
编写SQL查询,找出满足以下两个条件的所有2016年投资记录:
- 该保单的2015年投资金额(tiv_2015)在至少一个其他保单中也存在(即有其他保单的2015年投资金额相同)
- 该保单的地理位置(lat, lon组合)在所有保单中是唯一的
最终输出2016年总投资金额,四舍五入到两位小数。
3. 解题思路拆解
3.1 条件一实现:相同2015年投资金额
我们需要找出那些tiv_2015值不唯一的记录。可以通过以下两种方式实现:
-- 方法1:使用窗口函数 SELECT pid, tiv_2015 FROM ( SELECT *, COUNT(*) OVER(PARTITION BY tiv_2015) as cnt FROM Insurance ) t WHERE cnt > 1 -- 方法2:使用子查询 SELECT pid, tiv_2015 FROM Insurance WHERE tiv_2015 IN ( SELECT tiv_2015 FROM Insurance GROUP BY tiv_2015 HAVING COUNT(*) > 1 )3.2 条件二实现:唯一地理位置
确保(lat, lon)组合在全表唯一:
SELECT pid, lat, lon FROM ( SELECT *, COUNT(*) OVER(PARTITION BY lat, lon) as loc_cnt FROM Insurance ) t WHERE loc_cnt = 13.3 综合查询方案
将两个条件用INTERSECT连接:
SELECT ROUND(SUM(tiv_2016), 2) as tiv_2016 FROM ( -- 满足条件1的记录 SELECT pid, tiv_2016 FROM Insurance WHERE tiv_2015 IN ( SELECT tiv_2015 FROM Insurance GROUP BY tiv_2015 HAVING COUNT(*) > 1 ) INTERSECT -- 满足条件2的记录 SELECT pid, tiv_2016 FROM ( SELECT *, COUNT(*) OVER(PARTITION BY lat, lon) as loc_cnt FROM Insurance ) t WHERE loc_cnt = 1 ) result4. 优化后的完整解决方案
经过多次测试和优化,最终的高效解决方案如下:
SELECT ROUND(SUM(tiv_2016), 2) AS tiv_2016 FROM Insurance WHERE tiv_2015 IN ( SELECT tiv_2015 FROM Insurance GROUP BY tiv_2015 HAVING COUNT(*) > 1 ) AND (lat, lon) IN ( SELECT lat, lon FROM Insurance GROUP BY lat, lon HAVING COUNT(*) = 1 )这个版本比使用INTERSECT的方案更简洁,执行效率也更高。关键在于理解:
- 第一个子查询筛选出tiv_2015不唯一的记录
- 第二个子查询筛选出地理位置唯一的记录
- 两个条件用AND连接,同时满足的记录才会被选中
5. 执行计划分析与性能考量
5.1 索引建议
对于大型数据集,建议在以下列上创建索引:
- tiv_2015:加速GROUP BY和IN子查询
- (lat, lon):复合索引加速地理位置唯一性检查
CREATE INDEX idx_tiv2015 ON Insurance(tiv_2015); CREATE INDEX idx_location ON Insurance(lat, lon);5.2 替代方案对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| 窗口函数+INTERSECT | 逻辑清晰,易于理解 | 需要多次扫描表,性能较差 |
| 子查询+AND连接 | 执行效率高 | 对复合条件理解要求较高 |
| 临时表方案 | 适合超大数据集 | 需要额外存储空间 |
6. 常见错误与调试技巧
6.1 典型错误示例
错误1:忘记四舍五入
-- 错误:直接SUM没有ROUND SELECT SUM(tiv_2016) AS tiv_2016错误2:地理位置判断逻辑错误
-- 错误:单独判断lat和lon的唯一性 WHERE lat IN (...) AND lon IN (...)6.2 调试建议
- 先分别验证两个条件的筛选结果是否正确
- 使用CTE(Common Table Expression)提高可读性:
WITH same_tiv2015 AS ( SELECT pid FROM Insurance WHERE tiv_2015 IN (...) ), unique_loc AS ( SELECT pid FROM (...) ) SELECT ROUND(SUM(tiv_2016), 2) FROM Insurance WHERE pid IN (SELECT pid FROM same_tiv2015) AND pid IN (SELECT pid FROM unique_loc)7. 业务场景延伸
这道题目实际模拟了金融行业的以下业务场景:
- 异常交易检测:通过金额重复性和地理位置唯一性识别可疑交易
- 客户价值分析:找出既有历史交易(2015)又有新交易(2016)的独特客户
- 区域市场分析:识别那些在特定地理区域的独特投资行为
在实际工作中,类似的SQL技巧还可应用于:
- 电商平台的重复订单检测
- 物流系统中的异常路线识别
- 社交网络的用户行为分析
8. 高级技巧与变体问题
8.1 使用EXISTS优化
对于某些数据库引擎,EXISTS可能比IN性能更好:
SELECT ROUND(SUM(tiv_2016), 2) AS tiv_2016 FROM Insurance i1 WHERE EXISTS ( SELECT 1 FROM Insurance i2 WHERE i1.pid != i2.pid AND i1.tiv_2015 = i2.tiv_2015 ) AND NOT EXISTS ( SELECT 1 FROM Insurance i3 WHERE i1.pid != i3.pid AND i1.lat = i3.lat AND i1.lon = i3.lon )8.2 题目变体思考
如果题目改为"找出2016年投资金额比2015年增长超过10%且地理位置唯一的保单",解决方案:
SELECT ROUND(SUM(tiv_2016), 2) AS tiv_2016 FROM Insurance WHERE (lat, lon) IN ( SELECT lat, lon FROM Insurance GROUP BY lat, lon HAVING COUNT(*) = 1 ) AND tiv_2016 > tiv_2015 * 1.19. 不同数据库方言的适配
9.1 MySQL/MariaDB
SELECT ROUND(SUM(tiv_2016), 2) AS tiv_2016 FROM Insurance i WHERE EXISTS ( SELECT 1 FROM Insurance WHERE pid != i.pid AND tiv_2015 = i.tiv_2015 ) AND NOT EXISTS ( SELECT 1 FROM Insurance WHERE pid != i.pid AND lat = i.lat AND lon = i.lon )9.2 PostgreSQL
可以利用PG特有的DISTINCT ON语法:
WITH dup_tiv AS ( SELECT tiv_2015 FROM Insurance GROUP BY tiv_2015 HAVING COUNT(*) > 1 ), unique_loc AS ( SELECT DISTINCT ON (lat, lon) pid FROM Insurance GROUP BY lat, lon, pid HAVING COUNT(*) = 1 ) SELECT ROUND(SUM(tiv_2016), 2) AS tiv_2016 FROM Insurance WHERE tiv_2015 IN (SELECT tiv_2015 FROM dup_tiv) AND pid IN (SELECT pid FROM unique_loc)10. 学习路径建议
要系统掌握这类SQL问题,建议的学习路线:
基础语法:
- SELECT, WHERE, GROUP BY, HAVING
- 聚合函数(SUM, COUNT等)
- JOIN操作(INNER, LEFT, RIGHT)
中级技巧:
- 子查询和CTE
- 窗口函数
- 日期处理函数
高级主题:
- 查询优化与执行计划
- 索引策略
- 分区表
实战平台:
- LeetCode数据库题库
- HackerRank SQL挑战
- 公司真实业务场景