sql >> Database >  >> RDS >> PostgreSQL

Postgres retourneert een standaardwaarde wanneer een kolom niet bestaat

Waarom doet Rowan's hack werk (meestal)?

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)

Normaal gesproken zou het helemaal niet werken. Postgres ontleedt de SQL-instructie en genereert een uitzondering indien any van de betrokken kolommen bestaat niet.

De truc is om een ​​tabelnaam (of alias) in te voeren met dezelfde naam als de kolomnaam in kwestie. extra in dit geval. Er kan naar elke tabelnaam als geheel worden verwezen, wat ertoe leidt dat de hele rij wordt geretourneerd als type record . En aangezien elk type kan worden gecast naar text , we kunnen dit hele record casten naar text . Op deze manier accepteert Postgres de zoekopdracht als geldig.

Aangezien kolomnamen voorrang hebben op tabelnamen, extra::text wordt geïnterpreteerd als de kolom tbl.extra als de kolom bestaat. Anders zou het standaard de hele rij van de tabel extra . retourneren - wat nooit gebeurt.

Probeer een andere tafelalias te kiezen voor extra om het zelf te zien.

Dit is een hack zonder papieren en kan breken als Postgres besluit om de manier waarop SQL-strings worden geparseerd en gepland in toekomstige versies te veranderen - ook al lijkt dit onwaarschijnlijk.

Ondubbelzinnig

Als u besluit dit te gebruiken, maak het in ieder geval ondubbelzinnig .

Een tabelnaam alleen is niet uniek. Een tabel met de naam "tbl" kan een willekeurig aantal keren voorkomen in meerdere schema's van dezelfde database, wat kan leiden tot zeer verwarrende en volledig onjuiste resultaten. Je nodig om de schemanaam aanvullend op te geven:

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;

Sneller

Aangezien deze query nauwelijks overdraagbaar is naar andere RDBMS, raad ik aan om de te gebruiken catalogustabel pg_attribute in plaats van de informatieschemaweergave information_schema.columns . Ongeveer 10 keer sneller.

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

Ook gebruikmakend van de handigere en veiligere cast naar regclass . Zie:

Je kunt de benodigde alias toevoegen om Postgres voor de gek te houden aan elke tabel, inclusief de primaire tabel zelf. Je hoeft helemaal niet lid te worden van een andere relatie, wat het snelst zou moeten zijn:

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;

Gemak

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

Vereenvoudigt de zoekopdracht tot:

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

Gebruik hier het formulier met aanvullende relatie, omdat het sneller bleek te zijn met de functie.

Toch krijg je alleen de tekstweergave van de kolom met een van deze vragen. Het is niet zo eenvoudig om het echte type te krijgen .

Benchmark

Ik heb een snelle benchmark uitgevoerd met 100.000 rijen op pagina 9.1 en 9.2 om te zien dat deze het snelst zijn:

Snelste:

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;

2e snelste:

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 hier
Oude sqlfiddle



  1. Formulier validatie

  2. Hoe Chinees karakter te detecteren in MySQL?

  3. Problemen met GROEP VOOR GEVAL

  4. Tabel maken met vb.net, mysql