SQL 连接 3 个按最新组值分组的表
Posted
技术标签:
【中文标题】SQL 连接 3 个按最新组值分组的表【英文标题】:SQL join 3 tables grouped with latest group value 【发布时间】:2015-02-04 08:53:58 【问题描述】:我有 3 张桌子。具有以下结构的用户、消息和用户分析:
user (userId) - 包含所有用户 message (messageId(pk),userId(fk),time) - 包含所有消息 user_analytics (user_analyticsId(pk),userId(fk),device,time) - 包含在连接时收集的数据user : messages (1:n) user : device (1:n)
现在我想知道每天使用什么设备发送了多少条消息。因此,我首先需要根据消息时间本身收集用于发送消息的设备(桌面、ios、android)的每条消息。这意味着我需要 user_analytics.time
我看到了很多关于 best-n-per-group 的解决方案,但我没有让它发挥作用。
我只让它与一个需要 20 秒的子查询一起工作(user_analytics 拥有 100k 条记录和 3k 条消息......所以不多):
select date_format(m.time,'%Y-%m-%d') as date,
count(*) as message_count,
ua.device
from message m,
user u left join user_analytics ua on (
u.userId = ua.userId and
ua.user_analyticsId = ( select max(user_analyticsId)
from user_analytics
where userId = m.userId and
time < m.time))
where m.userId = u.userId
group by 1,3;
但这看起来非常低效。还有其他方法可以达到同样的效果吗?
更新: 我忘了提到我在用户表上有一个重要条件。这就是为什么我需要加入这个表。
我创建了一个 sql fiddle 给你一个例子。现在我已经实现了Jaguar Chang's 解决方案,它比我的快 100 倍:
sql fiddle
【问题讨论】:
在我回答问题之前,我需要您解决一些含糊不清的问题。如果您只想“现在每天使用什么设备发送多少条消息”,您的查询似乎没有回答这个问题。您能否更明确地说明您要获得哪些数据?接下来,这个“user_analytics.time 改用不相关的子查询 下面的答案看起来不错,但要获得更多帮助,请考虑遵循这个简单的两步操作过程: 1. 如果您还没有这样做,请提供适当的 DDL(和/或 sqlfiddle),以便我们可以更容易地复制问题。 2. 如果您尚未这样做,请提供与步骤 1 中提供的信息相对应的所需结果集。 "这比我的快 100 倍:" 并产生预期的结果? @Strawberry:是的,现在需要 0.7 秒(所以感觉是之前的 20 秒的 100 倍)我想知道当数据增长时它会如何执行。 【参考方案1】:没有必要加入用户表,因此您可以像这样简化代码:
select date_format(m.time,'%Y-%m-%d') as date,
count(*) as message_count,
ua.device
from message m,
left join user_analytics ua on (
m.userId = ua.userId and
ua.user_analyticsId = ( select max(user_analyticsId)
from user_analytics
where userId = m.userId and
time < m.time))
group by 1,3;
这可能不够有效,但你可以试试这个:
select date_format(t2.time,'%Y-%m-%d') as date,
count(*) as message_count,
t2.last_device
from
(select
@device :=
if(@uid = userid,
if(tbl = 'm' ,@device, device),
if(@uid := userid,device,device)) as last_device
,t1.*
from
(select @device := '' , @uid :=0) as t0
join
(select 'ua' as tbl,userid,time,device from user_analytics
union all
select 'm' as tbl,userid,time,null as device from messages
) as t1
order by userid,time
) as t2
where tbl='m'
group by 1,3;
我猜你最初的目的是通过设备上的连接次数来划分消息,所以将消息和连接记录按时间序列排序在一起,然后你可以得到每条消息最后一次连接使用的设备。
我认为这种方法会非常有效,因为 100k+3k 排序将比 3k*100k*100k 连接操作快得多
一个测试Sql Fiddle Demo。
【讨论】:
不错的解决方案 thx,一旦数据增长(分析表每年增长 10 万),您是否发现排序有问题? 我认为历史数据永远不会改变。所以没有必要使用这些历史数据,只需将结果扩展到您的消息表即可。【参考方案2】:那么这个比较如何(随着数据集的增长可能不太有利......)
SELECT DATE(message_time) dt
, b.device
, COUNT(*)
FROM
(
SELECT m.id message_id
, m.userid
, m.time message_time
, MAX(um.time) device_time
FROM messages m
JOIN user_analytics um
ON um.userid = m.userid
AND um.time <= m.time
GROUP
BY m.id
) a
JOIN user_analytics b
ON b.userid = a.userid
AND b.time = a.device_time
GROUP
BY DATE(message_time)
, b.device;
【讨论】:
感谢您的解决方案。它可以工作(预计没有设备信息的消息应该显示为 device: null ,正如您在我的小提琴中看到的那样)。我认为将 'JOIN user_analytics um' 更改为 'LEFT JOIN user_analytics um' 会解决它)。我在 100k 上试了一下,查询需要 15 秒。 是的 - 在这种情况下加入正确 - 但您可以轻松地交换查询的两个部分。分析表中 (userid,time) 的索引将有很大帮助【参考方案3】:不太确定您想要什么,但您说您的查询正在运行,您只是想要一种有效的方式。所以我们试试这个n让我知道它是否有效
select date_format(m.time,'%Y-%m-%d') as date,
count(*) as message_count,
ua.device, max(ua.user_analyticsId)
from message m
INNER JOIN
user u ON
m.userId = u.userId
LEFT JOIN
user_analytics ua ON
u.userId = ua.userId and
where
ua.time<m.time
group by 1,3;
如果需要,您可以忽略添加的新列,方法是将整个查询放入另一个查询的块中
select date, message_count, device from (
select date_format(m.time,'%Y-%m-%d') as date,
count(*) as message_count,
ua.device, max(ua.user_analyticsId)
from message m
INNER JOIN
user u ON
m.userId = u.userId
LEFT JOIN
user_analytics ua ON
u.userId = ua.userId and
where
ua.time<m.time
group by 1,3) A ;
【讨论】:
我会感谢上述查询的纠正和信息丰富的 cmets(如果有的话) 您的查询返回错误代码 1054,have 子句中的未知列 ua.user_analyticsId。 感谢您的努力,但它不会返回所需的结果,而且我的 100k 数据也需要 15 秒(我添加了一个演示所需结果的 sql fiddle)以上是关于SQL 连接 3 个按最新组值分组的表的主要内容,如果未能解决你的问题,请参考以下文章