Tại sao Rowan lại bị hack làm việc (chủ yếu)?
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)
Thông thường, nó sẽ không hoạt động ở tất cả. Postgres phân tích cú pháp câu lệnh SQL và ném một ngoại lệ nếu bất kỳ trong số các cột liên quan không tồn tại.
Bí quyết là giới thiệu một tên bảng (hoặc bí danh) trùng với tên cột được đề cập. extra
trong trường hợp này. Mọi tên bảng đều có thể được tham chiếu tổng thể, điều này dẫn đến toàn bộ hàng được trả về dưới dạng loại record
. Và vì mọi loại đều có thể được chuyển thành text
, chúng tôi có thể truyền toàn bộ bản ghi này sang text
. Bằng cách này, Postgres chấp nhận truy vấn là hợp lệ.
Vì tên cột được ưu tiên hơn tên bảng nên extra::text
được hiểu là cột tbl.extra
nếu cột tồn tại. Nếu không, nó sẽ mặc định trả về toàn bộ hàng của bảng extra
- điều này không bao giờ xảy ra.
Cố gắng chọn một bí danh bảng khác để có thêm extra
để tự mình xem.
Đây là một vụ tấn công không có giấy tờ và có thể bị phá vỡ nếu Postgres quyết định thay đổi cách phân tích cú pháp chuỗi SQL được lên kế hoạch trong các phiên bản tương lai - mặc dù điều này có vẻ khó xảy ra.
Rõ ràng
Nếu bạn quyết định sử dụng điều này, ít nhất làm cho nó rõ ràng .
Một tên bảng không phải là duy nhất. Một bảng có tên "tbl" có thể tồn tại bất kỳ số lần nào trong nhiều lược đồ của cùng một cơ sở dữ liệu, điều này có thể dẫn đến kết quả rất khó hiểu và hoàn toàn sai. Bạn cần để cung cấp thêm tên giản đồ:
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;
Nhanh hơn
Vì truy vấn này khó có thể di chuyển đến các RDBMS khác, tôi khuyên bạn nên sử dụng bảng danh mục pg_attribute
thay vì chế độ xem giản đồ thông tin information_schema.columns
. Nhanh hơn khoảng 10 lần.
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);
Đồng thời sử dụng truyền đến regclass
thuận tiện và an toàn hơn . Xem:
Bạn có thể đính kèm bí danh cần thiết để đánh lừa Postgres thành bất kỳ bảng, bao gồm cả bảng chính. Bạn không cần phải tham gia vào một mối quan hệ khác, điều này sẽ nhanh nhất:
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;
Tiện lợi
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!)';
Đơn giản hóa truy vấn thành:
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);
Sử dụng biểu mẫu có quan hệ bổ sung ở đây, vì nó hóa ra nhanh hơn với hàm.
Tuy nhiên, bạn chỉ nhận được biểu diễn văn bản của cột với bất kỳ truy vấn nào trong số này. Không đơn giản để có được loại thực tế .
Điểm chuẩn
Tôi đã chạy một điểm chuẩn nhanh với 100 nghìn hàng trên trang 9.1 và 9.2 để thấy những hàng này là nhanh nhất:
Nhanh nhất:
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;
Nhanh thứ 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 tại đây
sqlfiddle
cũ