为每组选择行数,其中两个列值组成一组

Posted

技术标签:

【中文标题】为每组选择行数,其中两个列值组成一组【英文标题】:Select number of rows for each group where two column values makes one group 【发布时间】:2012-10-08 17:34:44 【问题描述】:

我有两个由UNION ALL 连接的选择语句。在第一个语句中,where 子句仅收集先前已向用户显示的行。第二条语句收集所有未向用户显示的行,因此我首先得到查看的结果,然后得到未查看的结果。

当然,这可以通过使用简单的ORDER BY 的相同选择语句简单地实现,但是在您意识到我希望完成的事情之后,两个单独选择的原因很简单。

考虑以下结构和数据。

+----+------+-----+--------+------+
| id | from | to  | viewed | data |
+----+------+-----+--------+------+
| 1  | 1    | 10  | true   | .... |
| 2  | 10   | 1   | true   | .... |
| 3  | 1    | 10  | true   | .... |
| 4  | 6    | 8   | true   | .... |
| 5  | 1    | 10  | true   | .... |
| 6  | 10   | 1   | true   | .... |
| 7  | 8    | 6   | true   | .... |
| 8  | 10   | 1   | true   | .... |
| 9  | 6    | 8   | true   | .... |
| 10 | 2    | 3   | true   | .... |
| 11 | 1    | 10  | true   | .... |
| 12 | 8    | 6   | true   | .... |
| 13 | 10   | 1   | false  | .... |
| 14 | 1    | 10  | false  | .... |
| 15 | 6    | 8   | false  | .... |
| 16 | 10   | 1   | false  | .... |
| 17 | 8    | 6   | false  | .... |
| 18 | 3    | 2   | false  | .... |
+----+------+-----+--------+------+

基本上我希望语句选择所有未查看的行,这是通过检查天气来完成的,viewed 列是truefalse,非常简单明了,这里不用担心。

但是,对于已查看的行,即 viewed is TRUE 列,对于这些记录,我只希望为每个组返回 3 行。

本例中的适当结果应该是每组中最近的 3 行。

+----+------+-----+--------+------+
| id | from | to  | viewed | data |
+----+------+-----+--------+------+
| 6  | 10   | 1   | true   | .... |
| 7  | 8    | 6   | true   | .... |
| 8  | 10   | 1   | true   | .... |
| 9  | 6    | 8   | true   | .... |
| 10 | 2    | 3   | true   | .... |
| 11 | 1    | 10  | true   | .... |
| 12 | 8    | 6   | true   | .... |
+----+------+-----+--------+------+

正如您从理想结果集中看到的那样,我们分为三组。因此,查看结果的所需查询应该为它找到的每个分组显示最多 3 行。在这种情况下,这些分组是 10 和 1 和 8 和 6,都显示了三行,而另一组 2 和 3 只显示了一行。

请注意,from = xto = yfrom = yto = x 进行相同的分组。因此考虑到第一个分组(10 和 1),from = 10to = 1 如果是 from = 1to = 10 是同一组。

但是,整个表中有很多组,我只希望在 select 语句中返回每个组中最近的 3 个,这就是我的问题,我不确定如何以最有效的方式完成考虑到该表在某个时候将有数百条甚至数千条记录。

感谢您的帮助。

注意:idfromtoviewed 已编入索引,这应该有助于提高性能。

PS:我不确定如何准确命名这个问题,如果你有更好的主意,请成为我的客人并编辑标题。

【问题讨论】:

如果我猜对了,“组”被定义为 fromto 值 x,y 的不同组合,无论值顺序如何。您可能应该在开始时更清楚地说明这一点。在没有完整答案的情况下,我认为用于聚合反向排序对的 DISTINCT、GROUP BY 和 UNION 应该是您的朋友。 除了@Matze 不得不说的,我想“最近的”意味着“最高的 id 值”。对吗? 【参考方案1】:

真是个毛球!当您从最近的到最近的第二个,再到最近的第三个时,这会变得越来越难。

让我们通过获取我们需要的 ID 列表来将其放在一起。然后我们可以通过 ID 从表中拉取项目。

这个相对简单的查询可以让您获得最近项目的 ID

 SELECT id FROM
    (SELECT max(id) id, fromitem, toitem
       FROM stuff
      WHERE viewed = 'true'
      GROUP BY fromitem, toitem
    )a

小提琴:http://sqlfiddle.com/#!2/f7045/27/0

接下来,我们需要获取第二个最近项目的 ID。为此,我们需要一个自连接样式查询。我们需要做同样的总结,但是在一个省略最新项目的虚拟表上。

select id from (
  select max(b.id) id, b.fromitem, b.toitem
    from stuff a
    join
           (select id, fromitem, toitem
            from stuff
           where viewed = 'true'
            ) b on (    a.fromitem = b.fromitem 
                    and a.toitem = b.toitem
                    and b.id < a.id)
   where a.viewed = 'true'
   group by fromitem, toitem
  )c

小提琴:http://sqlfiddle.com/#!2/f7045/44/0

最后,我们需要获取第三个最近项目的 ID。怜悯!我们需要再次将刚才的查询加入到表中。

select id from
(
  select max(d.id) id, d.fromitem, d.toitem
    from stuff d
     join 
    (
       select max(b.id) id, b.fromitem, b.toitem
          from stuff a
          join
            (
               select id, fromitem, toitem
                 from stuff
                where viewed = 'true'
            ) b on  (    a.fromitem = b.fromitem 
                     and a.toitem = b.toitem
                     and b.id < a.id)
          where a.viewed = 'true'
          group by fromitem, toitem
     ) c on (    d.fromitem = c.fromitem
             and d.toitem = c.toitem
             and d.id < c.id)
    where d.viewed='true'
  group by d.fromitem, d.toitem
 ) e

