如何根据多个排序列选择每组的顶行?

时间:2016-08-10 13:28:14

标签: mysql sql select group-by aggregate-functions

我有一个如下所示的查询:

SELECT time_start, some_count
    FROM foo
    WHERE user_id = 1
    AND DATE(time_start) = '2016-07-27'
    ORDER BY some_count DESC, time_start DESC LIMIT 1;

这样做会让我返回一行,其中some_count是user_id = 1的最高计数。它还为我提供了some_count的最新时间戳,因为some_count对于多个time_start值可能相同,我想要最新的user_id

现在我要做的是运行一个查询,该查询会针对特定日期至少发生过一次的每个2016-07-27计算出来,在本例中为user_id。最终它可能需要GROUP BY,因为我正在寻找每outline: 3px solid #725b44; 组的最大值

编写这种性质的查询的最佳方法是什么?

8 个答案:

答案 0 :(得分:2)

我正在分享我的两种方法。

方法#1(可扩展):

使用MySQL user_defined variables

SELECT
    t.user_id,
    t.time_start,
    t.time_stop,
    t.some_count
FROM 
(
    SELECT
        user_id,
        time_start,
        time_stop,
        some_count,
        IF(@sameUser = user_id, @rn := @rn + 1,
             IF(@sameUser := user_id, @rn := 1, @rn := 1)
        ) AS row_number

    FROM    foo
    CROSS JOIN (
        SELECT
            @sameUser := - 1,
            @rn := 1
    ) var
    WHERE   DATE(time_start) = '2016-07-27'
    ORDER BY    user_id,    some_count DESC,    time_stop DESC
) AS t
WHERE t.row_number <= 1
ORDER BY t.user_id;

可扩展,因为如果您想为每个用户提供最新的n行,那么您只需更改此行:

... WHERE t.row_number <= n...

如果查询提供了预期的结果

,我可以稍后添加解释

方法#2 :(不可扩展)

使用INNER JOIN and GROUP BY

SELECT 
 F.user_id,
 F.some_count,
 F.time_start,
 MAX(F.time_stop) AS max_time_stop
FROM foo F
INNER JOIN 
(
    SELECT 
        user_id,
        MAX(some_count) AS max_some_count
    FROM foo
    WHERE DATE(time_start) = '2016-07-27'
    GROUP BY user_id
) AS t
ON F.user_id = t.user_id AND F.some_count = t.max_some_count
WHERE DATE(time_start) = '2016-07-27'
GROUP BY F.user_id

答案 1 :(得分:1)

您可以使用BottomUp

btn.AutoSizeMode = AutoSizeMode.GrowOnly;
btn.AutoSize = true;
btn.Dock = DockStyle.Fill;

NOT EXISTS()将仅选择记录为另一个记录较大的记录或另一记录具有相同的计数但较新的SELECT * FROM foo t WHERE (DATE(time_start) = '2016-07-27' OR DATE(time_stop) = '2016-07-27') AND NOT EXISTS(SELECT 1 FROM foo s WHERE t.user_id = s.user_id AND (s.some_count > t.some_count OR (s.some_count = t.some_count AND s.time_stop > t.time_stop))) 不存在的记录。

答案 2 :(得分:1)

您可以将原始查询用作WHERE子句中的相关子查询。

SELECT user_id, time_stop, some_count
FROM foo f
WHERE f.id = (
    SELECT f1.id
    FROM foo f1
    WHERE f1.user_id = f.user_id -- correlate
    AND DATE(f1.time_start) = '2016-07-27'
    ORDER BY f1.some_count DESC, f1.time_stop DESC LIMIT 1
)

MySQL应该能够为每个不同的user_id缓存子查询的结果。

另一种方法是使用嵌套的GROUP BY查询:

select f.user_id, f.some_count, max(f.time_stop) as time_stop
from (
    select f.user_id, max(f.some_count) as some_count
    from foo f
    where date(f.time_start) = '2016-07-27'
    group by f.user_id
) sub
join foo f using(user_id, some_count)
where date(f.time_start) = '2016-07-27'
group by f.user_id, f.some_count

答案 3 :(得分:1)

SELECT user_id,
       some_count,
       max(time_start) AS time_start
FROM
  (SELECT a.*
   FROM foo AS a
   INNER JOIN
     (SELECT user_id,
             max(some_count) AS some_count
      FROM foo
      WHERE DATE(time_start) = '2016-07-27'
      GROUP BY user_id) AS b ON a.user_id = b.user_id
   AND a.some_count = b.some_count) AS c
GROUP BY user_id,
         some_count;

从内到外解释:最内层的表(b)将为每位用户提供最大的some_count。这还不够,因为你想要两列的最大值 - 所以我将它与完整的表(a)连接起来以获得具有这些最大值(c)的记录,并从我那里获取每个用户/ some_count组合的最大time_start。

