【问题标题】:Group by repeating attribute按重复属性分组
【发布时间】:2012-12-10 05:26:23
【问题描述】:

基本上我有一个表messages,其中user_id 字段标识创建消息的用户。

当我显示两个用户之间的对话(一组消息)时,我希望能够按user_id 对消息进行分组,但采用一种棘手的方式:

假设有一些消息(按created_at desc排序):

  id: 1, user_id: 1
  id: 2, user_id: 1
  id: 3, user_id: 2
  id: 4, user_id: 2
  id: 5, user_id: 1

我想按以下顺序获取 3 个消息组: [1,2], [3,4], [5]

它应该按 *user_id* 分组,直到它看到另一个,然后按那个分组。

我正在使用 PostgreSQL,并且很乐意使用特定于它的东西,只要能提供最佳性能。

【问题讨论】:

  • 我认为您不需要GROUP BY。它应该是更多的应用功能。

标签: sql ruby-on-rails postgresql plpgsql window-functions


【解决方案1】:

试试这样的:

SELECT user_id, array_agg(id)
FROM (
SELECT id, 
       user_id, 
       row_number() OVER (ORDER BY created_at)-
       row_number() OVER (PARTITION BY user_id ORDER BY created_at) conv_id
FROM table1 ) t
GROUP BY user_id, conv_id;

表达式:

row_number() OVER (ORDER BY created_at)-
row_number() OVER (PARTITION BY user_id ORDER BY created_at) conv_id

将为每个消息组提供一个特殊的 ID(此 conv_id 可以为其他 user_id 重复,但 user_id, conv_id 将为您提供所有不同的消息组)

我的SQLFiddle 示例。

详情:row_number()OVER (PARTITION BY ... ORDER BY ...)

【讨论】:

    【解决方案2】:

    正确的 SQL

    我想按以下顺序获取 3 个消息组:[1,2]、[3,4]、[5]

    要获取请求的订单,请添加ORDER BY min(id)

    SELECT grp, user_id, array_agg(id) AS ids
    FROM  (
       SELECT id
            , user_id
            , row_number() OVER (ORDER BY id) -
              row_number() OVER (PARTITION BY user_id ORDER BY id) AS grp
       FROM   tbl
       ORDER  BY 1   -- for ordered arrays in result
       ) t
    GROUP  BY grp, user_id
    ORDER  BY min(id);
    

    db小提琴here
    sqliddle

    添加几乎不能保证另一个答案。更重要的问题是:

    使用 PL/pgSQL 更快

    我正在使用 PostgreSQL,并且很乐意使用特定于它的东西,只要能提供最佳性能

    纯 SQL 非常漂亮和闪亮,但过程化的服务器端函数对于这项任务要快得多。虽然在程序上处理行通常比较慢,但 plpgsql 赢得了这场比赛,因为它可以通过 单个 表扫描和 单ORDER BY操作:

    CREATE OR REPLACE FUNCTION f_msg_groups()
      RETURNS TABLE (ids int[])
      LANGUAGE plpgsql AS
    $func$
    DECLARE
       _id    int;
       _uid   int;
       _id0   int;                         -- id of last row
       _uid0  int;                         -- user_id of last row
    BEGIN
       FOR _id, _uid IN
           SELECT id, user_id FROM messages ORDER BY id
       LOOP
           IF _uid <> _uid0 THEN
              RETURN QUERY VALUES (ids);   -- output row (never happens after 1 row)
              ids := ARRAY[_id];           -- start new array
           ELSE
              ids := ids || _id;           -- add to array
           END IF;
    
           _id0  := _id;
           _uid0 := _uid;                  -- remember last row
       END LOOP;
    
       RETURN QUERY VALUES (ids);          -- output last iteration
    END
    $func$;
    

    呼叫:

    SELECT * FROM f_msg_groups();
    

    基准和链接

    我用EXPLAIN ANALYZE 对一个有 60k 行的类似现实生活表进行了快速测试(执行多次,选择最快的结果以排除兑现效应):

    SQL:
    总运行时间:1009.549 毫秒
    PL/pgSQL:
    总运行时间:336.971 毫秒

    相关:

    【讨论】:

      【解决方案3】:

      GROUP BY 子句将折叠 2 条记录中的响应 - 一条带有 user_id 1,一条带有 user_id 2,无论 ORDER BY 子句如何,所以我建议您只发送 ORDER BY created_at

      prev_id = -1
      messages.each do |m|
       if ! m.user_id == prev_id do 
          prev_id = m.user_id
          #do whatever you want with a new message group
       end
      end
      

      【讨论】:

        【解决方案4】:

        你可以使用chunk:

        Message = Struct.new :id, :user_id
        
        messages = []
        messages << Message.new(1, 1)
        messages << Message.new(2, 1)
        messages << Message.new(3, 2)
        messages << Message.new(4, 2)
        messages << Message.new(5, 1)
        
        messages.chunk(&:user_id).each do |user_id, records| 
          p "#{user_id} - #{records.inspect}" 
        end
        

        输出:

        "1 - [#<struct Message id=1, user_id=1>, #<struct Message id=2, user_id=1>]"
        "2 - [#<struct Message id=3, user_id=2>, #<struct Message id=4, user_id=2>]"
        "1 - [#<struct Message id=5, user_id=1>]"
        

        【讨论】:

          猜你喜欢
          • 2014-11-28
          • 2019-12-28
          • 2013-03-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-08-18
          • 1970-01-01
          • 2015-01-02
          相关资源
          最近更新 更多