PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Postgres restituisce un valore predefinito quando una colonna non esiste

Perché hack di Rowan lavoro (soprattutto)?

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, non funzionerebbe affatto. Postgres analizza l'istruzione SQL e genera un'eccezione se qualsiasi delle colonne coinvolte non esiste.

Il trucco è introdurre un nome di tabella (o alias) con lo stesso nome del nome della colonna in questione. extra in questo caso. Ogni nome di tabella può essere referenziato nel suo insieme, il che fa sì che l'intera riga venga restituita come tipo record . E poiché ogni tipo può essere trasmesso a text , possiamo trasmettere l'intero record a text . In questo modo Postgres accetta la query come valida.

Poiché i nomi delle colonne hanno la precedenza sui nomi delle tabelle, extra::text viene interpretato come la colonna tbl.extra se la colonna esiste. In caso contrario, restituirebbe l'intera riga della tabella extra - cosa che non succede mai.

Prova a scegliere un alias tabella diverso per extra da vedere di persona.

Questo è un hack non documentato e potrebbe non funzionare se Postgres decide di cambiare il modo in cui le stringhe SQL vengono analizzate e pianificate nelle versioni future, anche se ciò sembra improbabile.

Non ambiguo

Se decidi di usarlo, almeno rendilo non ambiguo .

Il nome di una tabella da solo non è univoco. Una tabella denominata "tbl" può esistere un numero qualsiasi di volte in più schemi dello stesso database, il che potrebbe portare a risultati molto confusi e completamente falsi. Hai necessità per fornire in aggiunta il nome dello schema:

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;

Più veloce

Poiché questa query è difficilmente trasferibile su altri RDBMS, suggerisco di utilizzare tabella catalogo pg_attribute invece dello schema delle informazioni, visualizza information_schema.columns . Circa 10 volte più veloce.

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

Usando anche il cast più comodo e sicuro per regclass . Vedi:

Puoi allegare l'alias necessario per ingannare Postgres con qualsiasi tabella, inclusa la tabella primaria stessa. Non è necessario unirti a un'altra relazione, che dovrebbe essere la più veloce:

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;

Convenienza

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

Semplifica la query in:

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 il modulo con relazione aggiuntiva qui, poiché si è rivelato più veloce con la funzione.

Tuttavia, ottieni solo la rappresentazione testuale della colonna con una di queste query. Non è così semplice ottenere il tipo reale .

Parametro

Ho eseguito un rapido benchmark con 100.000 righe a pagina 9.1 e 9.2 per trovare questi risultati più veloci:

Il più veloce:

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° più veloce:

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<>violino qui
Vecchio sqlfiddle