Làm cách nào để tạo CROSS THAM GIA xoay vòng trong đó không xác định định nghĩa bảng kết quả?


17

Đưa ra hai bảng có số lượng hàng không xác định với tên và giá trị, làm thế nào tôi sẽ hiển thị một trục CROSS JOINcủa hàm trên các giá trị của chúng.

CREATE TEMP TABLE foo AS
SELECT x::text AS name, x::int
FROM generate_series(1,10) AS t(x);

CREATE TEMP TABLE bar AS
SELECT x::text AS name, x::int
FROM generate_series(1,5) AS t(x);

Ví dụ: nếu hàm đó là phép nhân, làm thế nào tôi có thể tạo bảng (phép nhân) như bảng dưới đây,

Bảng nhân phổ biến của 1..12

Tất cả các (arg1,arg2,result)hàng đó có thể được tạo bằng

SELECT foo.name AS arg1, bar.name AS arg2, foo.x*bar.x AS result
FROM foo
CROSS JOIN bar; 

Vì vậy, đây chỉ là một câu hỏi về cách trình bày, tôi muốn nó cũng hoạt động với một tên tùy chỉnh - một tên không chỉ đơn giản là đối số CASTed thành văn bản mà được đặt trong bảng,

CREATE TEMP TABLE foo AS
SELECT chr(x+64) AS name, x::int
FROM generate_series(1,10) AS t(x);

CREATE TEMP TABLE bar AS
SELECT chr(x+72) AS name, x::int
FROM generate_series(1,5) AS t(x);

Tôi nghĩ rằng điều này sẽ dễ dàng thực hiện được với CROSSTAB có khả năng loại trả về động.

SELECT * FROM crosstab(
  '
    SELECT foo.x AS arg1, bar.x AS arg2, foo.x*bar.x
    FROM foo
    CROSS JOIN bar
  ', 'SELECT DISTINCT name FROM bar'
) AS **MAGIC**

Nhưng, không có **MAGIC**, tôi nhận được

