ฉันจะสร้าง CROSS JOIN ที่หมุนได้ได้อย่างไรโดยที่ไม่ทราบนิยามของตารางที่ได้


18

เมื่อกำหนดสองตารางที่มีจำนวนแถวที่ไม่ได้กำหนดด้วยชื่อและค่าฉันจะแสดงCROSS JOINฟังก์ชันที่มีเครื่องหมายทับค่าของพวกมันได้อย่างไร

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);

ตัวอย่างเช่นถ้าฟังก์ชันนั้นเป็นการคูณฉันจะสร้างตาราง (การคูณ) เช่นเดียวกับด้านล่างได้อย่างไร

ตารางการคูณสามัญ 1..12

(arg1,arg2,result)แถวเหล่านั้นทั้งหมดสามารถสร้างขึ้นได้ด้วย

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

ดังนั้นนี่เป็นเพียงคำถามของงานนำเสนอฉันต้องการสิ่งนี้เพื่อทำงานกับชื่อที่กำหนดเอง - ชื่อที่ไม่ได้เป็นเพียงอาร์กิวเมนต์CASTที่เขียนเป็นข้อความ แต่ตั้งอยู่ในตาราง

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);

ฉันคิดว่าสิ่งนี้จะทำได้อย่างง่ายดายด้วย CROSSTAB ที่มีความสามารถในการส่งคืนแบบไดนามิก

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**

แต่ถ้าไม่มี**MAGIC**ฉันก็จะได้

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

สำหรับการอ้างอิงโดยใช้ตัวอย่างข้างต้นที่มีชื่อนี้เป็นสิ่งที่มากขึ้นเช่นสิ่งที่tablefunc's crosstab()ความต้องการ

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);

แต่ตอนนี้เรากลับไปตั้งสมมติฐานเกี่ยวกับเนื้อหาและขนาดของbarตารางในตัวอย่างของเรา ดังนั้นถ้า

  1. ตารางมีความยาวไม่ได้กำหนด
  2. จากนั้น cross-join จะแสดงถึงมิติที่ไม่ได้กำหนด (เนื่องจากด้านบน)
  3. catagory-names (parlance cross-tab) อยู่ในตาราง

อะไรคือสิ่งที่ดีที่สุดที่เราสามารถทำได้ใน PostgreSQL โดยไม่มี "รายการนิยามคอลัมน์" เพื่อสร้างงานนำเสนอแบบนั้น


1
ผลลัพธ์ของ JSON จะเป็นแนวทางที่ดีหรือไม่ ARRAY จะเป็น aprpoach ที่ดีหรือไม่? ด้วยวิธีนี้คำจำกัดความของ "ตารางผลลัพธ์" จะเป็นที่รู้จักอยู่แล้ว (และคงที่) คุณใส่ความยืดหยุ่นภายใน JSON หรือ ARRAY ฉันเดาว่ามันจะขึ้นอยู่กับเครื่องมือมากมายที่ใช้ในการประมวลผลข้อมูลในภายหลัง
joanolo

ฉันอยากให้มันเป็นแบบนี้ถ้าเป็นไปได้
Evan Carroll

คำตอบ:


12

กรณีง่าย SQL แบบคงที่

การแก้ปัญหาที่ไม่ไดนามิกด้วยcrosstab()สำหรับกรณีที่เรียบง่าย:

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);

ผมสั่งที่เกิดคอลัมน์โดยไม่foo.name foo.xทั้งคู่เกิดการเรียงลำดับแบบขนาน แต่เป็นเพียงการตั้งค่าอย่างง่าย เลือกลำดับการจัดเรียงที่ถูกต้องสำหรับกรณีของคุณ มูลค่าที่แท้จริงของคอลัมน์ที่สองนั้นไม่เกี่ยวข้องในแบบสอบถามนี้ (รูปแบบ 1 พารามิเตอร์ของcrosstab())

เราไม่ต้องการแม้แต่crosstab()พารามิเตอร์ 2 ตัวเพราะไม่มีค่าที่ขาดหายไปตามคำจำกัดความ ดู:

(คุณแก้ไขแบบสอบถามแท็บไขว้ในคำถามโดยแทนที่fooด้วยbarในการแก้ไขในภายหลังซึ่งจะช่วยแก้ไขแบบสอบถาม แต่ทำงานกับชื่อจากfoo )

ชนิดส่งคืนที่ไม่รู้จัก SQL แบบไดนามิก

ชื่อคอลัมน์และประเภทไม่สามารถเป็นแบบไดนามิกได้ SQL ต้องการทราบจำนวนชื่อและชนิดของคอลัมน์ผลลัพธ์ ณ เวลาที่เรียกใช้ ไม่ว่าจะโดยการประกาศอย่างชัดเจนหรือจากข้อมูลในแคตตาล็อกระบบ (นั่นคือสิ่งที่เกิดขึ้นกับSELECT * FROM tbl: Postgres ค้นหาคำนิยามตารางที่ลงทะเบียน)

