1

基本的に、メッセージを作成したユーザーを識別するフィールドをmessages持つtable があります。user_id

2 人のユーザー間の会話 (メッセージのセット) を表示するとき、メッセージを でグループ化できるようにしたいのです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 を使用していますが、最高のパフォーマンスが得られるものであれば、PostgreSQL に固有のものを喜んで使用します。

4

4 に答える 4

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
古いsqliddle

追加は、別の答えをほとんど保証しません。より重要な問題は次のとおりです。

PL/pgSQL で高速化

私は PostgreSQL を使用していますが、最高のパフォーマンスが得られるものであれば、PostgreSQL に固有のものを喜んで使用します。

純粋な SQL はどれも優れた機能を備えていますが、このタスクでは手続き型のサーバー側関数の方がはるかに高速です。行を手続き的に処理するのは一般的に遅くなりますが、plpgsqlは1 回のテーブル スキャンと 1 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 ANALYZE60k行の同様の実際のテーブルで簡単なテストを実行しました(数回実行し、キャッシュ効果を除外するために最速の結果を選択します):

SQL:
合計実行時間: 1009.549 ミリ秒
Pl/pgSQL:
合計実行時間: 336.971 ミリ秒

関連している:

于 2012-12-24T02:59:28.460 に答える
0

条項は、GROUP BY2つのレコードで応答を折りたたむことになります。1つは条項に関係なくuser_id1つ、もう1つは2つであるため、user_idORDER BYORDER 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 に答える