PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Postgres gibt einen Standardwert zurück, wenn eine Spalte nicht vorhanden ist

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