คุณต้องการให้ Postgres หาคอลัมน์ผลลัพธ์จากข้อมูลในตารางผู้ใช้ จะไม่เกิดขึ้น

ไม่ทางใดก็ทางหนึ่งคุณต้องเดินทางไปกลับสองรอบไปยังเซิร์ฟเวอร์ ไม่ว่าคุณจะสร้างเคอร์เซอร์แล้วก็ลองดู หรือคุณสร้างตารางชั่วคราวแล้วเลือกจากมัน หรือคุณลงทะเบียนประเภทและใช้ในการโทร

หรือคุณเพียงแค่สร้างแบบสอบถามในขั้นตอนเดียวและดำเนินการในขั้นตอนต่อไป:

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;

สิ่งนี้สร้างแบบสอบถามด้านบนแบบไดนามิก ดำเนินการในขั้นตอนถัดไป

ฉันใช้เครื่องหมายคำพูดดอลลาร์ ( $$) เพื่อให้การจัดการคำพูดซ้อนง่าย ดู:

quote_ident() จำเป็นต่อการหลีกเลี่ยงชื่อคอลัมน์ที่ผิดกฎหมาย (และอาจป้องกันการฉีด SQL)

ที่เกี่ยวข้อง:


ฉันสังเกตเห็นว่าการดำเนินการแบบสอบถามที่คุณเรียกว่า "ไม่ทราบประเภทคืนแบบไดนามิก SQL" จริง ๆ แล้วเพียงแค่ส่งกลับสตริงที่แสดงถึงแบบสอบถามอื่นแล้วคุณพูดว่า "ดำเนินการในขั้นตอนต่อไป" นี่หมายถึงว่ามันจะเป็นเรื่องยากสำหรับตัวอย่างในการสร้างมุมมองที่เป็นรูปธรรมจากสิ่งนี้หรือไม่?
โคลิน D

@ColinD: ไม่ยาก แต่เป็นไปไม่ได้ธรรมดา คุณสามารถสร้าง MV จาก SQL ที่สร้างขึ้นด้วยชนิดส่งคืนที่รู้จัก แต่คุณไม่สามารถมี MV ที่มีประเภทผลตอบแทนที่ไม่รู้จัก
Erwin Brandstetter

11

อะไรคือสิ่งที่ดีที่สุดที่เราสามารถทำได้ใน PostgreSQL โดยไม่มี "รายการนิยามคอลัมน์" เพื่อสร้างงานนำเสนอแบบนั้น

หากคุณกำหนดให้สิ่งนี้เป็นปัญหาการนำเสนอคุณอาจพิจารณาคุณลักษณะการนำเสนอหลังการสืบค้น

เวอร์ชั่นใหม่กว่าของpsql(9.6) มาพร้อมกับ\crosstabviewแสดงผลในการอ้างอิงไขว้โดยไม่สนับสนุน SQL (เนื่องจาก SQL ไม่สามารถผลิตได้โดยตรงดังที่ได้กล่าวไว้ในคำตอบของ @ Erwin: SQL ต้องการทราบจำนวนชื่อและประเภทของคอลัมน์ผลลัพธ์ในเวลาโทร )

ตัวอย่างเช่นแบบสอบถามแรกของคุณให้:

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)

ตัวอย่างที่สองที่มีชื่อคอลัมน์ ASCII จะให้:

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)

ดูคู่มือ psqlและhttps://wiki.postgresql.org/wiki/Crosstabviewสำหรับข้อมูลเพิ่มเติม


1
นี่มันเจ๋งจริงๆ
Evan Carroll

1
วิธีแก้ปัญหาที่หรูหราที่สุด
Erwin Brandstetter

1

นี่ไม่ใช่ทางออกที่ชัดเจน

นี่เป็นวิธีที่ดีที่สุดของฉันจนถึงตอนนี้ ยังคงต้องแปลงอาร์เรย์สุดท้ายเป็นคอลัมน์

ก่อนอื่นฉันได้ผลิตภัณฑ์คาร์ทีเซียนของทั้งสองตาราง:

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

แต่ฉันได้เพิ่มหมายเลขแถวเพื่อระบุทุกแถวของตารางแรก

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

จากนั้นฉันก็ซื้อผลลัพธ์ในรูปแบบนี้:

[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 |
+---+---------------------+ 

การแปลงเป็นสตริงที่คั่นด้วย comas:

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 |
+---+------------------------------+

(เพื่อลองในภายหลัง: http://rextester.com/NBCYXA2183 )


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.