Perché hack di Rowan lavoro (soprattutto)?
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)
Normalmente, non funzionerebbe affatto. Postgres analizza l'istruzione SQL e genera un'eccezione se qualsiasi delle colonne coinvolte non esiste.
Il trucco è introdurre un nome di tabella (o alias) con lo stesso nome del nome della colonna in questione. extra
in questo caso. Ogni nome di tabella può essere referenziato nel suo insieme, il che fa sì che l'intera riga venga restituita come tipo record
. E poiché ogni tipo può essere trasmesso a text
, possiamo trasmettere l'intero record a text
. In questo modo Postgres accetta la query come valida.
Poiché i nomi delle colonne hanno la precedenza sui nomi delle tabelle, extra::text
viene interpretato come la colonna tbl.extra
se la colonna esiste. In caso contrario, restituirebbe l'intera riga della tabella extra
- cosa che non succede mai.
Prova a scegliere un alias tabella diverso per extra
da vedere di persona.
Questo è un hack non documentato e potrebbe non funzionare se Postgres decide di cambiare il modo in cui le stringhe SQL vengono analizzate e pianificate nelle versioni future, anche se ciò sembra improbabile.
Non ambiguo
Se decidi di usarlo, almeno rendilo non ambiguo .
Il nome di una tabella da solo non è univoco. Una tabella denominata "tbl" può esistere un numero qualsiasi di volte in più schemi dello stesso database, il che potrebbe portare a risultati molto confusi e completamente falsi. Hai necessità per fornire in aggiunta il nome dello schema:
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;
Più veloce
Poiché questa query è difficilmente trasferibile su altri RDBMS, suggerisco di utilizzare tabella catalogo pg_attribute
invece dello schema delle informazioni, visualizza information_schema.columns
. Circa 10 volte più veloce.
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);
Usando anche il cast più comodo e sicuro per regclass
. Vedi:
Puoi allegare l'alias necessario per ingannare Postgres con qualsiasi tabella, inclusa la tabella primaria stessa. Non è necessario unirti a un'altra relazione, che dovrebbe essere la più veloce:
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;
Convenienza
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!)';
Semplifica la query in:
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);
Usando il modulo con relazione aggiuntiva qui, poiché si è rivelato più veloce con la funzione.
Tuttavia, ottieni solo la rappresentazione testuale della colonna con una di queste query. Non è così semplice ottenere il tipo reale .
Parametro
Ho eseguito un rapido benchmark con 100.000 righe a pagina 9.1 e 9.2 per trovare questi risultati più veloci:
Il più veloce:
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° più veloce:
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<>violino qui
Vecchio sqlfiddle