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