如果您尚未安装附加模块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?
其余的几乎都是普通的。在这些密切相关的答案中找到更多解释和链接。
基础知识:
如果您不熟悉该crosstab()
功能,请先阅读此内容!
先进的:
正确的测试设置
这就是你应该如何提供一个测试用例开始的方式:
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_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
。
一次创建复合类型:
您需要为要使用的每种返回类型定义一次:
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);
这个函数适用于所有返回类型,而模块提供的框架需要为每个类型提供单独的函数。
如果您按照上面演示的顺序命名了您的类型,则只需替换粗体数字。要查找基表中的最大类别数:crosstabN()
tablefunc
SELECT max(count(*)) OVER () FROM tbl -- returns 3
GROUP BY row_name
LIMIT 1;
如果您想要单独的列,那将是动态的。@Clocoaldo演示的数组或简单的文本表示形式或包装在文档类型中的结果,例如json
orhstore
可以动态地用于任意数量的类别。
免责声明:
将用户输入转换为代码时总是存在潜在危险。确保这不能用于 SQL 注入。不要(直接)接受来自不受信任用户的输入。
要求原始问题:
SELECT * FROM crosstab_n('SELECT bar, 1, feh FROM tbl_org ORDER BY 1,2'
, NULL::tablefunc_crosstab_int_3);