基本上我有一个表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,很乐意使用特定的东西,无论什么都可以提供最佳性能。
答案 0 :(得分: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
重复此user_id
,但user_id, conv_id
将为您提供所有不同的邮件组)
我的SQLFiddle示例。
答案 1 :(得分:3)
@Igor提供了一个很好的带有窗口函数的纯SQL技术 但是:
我希望按以下顺序获得3个消息组:[1,2],[3,4],[5]
要获取所请求的订单,请添加ORDER BY min(id)
:
SELECT 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 messages
ORDER BY id) t -- for ordered arrays in result
GROUP BY grp, user_id
ORDER BY min(id);
这一补充几乎无法得到另一个答案。更重要的问题是:
我正在使用PostgreSQL,并且很乐意使用特定的东西,无论什么能提供最佳性能。
Pure SQL非常好用,但是这个任务的程序性服务器端功能 很多更快。虽然在程序上处理行通常较慢, plpgsql 会赢得此次竞争,因为它可以用单表扫描和< em>单 ORDER BY
操作:
CREATE OR REPLACE FUNCTION f_msg_groups()
RETURNS TABLE (ids int[]) 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$ LANGUAGE plpgsql;
呼叫:
SELECT * FROM f_msg_groups();
我在{6}行的类似真实生活表上进行了EXPLAIN ANALYZE
的快速测试(执行几次,选择最快的结果以排除兑现效果):
SQL:
总运行时间:1009.549 ms
PL / pgSQL的:
总运行时间: 336.971 ms
还要考虑这些密切相关的问题:
答案 2 :(得分:0)
GROUP BY
子句会将响应折叠为2条记录 - 其中一条记录user_id
1而一条记录user_id
2,无论ORDER BY
条款如何,我建议你这样做d只发送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
答案 3 :(得分:0)
您可以使用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>]"