小提琴:http://sqlfiddle.com/#!2/f7045/45/0

所以,现在我们将所有这些 id 合并,并使用它们从表中获取正确的行,我们就完成了。

SELECT * 
  FROM STUFF
 WHERE ID IN
(

SELECT id FROM
    (SELECT max(id) id, fromitem, toitem
       FROM stuff
      WHERE viewed = 'true'
      GROUP BY fromitem, toitem
    )a
UNION
select id from (
  select max(b.id) id, b.fromitem, b.toitem
    from stuff a
    join
           (select id, fromitem, toitem
            from stuff
           where viewed = 'true'
            ) b on (    a.fromitem = b.fromitem 
                    and a.toitem = b.toitem
                    and b.id < a.id)
   where a.viewed = 'true'
   group by fromitem, toitem
  )c
UNION
select id from
(
  select max(d.id) id, d.fromitem, d.toitem
    from stuff d
     join 
    (
       select max(b.id) id, b.fromitem, b.toitem
          from stuff a
          join
            (
               select id, fromitem, toitem
                 from stuff
                where viewed = 'true'
            ) b on  (    a.fromitem = b.fromitem 
                     and a.toitem = b.toitem
                     and b.id < a.id)
          where a.viewed = 'true'
          group by fromitem, toitem
     ) c on (    d.fromitem = c.fromitem
             and d.toitem = c.toitem
             and d.id < c.id)
    where d.viewed='true'
  group by d.fromitem, d.toitem
 ) e
UNION
select id from stuff where viewed='false'
)
order by viewed desc, fromitem, toitem, id desc

嘻嘻。太多的 SQL。小提琴:http://sqlfiddle.com/#!2/f7045/47/0

现在,我们需要处理您的最后一个要求,即您的图表是无序的。即 from=n to=m 与 from=m to=n 相同。

为此,我们需要一个虚拟表而不是物理表。这样就可以了。

 SELECT id, least(fromitem, toitem) fromitem, greatest(fromitem,toitem) toitem, data
   FROM stuff

现在我们需要使用这个虚拟表,这个视图,到处都是物理表出现的地方。让我们使用视图来执行此操作。

CREATE VIEW 
AS 
SELECT id,
       LEAST(fromitem, toitem) fromitem,
       GREATEST (fromitem, toitem) toitem,
       viewed,
       data;

所以,我们的最终查询是:

SELECT *
      FROM stuff
     WHERE ID IN
    (

    SELECT id FROM
        (SELECT max(id) id, fromitem, toitem
           FROM STUFF_UNORDERED
          WHERE viewed = 'true'
          GROUP BY fromitem, toitem
        )a
    UNION
    SELECT id FROM (
      SELECT max(b.id) id, b.fromitem, b.toitem
        FROM STUFF_UNORDERED a
        JOIN
               (SELECT id, fromitem, toitem
                FROM STUFF_UNORDERED
               WHERE viewed = 'true'
                ) b ON (    a.fromitem = b.fromitem
                        AND a.toitem = b.toitem
                        AND b.id < a.id)
       WHERE a.viewed = 'true'
       GROUP BY fromitem, toitem
      )c
    UNION
    SELECT id FROM
    (
      SELECT max(d.id) id, d.fromitem, d.toitem
        FROM STUFF_UNORDERED d
         JOIN
        (
           SELECT max(b.id) id, b.fromitem, b.toitem
              FROM STUFF_UNORDERED a
              JOIN
                (
                   SELECT id, fromitem, toitem
                     FROM STUFF_UNORDERED
                    WHERE viewed = 'true'
                ) b ON  (    a.fromitem = b.fromitem
                         AND a.toitem = b.toitem
                         AND b.id < a.id)
              WHERE a.viewed = 'true'
              GROUP BY fromitem, toitem
         ) c ON (    d.fromitem = c.fromitem
                 AND d.toitem = c.toitem
                 AND d.id < c.id)
        WHERE d.viewed='true'
      GROUP BY d.fromitem, d.toitem
     ) e
    UNION
    SELECT id FROM STUFF_UNORDERED WHERE viewed='false'
    )
    ORDER BY viewed DESC,
            least(fromitem, toitem),
            greatest(fromitem, toitem),
            id DESC

小提琴:http://sqlfiddle.com/#!2/8c154/4/0

【讨论】:

从您的热情中,我感觉您喜欢这样做,而不是喜欢您花时间将其拆开并展示其工作原理的事实。你的方法给我留下了深刻的印象,当然我会标记它是正确的,在我回来的路上我有一个关于如何完成这个的想法,我只是要自己尝试一下,看看它是否有效,如果它确实我应该能够检索最后的 3、4、5 或我想要的数量。无论如何,就我而言,您的回答很好。只希望很多人支持它,这是当之无愧的。谢谢。 我的方法的问题在于,它无法很好地扩展到第四个、第五个等项目。使用 Oracle ROWNUM 功能或 postgreSQL RANK 窗口函数可以轻松实现另一种方法,但 mysql 不适合这种工作。感谢您有机会在下雨的假期下午进行一些黑客攻击。

以上是关于为每组选择行数,其中两个列值组成一组的主要内容,如果未能解决你的问题,请参考以下文章

为每组选择随机行

“展平”单元阵列

为每组 pyspark RDD/dataframe 选择随机列

sql:选择由另一列分组的两列值的计数并获得两个计数的比率

仅为每组记录选择最近的记录

选择每组的最大行数 - 熊猫性能问题