答案 4 :(得分:1)

<强>策略

通常,找到最大值而不是对记录组进行排序会更有效。在这种情况下,排序是一个整数(some_count),后跟一个日期/时间(time_start) - 所以要找到一个最大行,我们需要以某种方式组合这些。

这样做的一个简单方法是将两者组合成一个字符串,但通常会将"4"的字符串比较值高于"12"。使用LPAD添加前导零可以轻松克服这一问题,因此4变为"0000000004",在字符串比较中低于"0000000012"。假设time_startDATETIME字段,可以简单地将其附加到此字段以进行辅助排序,因为其字符串转换会产生可排序的格式(yyyy-mm-dd hh:MM:ss)。

<强> SQL

使用此策略,我们可以通过简单的子选择来限制:

SELECT time_start, some_count
FROM foo f1
WHERE DATE(time_start) = '2016-07-27'
  AND CONCAT(LPAD(some_count, 10, '0'), time_start) = 
      (SELECT MAX(CONCAT(LPAD(some_count, 10, '0'), time_start))
       FROM foo f2
       WHERE DATE(f2.time_start) = '2016-07-27'
         AND f2.user_id = f1.user_id);

<强>演示

Rextester演示:http://rextester.com/HCGY1362

答案 5 :(得分:0)

我相信,你不需要为查询做任何花哨的事情。 只需按 user_id 按升序对表格进行排序,然后按 some_count time_start 按降序排序,并从有序表格中选择预期字段GROUP BY USER_ID 即可。这很简单。如果有效,请尝试告诉我。

SELECT user_id, some_count, time_start
FROM (SELECT * FROM foo ORDER BY user_id ASC, some_count DESC, time_start DESC)sorted_foo
WHERE DATE( time_start ) = '2016-07-27'
GROUP BY user_id

答案 6 :(得分:0)

您的问题可以通过窗口函数解决,但MySQL不支持此类功能。

我有两个解决方案。一个是模拟窗口函数,另一个是你在MySQL中编写一些查询来解决这些情况的常用方法。

这是第一个,我回答this question

-- simulates the window function
-- first_value(<col>) over(partition by user_id order by some_count DESC, time_start DESC)
SELECT
  user_id,
  substring_index(group_concat(time_start ORDER BY some_count DESC, time_start DESC), ',', 1) time_start,
  substring_index(group_concat(some_count ORDER BY some_count DESC, time_start DESC), ',', 1) some_count
FROM foo
WHERE DATE(time_start) = '2016-07-27'
GROUP BY user_id
;

基本上,您按user_id对数据进行分组,并使用,分隔符连接指定列中的所有值,按所需的列排序,每个组,然后只对第一个有序的子字符串进行子串值。这不是一种最佳方法......

那是第二个,我回答this question

SELECT 
  user_id,
  some_count,
  MAX(time_start) time_start
FROM foo outq
WHERE 1=1
  AND DATE(time_start) = '2016-07-27'
  AND NOT EXISTS
  (
    SELECT 1
    FROM foo 
    WHERE 1=1
      AND user_id    = outq.user_id
      AND some_count > outq.some_count
      AND DATE(time_start) = DATE(outq.time_start)
  )
GROUP BY
  user_id,
  some_count
;

基本上,子查询检查每个user_id,如果在该日期检查了当前的some_countNOT EXISTS,则主要查询期望它some_count。您在某个日期中,每个user_id的{​​{1}}最高time_start,但是对于该用户的相同最高值,该日期可能会存在多个不同的GROUP BY。现在事情很简单。您可以安全time_start用户和计数,因为它们已经是您想要的数据,并从组中获取最大ORDER BY <columns>

这种子查询是解决像MySQL这样的问题的常用方法。我建议你尝试两种解决方案,但选择第二种解决方案并记住子查询sintax以解决任何未来的问题。

此外,在MySQL中,隐式GROUP BY <columns>适用于所有ORDER BY NULL的查询。如果您不打扰结果订单,可以通过声明{{1}}来保存一些处理,这将禁用查询中的隐式ordenation功能。

答案 7 :(得分:0)

SELECT  c1.user_id, c1.some_count, MAX(c1.time_start) AS time_start
    FROM  foo AS c1
    JOIN
      ( SELECT  user_id, MAX(some_count) AS some_count
            FROM  foo
            WHERE time_start >= '2016-07-27'
              AND time_start  < '2016-07-27' + INTERVAL 1 DAY
            GROUP BY  user_id
      ) AS c2 USING (user_id, some_count)
    GROUP BY c1.user_id, c1.some_count

并添加这些以获得更好的性能:

INDEX(user_id, some_count, time_start)
INDEX(time_start)

更改了time_start范围的测试,以便可以使用第二个索引。

这很大程度上来源于groupwise max上的博客。