PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Postgres zwraca domyślną wartość, gdy kolumna nie istnieje

Dlaczego Hakowanie Rowana praca (głównie)?

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)

Normalnie w ogóle by to nie zadziałało. Postgres analizuje instrukcję SQL i zgłasza wyjątek, jeśli jakikolwiek z zaangażowanych kolumn nie istnieje.

Sztuczka polega na wprowadzeniu nazwy tabeli (lub aliasu) o takiej samej nazwie, jak nazwa kolumny, o której mowa. extra w tym przypadku. Do każdej nazwy tabeli można się odwoływać jako całość, co skutkuje zwróceniem całego wiersza jako typu record . A ponieważ każdy typ można rzutować na text , możemy przesłać cały rekord do text . W ten sposób Postgres akceptuje zapytanie jako prawidłowe.

Ponieważ nazwy kolumn mają pierwszeństwo przed nazwami tabel, extra::text jest interpretowany jako kolumna tbl.extra jeśli kolumna istnieje. W przeciwnym razie domyślnie zwróci cały wiersz tabeli extra - co nigdy się nie zdarza.

Spróbuj wybrać inny alias tabeli dla extra aby zobaczyć na własne oczy.

To jest nieudokumentowany hack i może się zepsuć jeśli Postgres zdecyduje się zmienić sposób przetwarzania ciągów SQL i planowane w przyszłych wersjach - nawet jeśli wydaje się to mało prawdopodobne.

Jednoznaczne

Jeśli zdecydujesz się tego użyć, przynajmniej uczyń to jednoznacznym .

Sama nazwa tabeli nie jest unikalna. Tabela o nazwie „tbl” może istnieć dowolną liczbę razy w wielu schematach tej samej bazy danych, co może prowadzić do bardzo mylących i całkowicie fałszywych wyników. potrzebujesz aby dodatkowo podać nazwę schematu:

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;

Szybciej

Ponieważ to zapytanie jest trudne do przeniesienia na inne RDBMS, proponuję użyć tabela katalogowa pg_attribute zamiast widoku schematu informacji information_schema.columns . Około 10 razy szybciej.

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

Używając także wygodniejszego i bezpieczniejszego rzutowania na regclass . Zobacz:

Możesz dołączyć potrzebny alias, aby oszukać Postgresa do dowolnego tabela, w tym sama tabela podstawowa. Nie musisz w ogóle dołączać do innej relacji, co powinno być najszybsze:

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;

Wygoda

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

Upraszcza zapytanie do:

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

Korzystanie z formularza z dodatkową relacją tutaj, ponieważ funkcja okazała się szybsza.

Mimo to otrzymujesz tylko reprezentację tekstową kolumny z dowolnym z tych zapytań. Nie jest tak łatwo uzyskać rzeczywisty typ .

Wzorzec

Przeprowadziłem szybki test porównawczy ze 100 tys. wierszy na stronach 9.1 i 9.2, aby znaleźć najszybsze:

Najszybszy:

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;

Drugi najszybszy:

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 tutaj
Stary sqlfiddle



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Pięć fajnych rzeczy, których nauczyłem się na konferencji PostgreSQL w Europie 2018

  2. Zręczny problem podczas korzystania z PostgreSQL

  3. Pobierz tabelę i kolumnę z sekwencją

  4. Postgresql - Jak przyspieszyć aktualizację ogromnej tabeli (100 milionów wierszy)?

  5. Wywołanie funkcji zdefiniowanej przez użytkownika znajdującej się w postgres.c w postgreSQL przy użyciu GUI zdefiniowanego przez netbeans