sql >> Databáze >  >> RDS >> PostgreSQL

Postgres vrátí výchozí hodnotu, když sloupec neexistuje

Proč Rowanův hack práce (většinou)?

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)

Normálně by to nefungovalo vůbec. Postgres analyzuje příkaz SQL a vyvolá výjimku, pokud je jaká ze zapojených sloupců neexistuje.

Trik je vložit název tabulky (nebo alias) se stejným názvem jako název příslušného sloupce. extra v tomto případě. Na každý název tabulky lze odkazovat jako na celek, což má za následek, že celý řádek bude vrácen jako typ record . A protože každý typ lze přetypovat na text , můžeme celý tento záznam přenést do text . Tímto způsobem Postgres přijme dotaz jako platný.

Protože názvy sloupců mají přednost před názvy tabulek, extra::text je interpretován jako sloupec tbl.extra pokud sloupec existuje. Jinak by implicitně vrátil celý řádek tabulky extra - což se nikdy nestane.

Zkuste vybrat jiný alias tabulky pro extra přesvědčte se sami.

Toto je nedokumentovaný hack a může se zlomit pokud se Postgres rozhodne změnit způsob, jakým jsou analyzovány řetězce SQL a plánováno v budoucích verzích – i když se to zdá nepravděpodobné.

Jednoznačně

Pokud rozhodnete se to použít, alespoň uveďte to jednoznačně .

Samotný název tabulky není jedinečný. Tabulka s názvem "tbl" může existovat mnohokrát ve více schématech stejné databáze, což by mohlo vést k velmi matoucím a zcela falešným výsledkům. potřebujete dodat název schématu:

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;

Rychlejší

Protože tento dotaz je stěží přenositelný na jiné RDBMS, doporučuji použít katalogová tabulka pg_attribute namísto zobrazení informačního schématu information_schema.columns . Asi 10krát rychleji.

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

Také pomocí pohodlnějšího a bezpečnějšího odesílání do regclass . Viz:

Potřebný alias k oklamání Postgres můžete připojit k jakémukoli tabulky, včetně samotné primární tabulky. Nemusíte se vůbec připojovat k dalšímu vztahu, který by měl být nejrychlejší:

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;

Pohodlí

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

Zjednodušuje dotaz na:

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

Zde použijte formulář s dalším vztahem, protože se ukázalo, že je s funkcí rychlejší.

Přesto získáte pouze textovou reprezentaci sloupce s kterýmkoli z těchto dotazů. Získat skutečný typ není tak jednoduché .

Srovnávací

Spustil jsem rychlý benchmark se 100 000 řádky na str. 9.1 a 9.2, abych zjistil, že jsou nejrychlejší:

Nejrychlejší:

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. nejrychlejší:

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 zde
Starý sqlfiddle



  1. Jak získat počet státu a města země pomocí SQL dotazu z databáze?

  2. Existuje v Ruby funkce nvl() nebo ji musím napsat sám?

  3. Co je MariaDB TX? Jak spravovat nový MariaDB MySQL Fork!

  4. GROUP BY a ORDER BY