Varför hackar Rowans hack fungerar (för det mesta)?
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)
Normalt skulle det inte fungera alls. Postgres analyserar SQL-satsen och skapar ett undantag om något av de inblandade kolumnerna finns inte.
Tricket är att införa ett tabellnamn (eller alias) med samma namn som kolumnnamnet i fråga. extra
I detta fall. Varje tabellnamn kan refereras som en helhet, vilket resulterar i att hela raden returneras som typen record
. Och eftersom varje typ kan castas till text
, vi kan casta hela denna post till text
. På så sätt accepterar Postgres frågan som giltig.
Eftersom kolumnnamn har företräde framför tabellnamn, extra::text
tolkas som kolumnen tbl.extra
om kolumnen finns. Annars skulle det som standard returnera hela raden i tabellen extra
- vilket aldrig händer.
Försök att välja ett annat tabellalias för extra
att se själv.
Detta är ett odokumenterat hack och kan gå sönder om Postgres bestämmer sig för att ändra hur SQL-strängar tolkas abd planerade i framtida versioner - även om detta verkar osannolikt.
Entydigt
Om du bestämmer dig för att använda detta, åtminstone gör det entydigt .
Enbart ett tabellnamn är inte unikt. En tabell med namnet "tbl" kan finnas hur många gånger som helst i flera scheman i samma databas, vilket kan leda till mycket förvirrande och helt falska resultat. Du behöver för att ange schemanamnet ytterligare:
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;
Snabbare
Eftersom den här frågan knappast är portabel till andra RDBMS, föreslår jag att du använder katalogtabell pg_attribute
istället för informationsschemavyn information_schema.columns
. Cirka 10 gånger snabbare.
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);
Använder även den bekvämare och säkrare casten till regclass
. Se:
Du kan bifoga det nödvändiga aliaset för att lura Postgres till vilket som helst tabellen, inklusive den primära tabellen själv. Du behöver inte gå med i någon annan relation alls, vilket borde vara snabbast:
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;
Bekvämlighet
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!)';
Förenklar frågan till:
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);
Använder formuläret med ytterligare relation här, eftersom det visade sig vara snabbare med funktionen.
Ändå får du bara textrepresentationen i kolumnen med någon av dessa frågor. Det är inte lika enkelt att få den verkliga typen .
Benchmark
Jag körde en snabb benchmark med 100 000 rader på sidan 9.1 och 9.2 för att hitta dessa snabbast:
Snabbast:
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:a snabbast:
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);