PostgreSQL
 sql >> Teknologi Basis Data >  >> RDS >> PostgreSQL

Postgres mengembalikan nilai default ketika kolom tidak ada

Mengapa retas Rowan bekerja (kebanyakan)?

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)

Biasanya, itu tidak akan berhasil sama sekali. Postgres mem-parsing pernyataan SQL dan melempar pengecualian jika ada kolom yang terlibat tidak ada.

Caranya adalah dengan memperkenalkan nama tabel (atau alias) dengan nama yang sama dengan nama kolom yang dimaksud. extra pada kasus ini. Setiap nama tabel dapat direferensikan secara keseluruhan, yang mengakibatkan seluruh baris dikembalikan sebagai tipe record . Dan karena setiap jenis dapat dilemparkan ke text , kita dapat mentransmisikan seluruh rekaman ini ke text . Dengan cara ini, Postgres menerima kueri sebagai valid.

Karena nama kolom lebih diutamakan daripada nama tabel, extra::text ditafsirkan sebagai kolom tbl.extra jika kolom itu ada. Jika tidak, defaultnya adalah mengembalikan seluruh baris tabel extra - yang tidak pernah terjadi.

Coba pilih alias tabel yang berbeda untuk extra untuk melihat sendiri.

Ini adalah peretasan tidak berdokumen dan mungkin rusak jika Postgres memutuskan untuk mengubah cara string SQL diuraikan dan direncanakan di versi mendatang - meskipun ini tampaknya tidak mungkin.

Tidak ambigu

Jika Anda memutuskan untuk menggunakan ini, setidaknya membuatnya tidak ambigu .

Nama tabel saja tidak unik. Tabel bernama "tbl" dapat muncul beberapa kali dalam beberapa skema dari database yang sama, yang dapat menyebabkan hasil yang sangat membingungkan dan sepenuhnya salah. Anda membutuhkan untuk memberikan nama skema tambahan:

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;

Lebih cepat

Karena kueri ini hampir tidak portabel untuk RDBMS lain, saya sarankan untuk menggunakan tabel katalog pg_attribute alih-alih tampilan skema informasi information_schema.columns . Sekitar 10 kali lebih cepat.

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

Juga menggunakan gips yang lebih nyaman dan aman ke regclass . Lihat:

Anda dapat melampirkan alias yang diperlukan untuk mengelabui Postgres ke apa saja tabel, termasuk tabel utama itu sendiri. Anda tidak perlu bergabung ke relasi lain sama sekali, yang seharusnya tercepat:

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;

Kenyamanan

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

Sederhanakan kueri menjadi:

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

Menggunakan formulir dengan hubungan tambahan di sini, karena ternyata lebih cepat dengan fungsinya.

Namun, Anda hanya mendapatkan representasi teks kolom dengan salah satu kueri ini. Tidak mudah untuk mendapatkan tipe sebenarnya .

Tolok ukur

Saya menjalankan benchmark cepat dengan 100k baris pada hal 9.1 dan 9.2 untuk menemukan ini sebagai yang tercepat:

Tercepat:

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;

tercepat ke-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 di sini
Lama 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. Mengontrol durasi menunggu kunci PostgreSQL

  2. Lulus fungsi SQL dalam fungsi filter dplyr pada database

  3. Mengapa menggunakan bidang yang sama saat memfilter menyebabkan waktu eksekusi yang berbeda? (penggunaan indeks berbeda)

  4. Deploy Postgres11 ke Elastic Beanstalk - Memerlukan /etc/redhat-release

  5. Bagaimana cara menulis fungsi kombinatorik di postgres?