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

Thay thế động cho trục xoay với CASE và GROUP BY

Nếu bạn chưa cài đặt mô-đun bổ sung tablefunc , chạy lệnh này một lần mỗi cơ sở dữ liệu:

CREATE EXTENSION tablefunc;

Trả lời câu hỏi

Một giải pháp bảng chữ cái rất cơ bản cho trường hợp của bạn:

SELECT * FROM crosstab(
  'SELECT bar, 1 AS cat, feh
   FROM   tbl_org
   ORDER  BY bar, feh')
 AS ct (bar text, val1 int, val2 int, val3 int);  -- more columns?

Khó khăn đặc biệt đây là không có danh mục (cat ) trong bảng cơ sở. Đối với biểu mẫu 1 tham số cơ bản chúng tôi chỉ có thể cung cấp một cột giả với một giá trị giả phục vụ như một danh mục. Giá trị vẫn bị bỏ qua.

Đây là một trong những trường hợp hiếm hoi trong đó tham số thứ hai cho crosstab() chức năng không cần thiết , bởi vì tất cả NULL giá trị chỉ xuất hiện trong các cột lủng lẳng ở bên phải theo định nghĩa của vấn đề này. Và đơn đặt hàng có thể được xác định bởi giá trị .

Nếu chúng tôi có một danh mục thực tế cột có tên xác định thứ tự của các giá trị trong kết quả, chúng tôi cần biểu mẫu 2 tham số của crosstab() . Ở đây tôi tổng hợp một cột danh mục với sự trợ giúp của hàm window row_number() , đến cơ sở crosstab() trên:

SELECT * FROM crosstab(
   $$
   SELECT bar, val, feh
   FROM  (
      SELECT *, 'val' || row_number() OVER (PARTITION BY bar ORDER BY feh) AS val
      FROM tbl_org
      ) x
   ORDER BY 1, 2
   $$
 , $$VALUES ('val1'), ('val2'), ('val3')$$         -- more columns?
) AS ct (bar text, val1 int, val2 int, val3 int);  -- more columns?

Phần còn lại là khá nhiều. Tìm thêm lời giải thích và liên kết trong các câu trả lời có liên quan chặt chẽ này.

Khái niệm cơ bản:
Đọc phần này trước nếu bạn chưa quen với crosstab() chức năng!

  • Truy vấn bảng chéo PostgreSQL

Nâng cao:

  • Xoay vòng trên nhiều cột bằng cách sử dụng Tablefunc
  • Hợp nhất bảng và nhật ký thay đổi thành một dạng xem trong PostgreSQL

Thiết lập thử nghiệm phù hợp

Đó là cách bạn nên cung cấp một trường hợp thử nghiệm để bắt đầu:

CREATE TEMP TABLE tbl_org (id int, feh int, bar text);
INSERT INTO tbl_org (id, feh, bar) VALUES
   (1, 10, 'A')
 , (2, 20, 'A')
 , (3,  3, 'B')
 , (4,  4, 'B')
 , (5,  5, 'C')
 , (6,  6, 'D')
 , (7,  7, 'D')
 , (8,  8, 'D');

Bảng chéo động?

Không rất năng động , như @Clodoaldo đã nhận xét. Khó có thể đạt được kiểu trả về động với plpgsql. Nhưng có cách xung quanh nó - với một số hạn chế .

Vì vậy, không làm phức tạp thêm phần còn lại, tôi chứng minh bằng cách đơn giản hơn trường hợp thử nghiệm:

CREATE TEMP TABLE tbl (row_name text, attrib text, val int);
INSERT INTO tbl (row_name, attrib, val) VALUES
   ('A', 'val1', 10)
 , ('A', 'val2', 20)
 , ('B', 'val1', 3)
 , ('B', 'val2', 4)
 , ('C', 'val1', 5)
 , ('D', 'val3', 8)
 , ('D', 'val1', 6)
 , ('D', 'val2', 7);

Gọi:

SELECT * FROM crosstab('SELECT row_name, attrib, val FROM tbl ORDER BY 1,2')
AS ct (row_name text, val1 int, val2 int, val3 int);

Lợi nhuận:

 row_name | val1 | val2 | val3
----------+------+------+------
 A        | 10   | 20   |
 B        |  3   |  4   |
 C        |  5   |      |
 D        |  6   |  7   |  8

Tính năng tích hợp của tablefunc mô-đun

Mô-đun tablefunc cung cấp một cơ sở hạ tầng đơn giản cho crosstab() chung cuộc gọi mà không cung cấp danh sách định nghĩa cột. Một số hàm được viết bằng C (thường rất nhanh):

crosstabN()

crosstab1() - crosstab4() được xác định trước. Một điểm nhỏ:họ yêu cầu và trả lại tất cả text . Vì vậy, chúng tôi cần truyền integer của chúng tôi các giá trị. Nhưng nó đơn giản hóa cuộc gọi:

SELECT * FROM crosstab4('SELECT row_name, attrib, val::text  -- cast!
                         FROM tbl ORDER BY 1,2')

Kết quả:

 row_name | category_1 | category_2 | category_3 | category_4
----------+------------+------------+------------+------------
 A        | 10         | 20         |            |
 B        | 3          | 4          |            |
 C        | 5          |            |            |
 D        | 6          | 7          | 8          |

crosstab() tùy chỉnh chức năng

Đối với các cột khác hoặc các loại dữ liệu khác , chúng tôi tạo loại kết hợp của riêng mình và chức năng (một lần).
Loại:

CREATE TYPE tablefunc_crosstab_int_5 AS (
  row_name text, val1 int, val2 int, val3 int, val4 int, val5 int);

Chức năng:

CREATE OR REPLACE FUNCTION crosstab_int_5(text)
  RETURNS SETOF tablefunc_crosstab_int_5
AS '$libdir/tablefunc', 'crosstab' LANGUAGE c STABLE STRICT;

Gọi:

SELECT * FROM crosstab_int_5('SELECT row_name, attrib, val   -- no cast!
                              FROM tbl ORDER BY 1,2');

Kết quả:

 row_name | val1 | val2 | val3 | val4 | val5
----------+------+------+------+------+------
 A        |   10 |   20 |      |      |
 B        |    3 |    4 |      |      |
 C        |    5 |      |      |      |
 D        |    6 |    7 |    8 |      |

Một đa hình, hàm động cho tất cả

Điều này vượt xa những gì được đề cập bởi tablefunc mô-đun.
Để làm cho kiểu trả về là động, tôi sử dụng kiểu đa hình với một kỹ thuật được nêu chi tiết trong câu trả lời có liên quan này:

  • Cấu trúc lại một hàm PL / pgSQL để trả về kết quả đầu ra của các truy vấn SELECT khác nhau

Dạng 1 tham số:

CREATE OR REPLACE FUNCTION crosstab_n(_qry text, _rowtype anyelement)
  RETURNS SETOF anyelement AS
$func$
BEGIN
   RETURN QUERY EXECUTE 
   (SELECT format('SELECT * FROM crosstab(%L) t(%s)'
                , _qry
                , string_agg(quote_ident(attname) || ' ' || atttypid::regtype
                           , ', ' ORDER BY attnum))
    FROM   pg_attribute
    WHERE  attrelid = pg_typeof(_rowtype)::text::regclass
    AND    attnum > 0
    AND    NOT attisdropped);
END
$func$  LANGUAGE plpgsql;

Quá tải với biến thể này cho dạng 2 tham số:

CREATE OR REPLACE FUNCTION crosstab_n(_qry text, _cat_qry text, _rowtype anyelement)
  RETURNS SETOF anyelement AS
$func$
BEGIN
   RETURN QUERY EXECUTE 
   (SELECT format('SELECT * FROM crosstab(%L, %L) t(%s)'
                , _qry, _cat_qry
                , string_agg(quote_ident(attname) || ' ' || atttypid::regtype
                           , ', ' ORDER BY attnum))
    FROM   pg_attribute
    WHERE  attrelid = pg_typeof(_rowtype)::text::regclass
    AND    attnum > 0
    AND    NOT attisdropped);
END
$func$  LANGUAGE plpgsql;

pg_typeof(_rowtype)::text::regclass :Có một loại hàng được xác định cho mọi loại kết hợp do người dùng xác định, để các thuộc tính (cột) được liệt kê trong danh mục hệ thống pg_attribute . Làn đường nhanh chóng để có được nó:ép kiểu đã đăng ký (regtype ) thành text và truyền text này thành regclass .

Tạo các loại kết hợp một lần:

Bạn cần xác định một lần mọi kiểu trả về bạn sẽ sử dụng:

CREATE TYPE tablefunc_crosstab_int_3 AS (
    row_name text, val1 int, val2 int, val3 int);

CREATE TYPE tablefunc_crosstab_int_4 AS (
    row_name text, val1 int, val2 int, val3 int, val4 int);

...

Đối với các cuộc gọi đặc biệt, bạn cũng có thể chỉ tạo bảng tạm thời đến cùng một hiệu ứng (tạm thời):

CREATE TEMP TABLE temp_xtype7 AS (
    row_name text, x1 int, x2 int, x3 int, x4 int, x5 int, x6 int, x7 int);

Hoặc sử dụng loại bảng hiện có, dạng xem hoặc dạng xem cụ thể hóa nếu có.

Gọi

Sử dụng các loại hàng trên:

Dạng 1 tham số (không có giá trị bị thiếu):

SELECT * FROM crosstab_n(
   'SELECT row_name, attrib, val FROM tbl ORDER BY 1,2'
 , NULL::tablefunc_crosstab_int_3);

Dạng 2 tham số (có thể thiếu một số giá trị):

SELECT * FROM crosstab_n(
   'SELECT row_name, attrib, val FROM tbl ORDER BY 1'
 , $$VALUES ('val1'), ('val2'), ('val3')$$
 , NULL::tablefunc_crosstab_int_3);

một chức năng này hoạt động cho tất cả các loại trả về, trong khi crosstabN() khung được cung cấp bởi tablefunc mô-đun cần một chức năng riêng biệt cho từng loại.
Nếu bạn đã đặt tên các loại của mình theo thứ tự như đã trình bày ở trên, bạn chỉ phải thay thế số in đậm. Để tìm số danh mục tối đa trong bảng cơ sở:

SELECT max(count(*)) OVER () FROM tbl  -- returns 3
GROUP  BY row_name
LIMIT  1;

Điều đó gần như năng động nếu bạn muốn từng cột riêng lẻ . Mảng như được chứng minh bởi @Clocoaldo hoặc biểu diễn văn bản đơn giản hoặc kết quả được bao bọc trong một loại tài liệu như json hoặc hstore có thể hoạt động động cho bất kỳ số lượng danh mục nào.

Tuyên bố từ chối trách nhiệm:
Nó luôn tiềm ẩn nguy hiểm khi đầu vào của người dùng được chuyển đổi thành mã. Đảm bảo rằng điều này không thể được sử dụng cho SQL injection. Không chấp nhận đầu vào từ những người dùng không đáng tin cậy (trực tiếp).

Gọi cho câu hỏi ban đầu:

SELECT * FROM crosstab_n('SELECT bar, 1, feh FROM tbl_org ORDER BY 1,2'
                       , NULL::tablefunc_crosstab_int_3);


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Cách justify_days () hoạt động trong PostgreSQL

  2. Cách pg_typeof () hoạt động trong PostgreSQL

  3. Các điều khoản cần biết:Tất cả về CHỌN, TỪ, ĐÂU, NHÓM THEO, CÓ, ĐẶT HÀNG THEO, và GIỚI HẠN

  4. Bảo vệ sar (và cách cấu hình nó)

  5. Hướng dẫn của một chuyên gia về sao chép Slony cho PostgreSQL