Proč Rowanův hack práce (většinou)?
SELECT id, title
, CASE WHEN extra_exists THEN extra::text ELSE 'default' END AS extra
FROM tbl
CROSS JOIN (
SELECT EXISTS (
SELECT FROM information_schema.columns
WHERE table_name = 'tbl'
AND column_name = 'extra')
) AS extra(extra_exists)
Normálně by to nefungovalo vůbec. Postgres analyzuje příkaz SQL a vyvolá výjimku, pokud je jaká ze zapojených sloupců neexistuje.
Trik je vložit název tabulky (nebo alias) se stejným názvem jako název příslušného sloupce. extra
v tomto případě. Na každý název tabulky lze odkazovat jako na celek, což má za následek, že celý řádek bude vrácen jako typ record
. A protože každý typ lze přetypovat na text
, můžeme celý tento záznam přenést do text
. Tímto způsobem Postgres přijme dotaz jako platný.
Protože názvy sloupců mají přednost před názvy tabulek, extra::text
je interpretován jako sloupec tbl.extra
pokud sloupec existuje. Jinak by implicitně vrátil celý řádek tabulky extra
- což se nikdy nestane.
Zkuste vybrat jiný alias tabulky pro extra
přesvědčte se sami.
Toto je nedokumentovaný hack a může se zlomit pokud se Postgres rozhodne změnit způsob, jakým jsou analyzovány řetězce SQL a plánováno v budoucích verzích – i když se to zdá nepravděpodobné.
Jednoznačně
Pokud rozhodnete se to použít, alespoň uveďte to jednoznačně .
Samotný název tabulky není jedinečný. Tabulka s názvem "tbl" může existovat mnohokrát ve více schématech stejné databáze, což by mohlo vést k velmi matoucím a zcela falešným výsledkům. potřebujete dodat název schématu:
SELECT id, title
, CASE WHEN col_exists THEN extra::text ELSE 'default' END AS extra
FROM tbl
CROSS JOIN (
SELECT EXISTS (
SELECT FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'tbl'
AND column_name = 'extra'
) AS col_exists
) extra;
Rychlejší
Protože tento dotaz je stěží přenositelný na jiné RDBMS, doporučuji použít katalogová tabulka pg_attribute
namísto zobrazení informačního schématu information_schema.columns
. Asi 10krát rychleji.
SELECT id, title
, CASE WHEN col_exists THEN extra::text ELSE 'default' END AS extra
FROM tbl
CROSS JOIN (
SELECT EXISTS (
SELECT FROM pg_catalog.pg_attribute
WHERE attrelid = 'myschema.tbl'::regclass -- schema-qualified!
AND attname = 'extra'
AND NOT attisdropped -- no dropped (dead) columns
AND attnum > 0 -- no system columns
)
) extra(col_exists);
Také pomocí pohodlnějšího a bezpečnějšího odesílání do regclass
. Viz:
Potřebný alias k oklamání Postgres můžete připojit k jakémukoli tabulky, včetně samotné primární tabulky. Nemusíte se vůbec připojovat k dalšímu vztahu, který by měl být nejrychlejší:
SELECT id, title
, CASE WHEN EXISTS (SELECT FROM pg_catalog.pg_attribute
WHERE attrelid = 'tbl'::regclass
AND attname = 'extra'
AND NOT attisdropped
AND attnum > 0)
THEN extra::text
ELSE 'default' END AS extra
FROM tbl AS extra;
Pohodlí
You could encapsulate the test for existence in a simple SQL function (once), arriving (almost) at the function you have been asking for:
CREATE OR REPLACE FUNCTION col_exists(_tbl regclass, _col text)
RETURNS bool
LANGUAGE sql STABLE AS
$func$
SELECT EXISTS (
SELECT FROM pg_catalog.pg_attribute
WHERE attrelid = $1
AND attname = $2
AND NOT attisdropped
AND attnum > 0
)
$func$;
COMMENT ON FUNCTION col_exists(regclass, text) IS
'Test for existence of a column. Returns TRUE / FALSE.
$1 .. exact table name (case sensitive!), optionally schema-qualified
$2 .. exact column name (case sensitive!)';
Zjednodušuje dotaz na:
SELECT id, title
, CASE WHEN col_exists THEN extra::text ELSE 'default' END AS extra
FROM tbl
CROSS JOIN col_exists('tbl', 'extra') AS extra(col_exists);
Zde použijte formulář s dalším vztahem, protože se ukázalo, že je s funkcí rychlejší.
Přesto získáte pouze textovou reprezentaci sloupce s kterýmkoli z těchto dotazů. Získat skutečný typ není tak jednoduché .
Srovnávací
Spustil jsem rychlý benchmark se 100 000 řádky na str. 9.1 a 9.2, abych zjistil, že jsou nejrychlejší:
Nejrychlejší:
SELECT id, title
, CASE WHEN EXISTS (SELECT FROM pg_catalog.pg_attribute
WHERE attrelid = 'tbl'::regclass
AND attname = 'extra'
AND NOT attisdropped
AND attnum > 0)
THEN extra::text
ELSE 'default' END AS extra
FROM tbl AS extra;
2. nejrychlejší:
SELECT id, title
, CASE WHEN col_exists THEN extra::text ELSE 'default' END AS extra
FROM tbl
CROSS JOIN col_exists('tbl', 'extra') AS extra(col_exists);
db<>fiddle zde
Starý sqlfiddle