sql >> Base de Datos >  >> RDS >> PostgreSQL

Postgres devuelve un valor predeterminado cuando una columna no existe

¿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