ERROR:  a column definition list is required for functions returning "record"
LINE 1: SELECT * FROM crosstab(

Để tham khảo, sử dụng các ví dụ trên với tên, đây là một cái gì đó giống như những gì tablefunchọ crosstab()muốn.

SELECT * FROM crosstab(
  '
    SELECT foo.x AS arg1, bar.x AS arg2, foo.x*bar.x
    FROM foo
    CROSS JOIN bar
  '
) AS t(row int, i int, j int, k int, l int, m int);

Nhưng, bây giờ chúng tôi quay lại đưa ra các giả định về nội dung và kích thước của barbảng trong ví dụ của chúng tôi. Vì thế nếu,

  1. Các bảng có độ dài không xác định,
  2. Sau đó, liên kết chéo đại diện cho một khối có kích thước không xác định (vì ở trên),
  3. Các tên catagory (cách nói chéo tab) nằm trong bảng

Điều tốt nhất chúng ta có thể làm trong PostgreSQL mà không có "danh sách định nghĩa cột" để tạo ra loại bản trình bày đó là gì?


1
Kết quả JSON sẽ là một cách tiếp cận tốt? Một ARRAY sẽ là một aprpoach tốt? Theo cách này, định nghĩa của "bảng đầu ra" đã được biết (và cố định). Bạn đặt tính linh hoạt trong JSON hoặc ARRAY. Tôi đoán nó sẽ phụ thuộc rất nhiều công cụ được sử dụng sau đó để xử lý thông tin.
joanolo

Tôi muốn nó giống như ở trên, nếu có thể.
Evan Carroll

Câu trả lời:


11

Trường hợp đơn giản, SQL tĩnh

Giải pháp không động với crosstab()trường hợp đơn giản:

SELECT * FROM crosstab(
  'SELECT b.x, f.name, f.x * b.x AS prod
   FROM   foo f, bar b
   ORDER  BY 1, 2'
   ) AS ct (x int, "A" int, "B" int, "C" int, "D" int, "E" int
                 , "F" int, "G" int, "H" int, "I" int, "J" int);

Tôi đặt hàng cột kết quả bằng foo.name, không foo.x. Cả hai tình cờ được sắp xếp song song, nhưng đó chỉ là thiết lập đơn giản. Chọn thứ tự sắp xếp đúng cho trường hợp của bạn. Giá trị thực của cột thứ hai không liên quan trong truy vấn này (dạng 1 tham số của crosstab()).

Chúng tôi thậm chí không cần crosstab()với 2 tham số vì không có giá trị thiếu theo định nghĩa. Xem:

(Bạn đã sửa truy vấn chéo bảng trong câu hỏi bằng cách thay thế foobằng barlần chỉnh sửa sau. Điều này cũng sửa truy vấn, nhưng vẫn hoạt động với các tên từ foo.)

Kiểu trả về không xác định, SQL động

Tên cột và loại không thể động. SQL yêu cầu biết số lượng, tên và loại cột kết quả tại thời điểm cuộc gọi. Bằng cách khai báo rõ ràng hoặc từ thông tin trong danh mục hệ thống (Đó là những gì xảy ra với SELECT * FROM tbl: Postgres tra cứu định nghĩa bảng đã đăng ký.)

Bạn muốn Postgres lấy được các cột kết quả từ dữ liệu trong bảng người dùng. Sẽ không xảy ra.

Bằng cách này hay cách khác, bạn cần hai chuyến đi khứ hồi đến máy chủ. Hoặc bạn tạo một con trỏ và sau đó đi qua nó. Hoặc bạn tạo một bảng tạm thời và sau đó chọn từ nó. Hoặc bạn đăng ký một loại và sử dụng nó trong cuộc gọi.

Hoặc bạn chỉ cần tạo truy vấn trong một bước và thực hiện nó trong bước tiếp theo:

SELECT $$SELECT * FROM crosstab(
  'SELECT b.x, f.name, f.x * b.x AS prod
   FROM   foo f, bar b
   ORDER  BY 1, 2'
   ) AS ct (x int, $$
 || string_agg(quote_ident(name), ' int, ' ORDER BY name) || ' int)'
FROM   foo;

Điều này tạo ra các truy vấn ở trên, một cách linh hoạt. Thực hiện nó trong bước tiếp theo.

Tôi đang sử dụng dấu ngoặc kép ( $$) để tiếp tục xử lý các trích dẫn lồng nhau đơn giản. Xem:

quote_ident() là điều cần thiết để thoát khỏi các tên cột bất hợp pháp (và có thể bảo vệ chống lại việc tiêm SQL).

Liên quan:


Tôi nhận thấy rằng việc thực hiện truy vấn mà bạn gọi là "Kiểu trả về không xác định, SQL động" thực sự chỉ trả về một chuỗi đại diện cho một truy vấn khác và sau đó bạn nói "thực hiện nó trong bước tiếp theo". Điều này có nghĩa là khó có thể tạo ra một cái nhìn cụ thể hóa về điều này?
Colin D

@ColinD: Không khó, nhưng đơn giản là không thể. Bạn có thể tạo MV từ SQL được tạo với loại trả về đã biết. Nhưng bạn không thể có một MV với loại trả lại không xác định.
Erwin Brandstetter

10

Điều tốt nhất chúng ta có thể làm trong PostgreSQL mà không có "danh sách định nghĩa cột" để tạo ra loại bản trình bày đó là gì?

Nếu bạn đóng khung vấn đề này như một vấn đề về bản trình bày, bạn có thể xem xét tính năng trình bày sau truy vấn.

Các phiên bản mới hơn của psql(9.6) đi kèm \crosstabview, hiển thị kết quả trong biểu diễn chéo bảng mà không có hỗ trợ SQL (vì SQL không thể tạo ra điều này trực tiếp, như đã đề cập trong câu trả lời của @ Erwin: SQL yêu cầu biết số lượng, tên và loại cột kết quả trong thời gian gọi )

Chẳng hạn, truy vấn đầu tiên của bạn đưa ra:

SELECT foo.name AS arg1, bar.name AS arg2, foo.x*bar.x AS result
FROM foo
CROSS JOIN bar
\crosstabview

 arg1 | 1  | 2  | 3  | 4  | 5  
------+----+----+----+----+----
 1    |  1 |  2 |  3 |  4 |  5
 2    |  2 |  4 |  6 |  8 | 10
 3    |  3 |  6 |  9 | 12 | 15
 4    |  4 |  8 | 12 | 16 | 20
 5    |  5 | 10 | 15 | 20 | 25
 6    |  6 | 12 | 18 | 24 | 30
 7    |  7 | 14 | 21 | 28 | 35
 8    |  8 | 16 | 24 | 32 | 40
 9    |  9 | 18 | 27 | 36 | 45
 10   | 10 | 20 | 30 | 40 | 50
(10 rows)

Ví dụ thứ hai với tên cột ASCII cho:

SELECT foo.name AS arg1, bar.name AS arg2, foo.x*bar.x
    FROM foo
    CROSS JOIN bar
  \crosstabview

 arg1 | I  | J  | K  | L  | M  
------+----+----+----+----+----
 A    |  1 |  2 |  3 |  4 |  5
 B    |  2 |  4 |  6 |  8 | 10
 C    |  3 |  6 |  9 | 12 | 15
 D    |  4 |  8 | 12 | 16 | 20
 E    |  5 | 10 | 15 | 20 | 25
 F    |  6 | 12 | 18 | 24 | 30
 G    |  7 | 14 | 21 | 28 | 35
 H    |  8 | 16 | 24 | 32 | 40
 I    |  9 | 18 | 27 | 36 | 45
 J    | 10 | 20 | 30 | 40 | 50
(10 rows)

Xem hướng dẫn sử dụng psqlhttps://wiki.postgresql.org/wiki/Crosstabview để biết thêm.


1
Điều này thực sự rất tuyệt.
Evan Carroll

1
Cách giải quyết thanh lịch nhất.
Erwin Brandstetter

1

Đây không phải là một giải pháp dứt khoát

Đây là cách tiếp cận tốt nhất của tôi cho đến bây giờ. Vẫn cần chuyển đổi mảng cuối cùng thành cột.

Đầu tiên tôi đã có sản phẩm Cartesian của cả hai bảng:

select foo.name xname, bar.name yname, (foo.x * bar.x)::text as val,
       ((row_number() over ()) - 1) / (select count(*)::integer from foo) as row
 from bar
     cross join foo
 order by bar.name, foo.name

Nhưng, tôi đã thêm một số hàng chỉ để xác định mỗi hàng của bảng đầu tiên.

((row_number() over ()) - 1) / (select count(*)::integer from foo)

Sau đó, tôi đã đưa ra kết quả ở định dạng này:

[Row name] [Array of values]


select col_name, values
from
(
select '' as col_name, array_agg(name) as values from foo
UNION
select fy.name as col_name,
    (select array_agg(t.val) as values
    from  
        (select foo.name xname, bar.name yname, (foo.x * bar.x)::text as val,
              ((row_number() over ()) - 1) / (select count(*)::integer from foo) as row
        from bar
           cross join foo
        order by bar.name, foo.name) t
    where t.row = fy.row)
from
    (select name, (row_number() over(order by name)) - 1 as row from bar) fy
) a
order by col_name;

+---+---------------------+
|   |      ABCDEFGHIJ     |
+---+---------------------+
| I |     12345678910     |
+---+---------------------+
| J |   2468101214161820  |
+---+---------------------+
| K |  36912151821242730  |
+---+---------------------+
| L |  481216202428323640 |
+---+---------------------+
| M | 5101520253035404550 |
+---+---------------------+ 

Chuyển đổi nó thành chuỗi giới hạn bởi hôn mê:

select col_name, values
from
(
select '' as col_name, array_to_string(array_agg(name),',') as values from foo
UNION
select fy.name as col_name,
    (select array_to_string(array_agg(t.val),',') as values
    from  
        (select foo.name xname, bar.name yname, (foo.x * bar.x)::text as val,
              ((row_number() over ()) - 1) / (select count(*)::integer from foo) as row
        from bar
           cross join foo
        order by bar.name, foo.name) t
    where t.row = fy.row)
from
    (select name, (row_number() over(order by name)) - 1 as row from bar) fy
) a
order by col_name;


+---+------------------------------+
|   | A,B,C,D,E,F,G,H,I,J          |
+---+------------------------------+
| I | 1,2,3,4,5,6,7,8,9,10         |
+---+------------------------------+
| J | 2,4,6,8,10,12,14,16,18,20    |
+---+------------------------------+
| K | 3,6,9,12,15,18,21,24,27,30   |
+---+------------------------------+
| L | 4,8,12,16,20,24,28,32,36,40  |
+---+------------------------------+
| M | 5,10,15,20,25,30,35,40,45,50 |
+---+------------------------------+

(Chỉ để dùng thử sau: http://rextester.com/NBCYXA2183 )


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.