Dlaczego Hakowanie Rowana praca (głównie)?
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)
Normalnie w ogóle by to nie zadziałało. Postgres analizuje instrukcję SQL i zgłasza wyjątek, jeśli jakikolwiek z zaangażowanych kolumn nie istnieje.
Sztuczka polega na wprowadzeniu nazwy tabeli (lub aliasu) o takiej samej nazwie, jak nazwa kolumny, o której mowa. extra
w tym przypadku. Do każdej nazwy tabeli można się odwoływać jako całość, co skutkuje zwróceniem całego wiersza jako typu record
. A ponieważ każdy typ można rzutować na text
, możemy przesłać cały rekord do text
. W ten sposób Postgres akceptuje zapytanie jako prawidłowe.
Ponieważ nazwy kolumn mają pierwszeństwo przed nazwami tabel, extra::text
jest interpretowany jako kolumna tbl.extra
jeśli kolumna istnieje. W przeciwnym razie domyślnie zwróci cały wiersz tabeli extra
- co nigdy się nie zdarza.
Spróbuj wybrać inny alias tabeli dla extra
aby zobaczyć na własne oczy.
To jest nieudokumentowany hack i może się zepsuć jeśli Postgres zdecyduje się zmienić sposób przetwarzania ciągów SQL i planowane w przyszłych wersjach - nawet jeśli wydaje się to mało prawdopodobne.
Jednoznaczne
Jeśli zdecydujesz się tego użyć, przynajmniej uczyń to jednoznacznym .
Sama nazwa tabeli nie jest unikalna. Tabela o nazwie „tbl” może istnieć dowolną liczbę razy w wielu schematach tej samej bazy danych, co może prowadzić do bardzo mylących i całkowicie fałszywych wyników. potrzebujesz aby dodatkowo podać nazwę schematu:
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;
Szybciej
Ponieważ to zapytanie jest trudne do przeniesienia na inne RDBMS, proponuję użyć tabela katalogowa pg_attribute
zamiast widoku schematu informacji information_schema.columns
. Około 10 razy szybciej.
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);
Używając także wygodniejszego i bezpieczniejszego rzutowania na regclass
. Zobacz:
Możesz dołączyć potrzebny alias, aby oszukać Postgresa do dowolnego tabela, w tym sama tabela podstawowa. Nie musisz w ogóle dołączać do innej relacji, co powinno być najszybsze:
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;
Wygoda
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!)';
Upraszcza zapytanie do:
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);
Korzystanie z formularza z dodatkową relacją tutaj, ponieważ funkcja okazała się szybsza.
Mimo to otrzymujesz tylko reprezentację tekstową kolumny z dowolnym z tych zapytań. Nie jest tak łatwo uzyskać rzeczywisty typ .
Wzorzec
Przeprowadziłem szybki test porównawczy ze 100 tys. wierszy na stronach 9.1 i 9.2, aby znaleźć najszybsze:
Najszybszy:
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;
Drugi najszybszy:
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 tutaj
Stary sqlfiddle