Waarom doet Rowan's hack werk (meestal)?
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)
Normaal gesproken zou het helemaal niet werken. Postgres ontleedt de SQL-instructie en genereert een uitzondering indien any van de betrokken kolommen bestaat niet.
De truc is om een tabelnaam (of alias) in te voeren met dezelfde naam als de kolomnaam in kwestie. extra
in dit geval. Er kan naar elke tabelnaam als geheel worden verwezen, wat ertoe leidt dat de hele rij wordt geretourneerd als type record
. En aangezien elk type kan worden gecast naar text
, we kunnen dit hele record casten naar text
. Op deze manier accepteert Postgres de zoekopdracht als geldig.
Aangezien kolomnamen voorrang hebben op tabelnamen, extra::text
wordt geïnterpreteerd als de kolom tbl.extra
als de kolom bestaat. Anders zou het standaard de hele rij van de tabel extra
. retourneren - wat nooit gebeurt.
Probeer een andere tafelalias te kiezen voor extra
om het zelf te zien.
Dit is een hack zonder papieren en kan breken als Postgres besluit om de manier waarop SQL-strings worden geparseerd en gepland in toekomstige versies te veranderen - ook al lijkt dit onwaarschijnlijk.
Ondubbelzinnig
Als u besluit dit te gebruiken, maak het in ieder geval ondubbelzinnig .
Een tabelnaam alleen is niet uniek. Een tabel met de naam "tbl" kan een willekeurig aantal keren voorkomen in meerdere schema's van dezelfde database, wat kan leiden tot zeer verwarrende en volledig onjuiste resultaten. Je nodig om de schemanaam aanvullend op te geven:
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;
Sneller
Aangezien deze query nauwelijks overdraagbaar is naar andere RDBMS, raad ik aan om de te gebruiken catalogustabel pg_attribute
in plaats van de informatieschemaweergave information_schema.columns
. Ongeveer 10 keer sneller.
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);
Ook gebruikmakend van de handigere en veiligere cast naar regclass
. Zie:
Je kunt de benodigde alias toevoegen om Postgres voor de gek te houden aan elke tabel, inclusief de primaire tabel zelf. Je hoeft helemaal niet lid te worden van een andere relatie, wat het snelst zou moeten zijn:
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;
Gemak
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!)';
Vereenvoudigt de zoekopdracht tot:
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);
Gebruik hier het formulier met aanvullende relatie, omdat het sneller bleek te zijn met de functie.
Toch krijg je alleen de tekstweergave van de kolom met een van deze vragen. Het is niet zo eenvoudig om het echte type te krijgen .
Benchmark
Ik heb een snelle benchmark uitgevoerd met 100.000 rijen op pagina 9.1 en 9.2 om te zien dat deze het snelst zijn:
Snelste:
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;
2e snelste:
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 hier
Oude sqlfiddle