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