PostgreSQL
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> PostgreSQL

Postgres trả về giá trị mặc định khi một cột không tồn tại

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



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Sử dụng nhiều mục tiêu xung đột trong mệnh đề ON CONFLICT

  2. Sử dụng .pgpass từ Apache libphp5.so

  3. Sau khi nhập dữ liệu trong PostgreSQL, giá trị khóa trùng lặp vi phạm ràng buộc duy nhất

  4. PostgreSQL Meltdown Benchmarks

  5. Tạo bảng ở chế độ một người dùng trong postgres