さまざまな簡単で高速な方法があります。
2x DISTINCT ON
SELECT *
FROM (
SELECT DISTINCT ON (name)
name, week AS first_week, value AS first_val
FROM tbl
ORDER BY name, week
) f
JOIN (
SELECT DISTINCT ON (name)
name, week AS last_week, value AS last_val
FROM tbl
ORDER BY name, week DESC
) l USING (name);
またはそれより短い:
SELECT *
FROM (SELECT DISTINCT ON (1) name, week AS first_week, value AS first_val FROM tbl ORDER BY 1,2) f
JOIN (SELECT DISTINCT ON (1) name, week AS last_week , value AS last_val FROM tbl ORDER BY 1,2 DESC) l USING (name);
シンプルでわかりやすい。私の古いテストでも最速です。 DISTINCT ON
の詳細な説明 :
- 各GROUPBYグループの最初の行を選択しますか?
2xウィンドウ関数、1x DISTINCT ON
SELECT DISTINCT ON (name)
name, week AS first_week, value AS first_val
, first_value(week) OVER w AS last_week
, first_value(value) OVER w AS last_value
FROM tbl t
WINDOW w AS (PARTITION BY name ORDER BY week DESC)
ORDER BY name, week;
明示的なWINDOW
句はコードを短縮するだけで、パフォーマンスには影響しません。
first_value()
複合型の
集計関数min()
またはmax()
複合タイプを入力として受け入れないでください。カスタム集計関数を作成する必要があります(それほど難しくはありません)。
ただし、ウィンドウ関数は first_value()
およびlast_value()
行う 。その上で、簡単なソリューションを考案できます:
簡単なクエリ
SELECT DISTINCT ON (name)
name, week AS first_week, value AS first_value
,(first_value((week, value)) OVER (PARTITION BY name ORDER BY week DESC))::text AS l
FROM tbl t
ORDER BY name, week;
出力にはすべてのデータが含まれますが、先週の値は匿名レコードに詰め込まれます(オプションで text
にキャストされます) )。分解された値が必要になる場合があります。
テーブルタイプの日和見的な使用による分解結果
そのためには、よく知られている複合型が必要です。適合したテーブル定義により、テーブルタイプ自体を日和見的に直接使用できるようになります。
CREATE TABLE tbl (week int, value int, name text); -- optimized column order
週コード> および
value
最初に来るので、テーブルタイプ自体で並べ替えることができます:
SELECT (l).name, first_week, first_val
, (l).week AS last_week, (l).value AS last_val
FROM (
SELECT DISTINCT ON (name)
week AS first_week, value AS first_val
, first_value(t) OVER (PARTITION BY name ORDER BY week DESC) AS l
FROM tbl t
ORDER BY name, week
) sub;
ユーザー定義の行タイプからの分解結果
ほとんどの場合、それはおそらく不可能です。複合型をCREATETYPE
に登録します (永続的)または CREATE TEMP TABLE
(セッション期間中):
CREATE TEMP TABLE nv(last_week int, last_val int); -- register composite type
SELECT name, first_week, first_val, (l).last_week, (l).last_val
FROM (
SELECT DISTINCT ON (name)
name, week AS first_week, value AS first_val
, first_value((week, value)::nv) OVER (PARTITION BY name ORDER BY week DESC) AS l
FROM tbl t
ORDER BY name, week
) sub;
カスタム集計関数first()
& last()
データベースごとに1回関数と集計を作成します:
CREATE OR REPLACE FUNCTION public.first_agg (anyelement, anyelement)
RETURNS anyelement
LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS
'SELECT $1;'
CREATE AGGREGATE public.first(anyelement) (
SFUNC = public.first_agg
, STYPE = anyelement
, PARALLEL = safe
);
CREATE OR REPLACE FUNCTION public.last_agg (anyelement, anyelement)
RETURNS anyelement
LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS
'SELECT $2';
CREATE AGGREGATE public.last(anyelement) (
SFUNC = public.last_agg
, STYPE = anyelement
, PARALLEL = safe
);
次に:
SELECT name
, first(week) AS first_week, first(value) AS first_val
, last(week) AS last_week , last(value) AS last_val
FROM (SELECT * FROM tbl ORDER BY name, week) t
GROUP BY name;
おそらく最もエレガントなソリューションです。追加のモジュールfirst_last_agg
で高速化 C実装を提供します。
PostgresWikiの手順を比較してください。
関連:
- 各インフルエンサーのフォロワーの成長を経時的に計算する
db<>ここでフィドル (すべて表示)
古いsqlfiddle
これらの各クエリは、 EXPLAIN ANALYZE
を使用した5万行のテーブルでのクイックテストで現在受け入れられている回答よりも大幅に高速でした。 。
他にも方法があります。データの分散によっては、さまざまなクエリスタイルの方が(はるかに)高速になる場合があります。参照:
- GROUP BYクエリを最適化して、ユーザーごとに最新の行を取得します