shaoxyz

你看上去像是体面人

0%

SQL联表查询辅助表去重

老东家运营妹子要做数据分析提的一个需求

统计所有用户在注册当天是否完成过消费

涉及用户表和订单表的联表查询, 主要字段为用户id,注册时间,第一笔消费时间

我们的MySQL版本是5.7.18

先说遇到的问题

首先,用 GROUP BY 会触发 ONLY_FULL_GROUP_BY

1
2
3
4
SELECT u.id, u.created_at, o.created_at 
FROM user u
LEFT JOIN orders o ON o.buyer_id = user.id AND o.state = 1
GROUP BY o.buyer_id

简单来说,MySQL5.7.5之后,SQL需要检测函数依赖关系,SELECT & ORDER BY & HAVING 不能引用非聚合列

之所以要这么做是为了避免当出现不正确和不可预测的查询结果时MySQL没有任何报错和警告,这里有具体例子与说明

再者,用MIN || MAX || ANY_VALUE将非聚合列包起来也是可行的,但实测在数据量较大的情况下很慢。

1
2
3
SELECT MIN(u.id), MIN(u.created_at), MIN(o.created_at) 
FROM user u LEFT JOIN orders o ON o.buyer_id = u.id AND o.state = 1
GROUP BY o.buyer_id

推测是这么做依然没有真正意义上的去重,而是类似与把原来的查询作为子查询又过滤了一遍

想要的效果是:直接限制辅助表只取一行

stackoverflow上找到了最佳答案

最后修改SQL如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SELECT 
user.id,
CASE
WHEN DATE(o.created_at) = DATE(u.created_at) THEN 1
ELSE 0
END AS '是否在注册当天购买',
DATE(u.created_at) AS '注册时间'
FROM user u
LEFT JOIN orders o ON o.id = ( # 注意这里必须是主键
SELECT o2.id
FROM orders o2
WHERE o2.buyer_id = u.id
AND o2.state = 1
ORDER BY o2.id
LIMIT 1
)

用户表30w
订单表70w
时间大概在10~20ms, 可以接受