Sonuçta ortaya çıkan tablo tanımının bilinmediği, döndürülmüş bir CROSS JOIN'i nasıl oluştururum?


18

Ad ve değer içeren tanımsız satır sayısına sahip iki tablo göz önüne alındığında CROSS JOIN, bir işlevin değerlerinin üzerinde bir özetini nasıl görüntüleyeceğim .

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

Örneğin, bu işlev çarpma olsaydı, aşağıdaki gibi bir (çarpma) tablosu nasıl oluştururum,

1..12 ortak çarpım tablosu

Bu (arg1,arg2,result)satırların tümü aşağıdakilerle oluşturulabilir

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

Yani bu sadece bir sunum meselesi, bunun da özel bir adla çalışmasını istiyorum - sadece CASTmetinde düzenlenen argüman değil, tabloda ayarlanan bir ad ,

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

Bence bu dinamik bir dönüş tipi yapabilen bir CROSSTAB ile kolayca yapılabilir.

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

Ama, olmadan **MAGIC**, anladım

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

Başvuru için, adları ile yukarıdaki örneklerle bu daha fazla ne gibi bir şeydir tablefunc'ın crosstab()istek.

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

Ancak, şimdi barörneğimizde tablonun içeriği ve boyutu hakkında varsayımlar yapmaya geri döndük . Yani,

  1. Tablolar tanımlanmamış uzunluktadır,
  2. Daha sonra çapraz birleştirme, tanımlanmamış boyutta bir küpü temsil eder (yukarıdaki nedeniyle),
  3. Katagori adları (sekmeler arası parlance) tabloda

Bu tür bir sunum oluşturmak için "sütun tanımlama listesi" olmadan PostgreSQL'de yapabileceğimiz en iyi şey nedir?


1
JSON sonuçları iyi bir yaklaşım olabilir mi? Bir ARRAY iyi bir yaklaşım olabilir mi? Bu şekilde, "çıktı tablosu" nun tanımı zaten bilinir (ve sabitlenir). Esnekliği JSON veya ARRAY içine koydunuz. Sanırım daha sonra bilgileri işlemek için kullanılan araçların çoğuna bağlı olacaktır.
joanolo

Mümkünse, yukarıdaki gibi olmasını tercih ederim.
Evan Carroll

Yanıtlar:


12

Basit durum, statik SQL

Dinamik olmayan ile çözüm crosstab()basit durum için:

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

Ortaya çıkan sütunları sipariş ediyorum foo.name, değil foo.x. Her ikisi de paralel olarak sıralanır, ancak bu sadece basit kurulumdur. Davanız için doğru sıralama düzenini seçin. İkinci sütunun gerçek değeri bu sorguda önemsizdir (1-parametre biçimi crosstab()).

crosstab()2 parametreye bile ihtiyacımız yok çünkü tanım gereği eksik değer yok. Görmek:

(Sen değiştirerek, söz konusu çapraz sorgu sabit fooolan barbir sonraki düzenlemede. Bu aynı zamanda sorgu onarır, ancak gelen isimlerle çalışmaya devam etmektedir foo.)

Bilinmeyen dönüş türü, dinamik SQL

Sütun adları ve türleri dinamik olamaz. SQL, çağrı sırasında ortaya çıkan sütunların sayısını, adlarını ve türlerini bilmeyi ister. Açık bir beyanla veya sistem kataloglarındaki bilgilerden ( SELECT * FROM tblŞununla olur : Postgres kayıtlı tablo tanımını arar.)

Postgres öğelerinin sonuç sütunlarını bir kullanıcı tablosundaki verilerden türetmesini istiyorsunuz . Olmayacak.

Öyle ya da böyle , sunucuya iki gidiş-dönüş seyahate ihtiyacınız var . Ya bir imleç oluşturursunuz ve daha sonra üzerinde ilerlersiniz. Ya da bir geçici tablo oluşturun ve sonra onu seçin. Veya bir türü kaydedip çağrıda kullanırsınız.

Veya sorguyu bir adımda oluşturup bir sonraki adımda yürütebilirsiniz:

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;

Bu, yukarıdaki sorguyu dinamik olarak oluşturur. Bir sonraki adımda yürütün.

Ben $$iç içe tırnak basit tutmak için dolar-tırnak ( ) kullanıyorum . Görmek:

quote_ident() yasa dışı sütun adlarından kaçmak (ve muhtemelen SQL enjeksiyonuna karşı savunmak) gereklidir.

İlişkili:


"Bilinmeyen dönüş türü, dinamik SQL" olarak adlandırdığınız sorguyu yürütmenin aslında başka bir sorguyu temsil eden bir dize döndürdüğünü ve sonra "sonraki adımda yürüt" dediğinizi fark ettim. Bu, örneğin bundan somut bir bakış açısı oluşturmanın zor olacağı anlamına mı geliyor?
Colin D

@ColinD: Zor değil, ama imkansız. Bilinen dönüş türüyle oluşturulan SQL'den bir MV oluşturabilirsiniz. Ancak, dönüş türü bilinmeyen bir MV'niz olamaz.
Erwin Brandstetter

11

Bu tür bir sunum oluşturmak için "sütun tanımlama listesi" olmadan PostgreSQL'de yapabileceğimiz en iyi şey nedir?

Bunu bir sunu sorunu olarak çerçevelerseniz, bir sorgu sonrası sunu özelliği olarak düşünebilirsiniz.

psql(9.6) ' nın daha yeni sürümleri, \crosstabviewSQL desteği olmadan çapraz tablo gösterimi sonucunu gösterir (SQL @ Erwin'in cevabında belirtildiği gibi bunu doğrudan üretemediğinden: SQL, çağrı zamanında ortaya çıkan sütunların sayısını, adlarını ve türlerini bilmek ister )

Örneğin, ilk sorgunuz şunları sağlar:

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 sütun adlarına sahip ikinci örnek şunları verir:

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)

Daha fazla bilgi için psql kılavuzuna ve https://wiki.postgresql.org/wiki/Crosstabview adresine bakın .


1
Bu gerçekten çok havalı.
Evan Carroll

1
En zarif çözüm.
Erwin Brandstetter

1

Bu kesin bir çözüm değil

Bu şimdiye kadarki en iyi yaklaşımım. Yine de son diziyi sütunlara dönüştürmeniz gerekiyor.

İlk olarak her iki tablonun Kartezyen ürününü aldım:

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

Ancak, yalnızca ilk tablonun her satırını tanımlamak için bir satır numarası ekledim.

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

Sonra sonucu bu biçimde aldım:

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

Komalarla sınırlandırılmış dizeye dönüştürülüyor:

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

(Daha sonra denemek için: http://rextester.com/NBCYXA2183 )


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.