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

Postgres returnerer en standardværdi, når en kolonne ikke eksisterer

Hvorfor hacker Rowans hack arbejde (for det meste)?

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)

Normalt ville det slet ikke virke. Postgres analyserer SQL-sætningen og kaster en undtagelse, hvis nogen af de involverede kolonner eksisterer ikke.

Tricket er at indføre et tabelnavn (eller alias) med samme navn som det pågældende kolonnenavn. extra I dette tilfælde. Hvert tabelnavn kan refereres som en helhed, hvilket resulterer i, at hele rækken returneres som typen record . Og da hver type kan castes til text , kan vi caste hele denne post til text . På denne måde accepterer Postgres forespørgslen som gyldig.

Da kolonnenavne har forrang over tabelnavne, extra::text tolkes til at være kolonnen tbl.extra hvis kolonnen eksisterer. Ellers ville det som standard returnere hele rækken i tabellen extra - hvilket aldrig sker.

Prøv at vælge et andet tabelalias for extra at se selv.

Dette er et udokumenteret hack og kan gå i stykker hvis Postgres beslutter sig for at ændre den måde, SQL-strenge parses på abd planlagt i fremtidige versioner - selvom dette virker usandsynligt.

Entydig

Hvis du beslutter dig for at bruge dette, i det mindste gør det utvetydigt .

Et tabelnavn alene er ikke entydigt. En tabel med navnet "tbl" kan eksistere et vilkårligt antal gange i flere skemaer i den samme database, hvilket kan føre til meget forvirrende og fuldstændig falske resultater. Du bruger for at angive skemanavnet yderligere:

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;

Hurtigere

Da denne forespørgsel næppe kan overføres til andre RDBMS, foreslår jeg at bruge katalogtabel pg_attribute i stedet for informationsskemavisningen information_schema.columns . Cirka 10 gange hurtigere.

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

Bruger også den mere bekvemme og sikre cast til regclass . Se:

Du kan vedhæfte det nødvendige alias for at narre Postgres til hvilken som helst tabel, herunder selve den primære tabel. Du behøver slet ikke tilslutte dig en anden relation, som burde være hurtigst:

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;

Bekvemmelighed

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

Forenkler forespørgslen til:

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

Bruger formularen med yderligere relation her, da det viste sig at være hurtigere med funktionen.

Alligevel får du kun tekstrepræsentationen i kolonnen med nogen af ​​disse forespørgsler. Det er ikke så nemt at få den faktiske type .

Benchmark

Jeg kørte et hurtigt benchmark med 100.000 rækker på side 9.1 og 9.2 for at finde disse hurtigst:

Hurtigste:

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. hurtigste:

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 her
Gamle sqlfiddle



  1. MySQL gadenavn fuzzy søgning

  2. sqlalchemy.exc.NoSuchModuleError:Kan ikke indlæse plugin:sqlalchemy.dialects:postgres

  3. Postgresql lagret procedure returnerer tabel alle kolonner

  4. Lagret procedure for at få serverlagringsoplysninger på server