Za předpokladu, že Postgres 9.1 nebo novější.
Zjednodušil jsem/optimalizoval jsem váš základní dotaz tak, aby získal nejnovější hodnoty:
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;
Dotazuji se na katalog PostgreSQL místo na standardní informační schéma, protože je to rychlejší. Všimněte si speciálního obsazení ::regclass
.
Nyní získáte tabulku . Chcete všechny hodnoty pro jeden unique_id
v řadě .
Abyste toho dosáhli, máte v zásadě tři možnosti:
-
Jeden podvýběr (nebo spojení) na sloupec. Drahé a nepraktické. Ale platná možnost pouze pro několik sloupců.
-
Velký
CASE
prohlášení. -
Pivotní funkce . PostgreSQL poskytuje
crosstab()
funkce v přídavném modulutablefunc
pro to.
Základní pokyny:- PostgreSQL Crosstab Query
Základní kontingenční tabulka s crosstab()
Kompletně jsem přepsal funkci:
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 !!! --
);
Oddělil jsem vyhledávání v katalogu od dotazu na hodnotu jako crosstab()
funkce se dvěma parametry poskytuje názvy sloupců samostatně. Chybějící hodnoty (žádný záznam ve změnách) jsou nahrazeny NULL
automaticky. Dokonalá kombinace pro tento případ použití!
Za předpokladu, že attname
odpovídá column_name
. S výjimkou unique_id
, který hraje zvláštní roli.
Plná automatizace
Řešení vašeho komentáře:Existuje způsob pro automatické dodání seznamu definic sloupců. Není to však pro slabé povahy.
Zde používám řadu pokročilých funkcí Postgres:crosstab()
, funkce plpgsql s dynamickým SQL, zpracování složených typů, pokročilé kotování dolaru, vyhledávání v katalogu, agregační funkce, funkce okna, typ identifikátoru objektu, ...
Testovací prostředí:
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
Automatická funkce pro jednu tabulku
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;
Tabulka instances
je pevně zakódováno, schéma kvalifikované jako jednoznačné. Všimněte si použití typu tabulky jako návratového typu. Pro každou tabulku v PostgreSQL je automaticky registrován typ řádku. To se musí shodovat s návratovým typem crosstab()
funkce.
Tím se funkce sváže s typem tabulky:
- Pokud se pokusíte
DROP
, zobrazí se chybová zpráva stůl - Vaše funkce selže po
ALTER TABLE
. Musíte to znovu vytvořit (beze změn). Považuji to za chybu v 9.1.ALTER TABLE
by neměl potichu přerušit funkci, ale vyvolat chybu.
To funguje velmi dobře.
Volejte:
SELECT * FROM f_curr_instance(3);
unique_id | col1 | col2
----------+-------+-----
3 |<NULL> | bar3x
Všimněte si, jak col1
je NULL
zde.
Použijte v dotazu k zobrazení instance s jejími nejnovějšími hodnotami:
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;
Plná automatizace pro libovolnou tabulku
(Přidáno 2016. Toto je dynamit.)
Vyžaduje Postgres 9.1 nebo později. (Mohlo by fungovat s pg 8.4, ale neobtěžoval jsem se s backpatchem.)
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;
Volání (s uvedením typu tabulky s NULL::public.instances
:
SELECT * FROM f_curr_instance(3, NULL::public.instances);
Související:
- Refaktorujte funkci PL/pgSQL tak, aby vrátila výstup různých SELECT dotazů
- Jak nastavit hodnotu pole složené proměnné pomocí dynamického SQL