如何在结果表定义未知的情况下生成透视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(

作为参考,使用上面的例子用的名字,这是更多的东西像什么tablefunccrosstab()欲望。

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. 然后,交叉联接表示未定义尺寸的多维数据集(由于上述原因),
  3. 表格中有类别名称(交叉表用语)

在没有“列定义列表”来生成这种表示形式的PostgreSQL中,我们能做的最好的事情是什么?


1
JSON结果会是一个好方法吗?阵列会是一个很好的方法吗?这样,“输出表”的定义将是已知的(并且是固定的)。您将灵活性放在JSON或ARRAY中。我想这将取决于以后用于处理信息的许多工具。
joanolo

如果可能的话,我希望它像上面一样。
埃文·卡罗尔

Answers:


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个参数,因为根据定义,该参数不会丢失任何值。看到:

(您可以通过更换固定问题的交叉表查询foobar在以后的编辑,这也修复了查询,但一直与名工作foo。)

未知的返回类型,动态SQL

列名和类型不能是动态的。SQL要求在调用时知道结果列的数量,名称和类型。通过显式声明或从系统目录中的信息中进行更改(这就是SELECT * FROM tblPostgres会查找已注册的表定义的情况。)

您希望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:不难,但根本不可能。您可以从生成的具有已知返回类型的SQL创建MV。但是您不能拥有返回类型未知的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
这真是太酷了。
埃文·卡罗尔

1
最优雅的解决方法。
Erwin Brandstetter,2016年

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

将其转换为以逗号分隔的字符串:

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


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.