¿Por qué el truco de Rowan trabajo (principalmente)?
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, no funcionaría en absoluto. Postgres analiza la instrucción SQL y lanza una excepción si cualquiera de las columnas involucradas no existe.
El truco consiste en introducir un nombre de tabla (o alias) con el mismo nombre que el nombre de la columna en cuestión. extra
en este caso. Se puede hacer referencia a cada nombre de tabla como un todo, lo que da como resultado que la fila completa se devuelva como tipo record
. Y dado que cada tipo se puede convertir a text
, podemos convertir todo este registro en text
. De esta forma, Postgres acepta la consulta como válida.
Dado que los nombres de las columnas tienen prioridad sobre los nombres de las tablas, extra::text
se interpreta como la columna tbl.extra
si la columna existe. De lo contrario, devolvería por defecto la fila completa de la tabla extra
- lo que nunca sucede.
Intente elegir un alias de tabla diferente para extra
para verlo por ti mismo.
Este es un hackeo no documentado y podría fallar si Postgres decide cambiar la forma en que se analizan las cadenas SQL y se planifica en futuras versiones, aunque esto parezca poco probable.
Sin ambigüedades
Si decides usar esto, al menos que no sea ambiguo .
Un nombre de tabla por sí solo no es único. Una tabla llamada "tbl" puede existir cualquier cantidad de veces en varios esquemas de la misma base de datos, lo que podría generar resultados muy confusos y completamente falsos. Usted necesita para proporcionar el nombre del esquema adicionalmente:
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;
Más rápido
Dado que esta consulta difícilmente se puede transferir a otros RDBMS, sugiero usar tabla de catálogo pg_attribute
en lugar de la vista de esquema de información information_schema.columns
. Aproximadamente 10 veces más rápido.
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);
También usando la conversión más conveniente y segura a regclass
. Ver:
Puede adjuntar el alias necesario para engañar a Postgres con cualquier tabla, incluida la propia tabla principal. No necesita unirse a otra relación en absoluto, lo que debería ser más rápido:
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;
Conveniencia
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!)';
Simplifica la consulta a:
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 el formulario con relación adicional aquí, ya que resultó ser más rápido con la función.
Aún así, solo obtienes la representación de texto de la columna con alguna de estas consultas. No es tan simple obtener el tipo real .
Valor de referencia
Realicé un análisis comparativo rápido con 100 000 filas en las páginas 9.1 y 9.2 para encontrar que estas eran las más rápidas:
Más rápido:
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º más rápido:
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 aquí
Antiguo sqlfiddle