1

基本上我有一个表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,并且很乐意使用特定于它的东西,只要能提供最佳性能。

4

4 回答 4

5

尝试这样的事情:

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可以为 other 重复user_id,但user_id, conv_id会给你所有不同的消息组)

我的SQLFiddle示例。

详细信息:row_number(),OVER (PARTITION BY ... ORDER BY ...)

于 2012-12-23T12:08:16.450 回答
3

正确的 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<>fiddle 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();

基准和链接

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

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

有关的:

于 2012-12-24T02:59:28.460 回答
0

GROUP BY子句将折叠 2 条记录中的响应 - 一条为user_id1,一条为user_id2,无论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
于 2012-12-23T10:58:38.330 回答
0

您可以使用

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>]"
于 2012-12-23T12:21:17.673 回答