Postgres 9.1を想定
基本的なクエリを簡略化/最適化して、最新の値を取得しました:
SELECT DISTINCT ON (1,2)
c.unique_id, a.attname AS col, c.value
FROM pg_attribute a
LEFT JOIN changes c ON c.column_name = a.attname
AND c.table_name = 'instances'
-- AND c.unique_id = 3 -- uncomment to fetch single row
WHERE a.attrelid = 'instances'::regclass -- schema-qualify to be clear?
AND a.attnum > 0 -- no system columns
AND NOT a.attisdropped -- no deleted columns
ORDER BY 1, 2, c.updated_at DESC;
標準の情報スキーマの代わりにPostgreSQLカタログをクエリします。これは、より高速だからです。 ::regclass
への特別なキャストに注意してください 。
さて、それはあなたにテーブルを与えます 。 1つのunique_id
のすべての値が必要です 行 。
これを実現するには、基本的に3つのオプションがあります。
-
列ごとに1つの副選択(または結合)。高価で扱いにくい。ただし、有効なオプションは数列のみです。
-
大きな
CASE
ステートメント。 -
ピボット機能 。 PostgreSQLは
crosstab()
を提供します 追加モジュールtablefunc
の関数 そのためです。
基本的な手順:- PostgreSQLクロス集計クエリ
crosstab()
を使用した基本的なピボットテーブル
関数を完全に書き直しました:
SELECT *
FROM crosstab(
$x$
SELECT DISTINCT ON (1, 2)
unique_id, column_name, value
FROM changes
WHERE table_name = 'instances'
-- AND unique_id = 3 -- un-comment to fetch single row
ORDER BY 1, 2, updated_at DESC;
$x$,
$y$
SELECT attname
FROM pg_catalog.pg_attribute
WHERE attrelid = 'instances'::regclass -- possibly schema-qualify table name
AND attnum > 0
AND NOT attisdropped
AND attname <> 'unique_id'
ORDER BY attnum
$y$
)
AS tbl (
unique_id integer
-- !!! You have to list all columns in order here !!! --
);
crosstab()
として、カタログルックアップを値クエリから分離しました 2つのパラメーターを持つ関数は、列名を個別に提供します。欠落している値(変更にエントリがない)は、NULL
に置き換えられます 自動的。 このユースケースに完全に一致します!
attname
と仮定します column_name
と一致します 。 unique_id
を除く 、特別な役割を果たします。
完全自動化
コメントへの対応:方法があります 列定義リストを自動的に提供します。しかし、それは気弱な人のためではありません。
ここでは、いくつかの高度なPostgres機能を使用しています:crosstab()
、動的SQLを使用したplpgsql関数、複合型処理、高度なドル引用、カタログルックアップ、集計関数、ウィンドウ関数、オブジェクト識別子タイプ、...
テスト環境:
CREATE TABLE instances (
unique_id int
, col1 text
, col2 text -- two columns are enough for the demo
);
INSERT INTO instances VALUES
(1, 'foo1', 'bar1')
, (2, 'foo2', 'bar2')
, (3, 'foo3', 'bar3')
, (4, 'foo4', 'bar4');
CREATE TABLE changes (
unique_id int
, table_name text
, column_name text
, value text
, updated_at timestamp
);
INSERT INTO changes VALUES
(1, 'instances', 'col1', 'foo11', '2012-04-12 00:01')
, (1, 'instances', 'col1', 'foo12', '2012-04-12 00:02')
, (1, 'instances', 'col1', 'foo1x', '2012-04-12 00:03')
, (1, 'instances', 'col2', 'bar11', '2012-04-12 00:11')
, (1, 'instances', 'col2', 'bar17', '2012-04-12 00:12')
, (1, 'instances', 'col2', 'bar1x', '2012-04-12 00:13')
, (2, 'instances', 'col1', 'foo2x', '2012-04-12 00:01')
, (2, 'instances', 'col2', 'bar2x', '2012-04-12 00:13')
-- NO change for col1 of row 3 - to test NULLs
, (3, 'instances', 'col2', 'bar3x', '2012-04-12 00:13');
-- NO changes at all for row 4 - to test NULLs
1つのテーブルの自動化された機能
CREATE OR REPLACE FUNCTION f_curr_instance(int, OUT t public.instances) AS
$func$
BEGIN
EXECUTE $f$
SELECT *
FROM crosstab($x$
SELECT DISTINCT ON (1,2)
unique_id, column_name, value
FROM changes
WHERE table_name = 'instances'
AND unique_id = $f$ || $1 || $f$
ORDER BY 1, 2, updated_at DESC;
$x$
, $y$
SELECT attname
FROM pg_catalog.pg_attribute
WHERE attrelid = 'public.instances'::regclass
AND attnum > 0
AND NOT attisdropped
AND attname <> 'unique_id'
ORDER BY attnum
$y$) AS tbl ($f$
|| (SELECT string_agg(attname || ' ' || atttypid::regtype::text
, ', ' ORDER BY attnum) -- must be in order
FROM pg_catalog.pg_attribute
WHERE attrelid = 'public.instances'::regclass
AND attnum > 0
AND NOT attisdropped)
|| ')'
INTO t;
END
$func$ LANGUAGE plpgsql;
テーブルinstances
はハードコーディングされており、明確であるようにスキーマが認定されています。リターンタイプとしてテーブルタイプを使用していることに注意してください。 PostgreSQLのすべてのテーブルに自動的に登録される行タイプがあります。これは、crosstab()
の戻りタイプと一致するようにバインドされています 機能。
これにより、関数がテーブルのタイプにバインドされます:
-
DROP
を実行しようとすると、エラーメッセージが表示されます テーブル -
ALTER TABLE
の後で関数が失敗します 。 (変更せずに)再作成する必要があります。これは9.1のバグだと思います。ALTER TABLE
黙って関数を壊してはいけませんが、エラーが発生します。
これは非常にうまく機能します。
電話:
SELECT * FROM f_curr_instance(3);
unique_id | col1 | col2
----------+-------+-----
3 |<NULL> | bar3x
col1
に注意してください NULL
です ここにあります。
クエリで使用して、最新の値を持つインスタンスを表示します:
SELECT i.unique_id
, COALESCE(c.col1, i.col1)
, COALESCE(c.col2, i.col2)
FROM instances i
LEFT JOIN f_curr_instance(3) c USING (unique_id)
WHERE i.unique_id = 3;
任意のテーブルの完全自動化
(2016年に追加されました。これはダイナマイトです。)
Postgres 9.1が必要です またはそれ以降。 (8.4ページで動作するように作成できましたが、バックパッチを作成する必要はありませんでした。)
CREATE OR REPLACE FUNCTION f_curr_instance(_id int, INOUT _t ANYELEMENT) AS
$func$
DECLARE
_type text := pg_typeof(_t);
BEGIN
EXECUTE
(
SELECT format
($f$
SELECT *
FROM crosstab(
$x$
SELECT DISTINCT ON (1,2)
unique_id, column_name, value
FROM changes
WHERE table_name = %1$L
AND unique_id = %2$s
ORDER BY 1, 2, updated_at DESC;
$x$
, $y$
SELECT attname
FROM pg_catalog.pg_attribute
WHERE attrelid = %1$L::regclass
AND attnum > 0
AND NOT attisdropped
AND attname <> 'unique_id'
ORDER BY attnum
$y$) AS ct (%3$s)
$f$
, _type, _id
, string_agg(attname || ' ' || atttypid::regtype::text
, ', ' ORDER BY attnum) -- must be in order
)
FROM pg_catalog.pg_attribute
WHERE attrelid = _type::regclass
AND attnum > 0
AND NOT attisdropped
)
INTO _t;
END
$func$ LANGUAGE plpgsql;
呼び出し(テーブルタイプにNULL::public.instances
を指定します :
SELECT * FROM f_curr_instance(3, NULL::public.instances);
関連:
- PL / pgSQL関数をリファクタリングして、さまざまなSELECTクエリの出力を返します
- 動的SQLを使用して複合変数フィールドの値を設定する方法