如果您尚未安装附加模块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 参数形式,我们可以只提供一个带有虚拟值作为类别的虚拟列。无论如何都会忽略该值。
这是一个罕见的情况下,其中第二个参数为
crosstab()的功能并不需要,因为所有的
NULL值只出现在这一问题的界定,晃来晃去的列到右边。并且顺序可以由值确定。
如果我们有一个实际的类别与名称确定结果值的顺序列,我们需要的2参数形式的
crosstab()。这里我借助窗函数合成了一个类别列
row_number(),以
crosstab():
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(通常非常快):
crosstabN()
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 |
自定义crosstab()
功能
对于更多列**或其他数据类型*,我们创建自己的复合类型和函数*(一次)。
类型:
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_5AS '$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。
一次创建复合类型:
您需要定义要使用的每个返回类型:
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
SELECt max(count(*)) OVER () FROM tbl -- returns 3GROUP BY row_nameLIMIT 1;
如果您想要单独的列,这将是动态的。像@Clocoaldo 演示的数组或简单的文本表示或包装在文档类型中的结果,例如
json或
hstore可以动态地用于任意数量的类别。
免责声明:将
用户输入转换为代码时总是存在潜在危险。确保这不能用于 SQL 注入。不要接受来自不受信任用户的输入(直接)。
原始问题:
SELECt * FROM crosstab_n('SELECt bar, 1, feh FROM tbl_org ORDER BY 1,2' , NULL::tablefunc_crosstab_int_3);


