我有一张看起来像这样的表:
id feh bar 1 10 A 2 20 A 3 3 B 4 4 B 5 5 C 6 6 D 7 7 D 8 8 D
我希望它看起来像这样:
bar val1 val2 val3 A 10 20 B 3 4 C 5 D 6 7 8
我有这样的查询:
SELECT bar, MAX(CASE WHEN abc."row" = 1 THEN feh ELSE NULL END) AS "val1", MAX(CASE WHEN abc."row" = 2 THEN feh ELSE NULL END) AS "val2", MAX(CASE WHEN abc."row" = 3 THEN feh ELSE NULL END) AS "val3" FROM ( SELECT bar, feh, row_number() OVER (partition by bar) as row FROM "Foo" ) abc GROUP BY bar
这是一种非常灵活的方法,如果要创建大量新列,则它会变得笨拙。我想知道是否CASE可以更好地使此查询更具动态性?另外,我很想看到其他方法来做到这一点。
CASE
如果您尚未安装附加模块tablefunc,请为每个数据库运行一次此命令:
CREATE EXTENSION tablefunc;
适用于您的案例的非常基本的交叉表解决方案:
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?
这里的特殊困难在于,基表中没有类别( cat)。对于基本的1 参数形式,我们可以只提供一个带有虚拟值作为类别的虚拟列。无论如何都会忽略该值。
cat
这是一个罕见的情况下,其中第二个参数为crosstab()的功能并不需要,因为所有的NULL值只出现在这一问题的界定,晃来晃去的列到右边。并且顺序可以由值确定。
crosstab()
NULL
如果我们有一个实际的类别与名称确定结果值的顺序列,我们需要的2参数形式的crosstab()。这里我借助窗函数合成了一个类别列row_number(),以crosstab():
row_number()
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?
其余的几乎都是普通的。在这些密切相关的答案中找到更多解释和链接。
这就是您应该提供一个测试用例的方式:
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');
正如@Clodoaldo 评论的那样,还不是很有活力。使用 plpgsql 很难实现动态返回类型。但是有一些方法可以解决 -有一些限制。
所以为了不进一步复杂化其余部分,我用一个*更简单的*测试用例来演示:
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);
称呼:
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);
返回:
row_name | val1 | val2 | val3 ----------+------+------+------ A | 10 | 20 | B | 3 | 4 | C | 5 | | D | 6 | 7 | 8
tablefunc
tablefunc 模块为通用crosstab()调用提供了一个简单的基础结构,而无需提供列定义列表。编写的许多函数 C(通常非常快):
C
crosstabN()
crosstab1()-crosstab4()是预先定义的。一个小问题:他们需要并返回所有text. 所以我们需要投射我们的integer价值观。但它简化了调用:
crosstab1()
crosstab4()
text
integer
SELECT * FROM crosstab4('SELECT row_name, attrib, val::text -- cast! FROM tbl ORDER BY 1,2')
结果:
row_name | category_1 | category_2 | category_3 | category_4 ----------+------------+------------+------------+------------ A | 10 | 20 | | B | 3 | 4 | | C | 5 | | | D | 6 | 7 | 8 |
对于更多列**或其他数据类型*,我们创建自己的复合类型和函数*(一次)。 类型:
CREATE TYPE tablefunc_crosstab_int_5 AS ( row_name text, val1 int, val2 int, val3 int, val4 int, val5 int);
功能:
CREATE OR REPLACE FUNCTION crosstab_int_5(text) RETURNS SETOF tablefunc_crosstab_int_5 AS '$libdir/tablefunc', 'crosstab' LANGUAGE c STABLE STRICT;
SELECT * FROM crosstab_int_5('SELECT row_name, attrib, val -- no cast! FROM tbl ORDER BY 1,2');
row_name | val1 | val2 | val3 | val4 | val5 ----------+------+------+------+------+------ A | 10 | 20 | | | B | 3 | 4 | | | C | 5 | | | | D | 6 | 7 | 8 | |
这超出了tablefunc模块所涵盖的范围。 为了使返回类型动态化,我使用多态类型和此相关答案中详述的技术:
1-参数形式:
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;
使用此变体重载 2 参数形式:
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:为每个用户定义的复合类型定义了一个行类型,以便在系统目录中列出属性(列)pg_attribute。获得它的快速通道:将注册的类型 ( regtype) 转换为text并将其转换text为regclass。
pg_typeof(_rowtype)::text::regclass
pg_attribute
regtype
regclass
您需要定义要使用的每个返回类型:
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); ...
对于临时调用,您还可以创建一个临时表以达到相同(临时)效果:
CREATE TEMP TABLE temp_xtype7 AS ( row_name text, x1 int, x2 int, x3 int, x4 int, x5 int, x6 int, x7 int);
或者使用现有表、视图或物化视图的类型(如果可用)。
使用上述行类型:
1-参数形式(无缺失值):
SELECT * FROM crosstab_n( 'SELECT row_name, attrib, val FROM tbl ORDER BY 1,2' , NULL::tablefunc_crosstab_int_3);
2 参数形式(某些值可能会丢失):
SELECT * FROM crosstab_n( 'SELECT row_name, attrib, val FROM tbl ORDER BY 1' , $$VALUES ('val1'), ('val2'), ('val3')$$ , NULL::tablefunc_crosstab_int_3);
这一个函数适用于所有返回类型,而模块提供的框架需要为每个返回一个单独的函数。 如果您已按照上面演示的顺序命名您的类型,则只需替换粗体数字。要在基表中查找最大类别数:crosstab*N*()``tablefunc
crosstab*N*()``tablefunc
SELECT max(count(*)) OVER () FROM tbl -- returns 3 GROUP BY row_name LIMIT 1;
如果您想要单独的列,这将是动态的。像@Clocoaldo 演示的数组或简单的文本表示或包装在文档类型中的结果,例如json或hstore可以动态地用于任意数量的类别。
json
hstore
免责声明:将 用户输入转换为代码时总是存在潜在危险。确保这不能用于 SQL 注入。不要接受来自不受信任用户的输入(直接)。
SELECT * FROM crosstab_n('SELECT bar, 1, feh FROM tbl_org ORDER BY 1,2' , NULL::tablefunc_crosstab_int_3);