sql >> Databasteknik >  >> RDS >> PostgreSQL

Postgres returnerar ett standardvärde när en kolumn inte finns

Varför hackar Rowans hack fungerar (för det mesta)?

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 skulle det inte fungera alls. Postgres analyserar SQL-satsen och skapar ett undantag om något av de inblandade kolumnerna finns inte.

Tricket är att införa ett tabellnamn (eller alias) med samma namn som kolumnnamnet i fråga. extra I detta fall. Varje tabellnamn kan refereras som en helhet, vilket resulterar i att hela raden returneras som typen record . Och eftersom varje typ kan castas till text , vi kan casta hela denna post till text . På så sätt accepterar Postgres frågan som giltig.

Eftersom kolumnnamn har företräde framför tabellnamn, extra::text tolkas som kolumnen tbl.extra om kolumnen finns. Annars skulle det som standard returnera hela raden i tabellen extra - vilket aldrig händer.

Försök att välja ett annat tabellalias för extra att se själv.

Detta är ett odokumenterat hack och kan gå sönder om Postgres bestämmer sig för att ändra hur SQL-strängar tolkas abd planerade i framtida versioner - även om detta verkar osannolikt.

Entydigt

Om du bestämmer dig för att använda detta, åtminstone gör det entydigt .

Enbart ett tabellnamn är inte unikt. En tabell med namnet "tbl" kan finnas hur många gånger som helst i flera scheman i samma databas, vilket kan leda till mycket förvirrande och helt falska resultat. Du behöver för att ange schemanamnet ytterligare:

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;

Snabbare

Eftersom den här frågan knappast är portabel till andra RDBMS, föreslår jag att du använder katalogtabell pg_attribute istället för informationsschemavyn information_schema.columns . Cirka 10 gånger snabbare.

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

Använder även den bekvämare och säkrare casten till regclass . Se:

Du kan bifoga det nödvändiga aliaset för att lura Postgres till vilket som helst tabellen, inklusive den primära tabellen själv. Du behöver inte gå med i någon annan relation alls, vilket borde vara snabbast:

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;

Bekvämlighet

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

Förenklar frågan till:

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

Använder formuläret med ytterligare relation här, eftersom det visade sig vara snabbare med funktionen.

Ändå får du bara textrepresentationen i kolumnen med någon av dessa frågor. Det är inte lika enkelt att få den verkliga typen .

Benchmark

Jag körde en snabb benchmark med 100 000 rader på sidan 9.1 och 9.2 för att hitta dessa snabbast:

Snabbast:

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:a snabbast:

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<>fiol här
Gammal sqlfiddle



  1. Kan inte ansluta till den lokala MySQL-servern via sockeln '/var/run/mysqld/mysqld.sock' i Ubuntu 12.04.5 LTS

  2. Hur man skickar variabler till MYSQL med Python

  3. MySQL-frågan körs två gånger

  4. Extrahera dag/månad/år från en tidsstämpel på MYSQL