PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Postgres връща стойност по подразбиране, когато колона не съществува

Защо хаква Rowan работа (предимно)?

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)

Обикновено това изобщо няма да работи. Postgres анализира SQL израза и хвърля изключение, ако има такова от включените колони не съществува.

Номерът е да въведете име на таблица (или псевдоним) със същото име като името на въпросната колона. extra в такъв случай. Всяко име на таблица може да бъде препратено като цяло, което води до връщане на целия ред като тип record . И тъй като всеки тип може да бъде преобразуван в text , можем да прехвърлим целия този запис към text . По този начин Postgres приема заявката за валидна.

Тъй като имената на колони имат предимство пред имената на таблици, extra::text се интерпретира като колоната tbl.extra ако колоната съществува. В противен случай по подразбиране ще се върне целият ред на таблицата extra - което никога не се случва.

Опитайте да изберете различен псевдоним на таблица за extra за да видите сами.

Това е недокументиран хак и може да се повреди ако Postgres реши да промени начина, по който SQL низовете се анализират и планира в бъдещи версии - въпреки че това изглежда малко вероятно.

Недвусмислено

Ако решите да използвате това, поне направете го недвусмислено .

Самото име на таблица не е уникално. Таблица с име "tbl" може да съществува произволен брой пъти в множество схеми на една и съща база данни, което може да доведе до много объркващи и напълно неверни резултати. Вие трябвате за да предоставите името на схемата допълнително:

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;

По-бързо

Тъй като тази заявка е трудно преносима към други RDBMS, предлагам да използвате каталожна таблица pg_attribute вместо изглед на информационна схема information_schema.columns . Около 10 пъти по-бързо.

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

Също така използвайки по-удобното и сигурно прехвърляне към regclass . Вижте:

Можете да прикачите необходимия псевдоним, за да заблудите Postgres към всеки таблица, включително самата основна таблица. Изобщо не е необходимо да се присъединявате към друга релация, което трябва да е най-бързо:

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;

Удобство

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

Опростява заявката до:

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

Използване на формата с допълнителна релация тук, тъй като се оказа по-бързо с функцията.

Все пак получавате само текстово представяне на колоната с някоя от тези заявки. Не е толкова лесно да получите действителния тип .

Бенчмарк

Проведох бърз сравнителен анализ със 100 000 реда на стр. 9.1 и 9.2, за да установя, че тези са най-бързи:

Най-бърз:

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-ро най-бързо:

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 тук
Стар 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. PostgreSQL заявка с 'ANY' не работи

  2. Java Crosstab - заявка за подготвени декларации

  3. Допълване на нули вляво в postgreSQL

  4. Съхранявайте интервал от време в PostgreSQL от Rails

  5. Текстово търсене в Django с частично съвпадение на изречения, актуализация на django3