ヒープアナリティクスはかなり類似した何かのための素晴らしいblog post about lateral joinsを持っています。それはあなたにいくつかのアイデアを与えるかもしれない。あなたの状況は実際よりも簡単ですので、あなたのソリューションも簡単です。
最初にいくつかのメモ。 day
出力は、入力と常に同じであるため、出力が必要ないようです。第2に、毎日別の出力列が必要な場合(または結果が配列に蓄積されることはあまり望ましくないようです)、可変日数が必要な場合は、SQLを動的に構築する必要があります。それ。テストのために
私はテーブルを作って、それを数行を与えた:
create table messages (user_id integer, created_at timestamp);
insert into messages values (1, now() - interval '5 days'), (1, now() - interval '4 days'), (1, now() - interval '2 days');
insert into messages values (2, now() - interval '10 days'), (2, now() - interval '2 days');
insert into messages values (3, now() - interval '2 days'), (3, now() - interval '1 days');
insert into messages values (4, now() - interval '5 days');
私はあなたが横種類の上記の記事のように、合流使用して非常にきれいな解決策を得ることができると思う:
\set start_time '''2016-06-23 06:00:00'''
WITH t(s) AS (
SELECT :start_time::timestamp
)
SELECT COUNT(DISTINCT m1.user_id) AS day_5_messages,
COUNT(DISTINCT m2.user_id) AS day_4_messages,
COUNT(DISTINCT m3.user_id) AS day_3_messages,
COUNT(DISTINCT m4.user_id) AS day_2_messages,
COUNT(DISTINCT m5.user_id) AS day_1_messages
FROM messages m1
CROSS JOIN t
LEFT OUTER JOIN LATERAL (
SELECT * FROM messages msub
WHERE msub.user_id = m1.user_id
AND msub.created_at <@
tsrange(t.s + interval '1 day',
t.s + interval '2 days')
LIMIT 1
) m2
ON true
LEFT OUTER JOIN LATERAL (
SELECT * FROM messages msub
WHERE msub.user_id = m2.user_id
AND msub.created_at <@
tsrange(t.s + interval '2 days',
t.s + interval '3 days')
LIMIT 1
) m3
ON true
LEFT OUTER JOIN LATERAL (
SELECT * FROM messages msub
WHERE msub.user_id = m3.user_id
AND msub.created_at <@
tsrange(t.s + interval '3 days',
t.s + interval '4 days')
LIMIT 1
) m4
ON true
LEFT OUTER JOIN LATERAL (
SELECT * FROM messages msub
WHERE msub.user_id = m4.user_id
AND msub.created_at <@
tsrange(t.s + interval '4 days',
t.s + interval '5 days')
LIMIT 1
) m5
ON true
WHERE m1.created_at <@
tsrange(t.s,
t.s + interval '1 day')
;
を繰り返すのを避けるために、私はt(s)
CTEを使用しています。あなたが好きではない場合はオプションです。もちろん、Railsでは:start_time
の代わりに?
を使用してクエリをパラメータ化することもできます。それをテストするための
は右user_id
Sが含まれているかどうので、あなたが決めることができ、各COUNT(...)
array_agg(...)
とを交換すると便利です。
インデックスがcreated_at
とuser_id
(一緒にある)の場合、これはうまくいくと思います。または、あなたの日が常に同じ瞬間(UTCの深夜)で始まる場合は、日付(タイムスタンプではない)とuser_id
の機能インデックスを使用して、すべての範囲条件をその日だけに置き換えることができます。それはさらに優れたパフォーマンスを発揮します。
また、あなたのクエリ(と私の)は常に1つの行を返します。これはかなり疑わしいようです。私はそれが本当にあなたが望むものか、それがあなたの質問のために物事を単純化する事故であるのだろうかと思います。開始日に1行が必要な場合は、day
列をグループ化してグループ化し、WHERE
条件を削除し、t.s
の代わりに前のm
テーブルに基づいてすべての結合を実行できます。
私はおそらく指定するべきですが、 'user_id'は実際には別のテーブルのキーではありません。これは単なる一意の文字列識別子です。 – mnort9
ちょっと不思議なことに、なぜ外来キーを持っていないのですか? –
フィールドは実際に私のdbの 'user_id'ではなく、私はこのポストの例として使っています。おそらく私の悪い例ですが、外国のキーのように見えます。 – mnort9