Warum funktioniert Rowans Hack Arbeit (meistens)?
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)
Normalerweise würde es überhaupt nicht funktionieren. Postgres analysiert die SQL-Anweisung und löst eine Ausnahme aus, falls eine vorliegt der beteiligten Spalten existiert nicht.
Der Trick besteht darin, einen Tabellennamen (oder Alias) einzuführen, der denselben Namen wie der betreffende Spaltenname hat. extra
in diesem Fall. Jeder Tabellenname kann als Ganzes referenziert werden, was dazu führt, dass die gesamte Zeile als Typ record
zurückgegeben wird . Und da jeder Typ in text
gecastet werden kann , können wir diesen gesamten Datensatz in text
umwandeln . Auf diese Weise akzeptiert Postgres die Abfrage als gültig.
Da Spaltennamen Vorrang vor Tabellennamen haben, extra::text
wird als Spalte tbl.extra
interpretiert wenn die Spalte existiert. Andernfalls würde standardmäßig die gesamte Zeile der Tabelle extra
zurückgegeben - was nie passiert.
Versuchen Sie, einen anderen Tabellenalias für extra
auszuwählen um es selbst zu sehen.
Dies ist ein undokumentierter Hack und könnte kaputt gehen falls Postgres beschließt, die Art und Weise, wie SQL-Strings geparst werden, zu ändern und in zukünftigen Versionen geplant ist - auch wenn dies unwahrscheinlich erscheint.
Eindeutig
Wenn Wenn Sie sich dafür entscheiden, machen Sie es zumindest eindeutig .
Ein Tabellenname allein ist nicht eindeutig. Eine Tabelle mit dem Namen "tbl" kann beliebig oft in mehreren Schemas derselben Datenbank vorhanden sein, was zu sehr verwirrenden und völlig falschen Ergebnissen führen kann. Sie brauchen den Schemanamen zusätzlich zu liefern:
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;
Schneller
Da diese Abfrage kaum auf andere RDBMS portierbar ist, schlage ich vor, den zu verwenden Katalogtabelle pg_attribute
anstelle der Informationsschemaansicht information_schema.columns
. Etwa 10-mal schneller.
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);
Verwenden Sie auch die bequemere und sicherere Umwandlung in regclass
. Siehe:
Sie können den benötigten Alias anhängen, um Postgres an beliebige zu täuschen Tabelle, einschließlich der Primärtabelle selbst. Sie müssen überhaupt keiner anderen Relation beitreten, was am schnellsten sein sollte:
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;
Bequemlichkeit
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!)';
Vereinfacht die Abfrage zu:
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);
Verwenden Sie hier das Formular mit zusätzlicher Beziehung, da es sich mit der Funktion als schneller herausstellte.
Trotzdem erhalten Sie nur die Textdarstellung der Spalte mit einer dieser Abfragen. Es ist nicht so einfach, den tatsächlichen Typ zu ermitteln .
Benchmark
Ich habe einen schnellen Benchmark mit 100.000 Zeilen auf Seite 9.1 und 9.2 durchgeführt, um festzustellen, dass diese am schnellsten sind:
Am schnellsten:
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;
Zweitschnellste:
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
Altes sqlfiddle