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)

现在我想知道每天使用什么设备发送了多少条消息。因此,我首先需要根据消息时间本身收集用于发送消息的设备(桌面、iosandroid)的每条消息。这意味着我需要 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 个按最新组值分组的表的主要内容,如果未能解决你的问题,请参考以下文章

SQL 检索最新记录,按唯一外键分组

SQL 查询根据日期获取最新的 3 次发送,然后对记录进行分组

SQL分组查询每个组的最新时间和上一个时间的某个字段值的差

复杂的 SQL 连接查询 - 获取最新行

SQL如何从每个连接表中检索最新结果

sql 分组取最新的数据sqlserver巧用row_number和partition by分组取top数据