PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Postgres renvoie une valeur par défaut lorsqu'une colonne n'existe pas

Pourquoi le hack de Rowan travailler (surtout) ?

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)

Normalement, cela ne fonctionnerait pas du tout. Postgres analyse l'instruction SQL et lève une exception si any des colonnes concernées n'existe pas.

L'astuce consiste à introduire un nom de table (ou alias) avec le même nom que le nom de la colonne en question. extra dans ce cas. Chaque nom de table peut être référencé dans son ensemble, ce qui entraîne le retour de la ligne entière en tant que type record . Et puisque chaque type peut être converti en text , nous pouvons convertir tout cet enregistrement en text . De cette façon, Postgres accepte la requête comme valide.

Étant donné que les noms de colonne ont priorité sur les noms de table, extra::text est interprété comme étant la colonne tbl.extra si la colonne existe. Sinon, il renverrait par défaut la ligne entière de la table extra - qui n'arrive jamais.

Essayez de choisir un autre alias de table pour extra à voir par vous-même.

Ceci est un hack non documenté et pourrait casser si Postgres décide de changer la façon dont les chaînes SQL sont analysées et prévues dans les futures versions - même si cela semble peu probable.

Sans ambiguïté

Si vous décidez de l'utiliser, au moins rendez-le sans ambiguïté .

Un nom de table seul n'est pas unique. Une table nommée "tbl" peut exister un nombre quelconque de fois dans plusieurs schémas de la même base de données, ce qui peut conduire à des résultats très déroutants et complètement faux. Vous avez besoin pour fournir le nom du schéma en plus :

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;

Plus rapide

Étant donné que cette requête est difficilement portable vers d'autres SGBDR, je suggère d'utiliser le table de catalogue pg_attribute au lieu de la vue du schéma d'informations information_schema.columns . Environ 10 fois plus rapide.

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);

Utilise également le cast plus pratique et sécurisé vers regclass . Voir :

Vous pouvez joindre l'alias nécessaire pour tromper Postgres sur tout table, y compris la table principale elle-même. Vous n'avez pas du tout besoin de vous joindre à une autre relation, ce qui devrait être le plus rapide :

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;

Commodité

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!)';

Simplifie la requête pour :

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);

Utilisation du formulaire avec une relation supplémentaire ici, car il s'est avéré plus rapide avec la fonction.

Pourtant, vous n'obtenez que la représentation textuelle de la colonne avec l'une de ces requêtes. Il n'est pas aussi simple d'obtenir le type réel .

Référence

J'ai exécuté un benchmark rapide avec 100 000 lignes sur les pages 9.1 et 9.2 pour trouver celles-ci les plus rapides :

Le plus rapide :

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ème plus rapide :

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<>violon ici
Ancien sqlfiddle