crosstab(text sql)
crosstab(text sql, int N)
crosstab
函数被用来产生
"pivot"
显示,在其中数据被横布在页面上而不是直接向下列举。例如,我们可能有这样的数据
row1 val11
row1 val12
row1 val13
row2 val21
row2 val22
row2 val23
而我们希望显示成这样
row1 val11 val12 val13 ...
row2 val21 val22 val23 ...
crosstab
函数会采用一个文本参数,该文本是一个 SQL 查询,它产生按照第一种方式格式化的原始数据,并且产生以第二种方式格式化的一个表。
sql参数是一个产生数据的源集合的 SQL 语句。这个语句必须返回一个row_name列、一个category列和一个value列。N是一个废弃参数,即使提供也会被忽略(之前这必须匹配输出值列的数目,但是现在这由调用查询决定了)。
例如,所提供的查询可能会产生这样的一个集合:
row_name cat value
----------+-------+-------
row1 cat1 val1
row1 cat2 val2
row1 cat3 val3
row1 cat4 val4
row2 cat1 val5
row2 cat2 val6
row2 cat3 val7
row2 cat4 val8
crosstab
函数被声明为返回setof record,
因此输出列的实际名称和类型必须定义在调用的SELECT语句的FROM子句中,例如:
SELECT * FROM crosstab('...') AS ct(row_name text, category_1 text, category_2 text);
这个例子产生这样一个集合:
<== value columns ==>
row_name category_1 category_2
----------+------------+------------
row1 val1 val2
row2 val5 val6
FROM子句必须把输出定义为一个row_name列
(具有 SQL 查询的第一个结果列的相同数据类型),其后跟随着 N 个value列
(都具有 SQL 查询的第三个结果列的相同数据类型)。你可以按照你的意愿设置任意多的输出值列。
而输出列的名称取决于你。
crosstab
函数为具有相同row_name值的
输入行的每一个连续分组产生一个输出行。它使用来自这些行的值域
从左至右填充输出的值列。如果一个分组中的行比输出值列少,
多余的输出列将被用空值填充。如果行更多,则多余的输入行会被跳过。
事实上,SQL 查询应该总是指定ORDER BY 1,2来保证输入行被正确地排序,
也就是说具有相同row_name的值会被放在一起并且在行内
被正确地排序。注意crosstab
本身并不关注查询结果的第二列,它放在那里
只是为了被排序,以便控制出现在页面上的第三列值的顺序。
这是一个完整的例子:
CREATE TABLE ct(id SERIAL, rowid TEXT, attribute TEXT, value TEXT);
INSERT INTO ct(rowid, attribute, value) VALUES('test1','att1','val1');
INSERT INTO ct(rowid, attribute, value) VALUES('test1','att2','val2');
INSERT INTO ct(rowid, attribute, value) VALUES('test1','att3','val3');
INSERT INTO ct(rowid, attribute, value) VALUES('test1','att4','val4');
INSERT INTO ct(rowid, attribute, value) VALUES('test2','att1','val5');
INSERT INTO ct(rowid, attribute, value) VALUES('test2','att2','val6');
INSERT INTO ct(rowid, attribute, value) VALUES('test2','att3','val7');
INSERT INTO ct(rowid, attribute, value) VALUES('test2','att4','val8');
SELECT *
FROM crosstab(
'select rowid, attribute, value
from ct
where attribute = ''att2'' or attribute = ''att3''
order by 1,2')
AS ct(row_name text, category_1 text, category_2 text, category_3 text);
row_name | category_1 | category_2 | category_3
----------+------------+------------+------------
test1 | val2 | val3 |
test2 | val6 | val7 |
(2 rows)
你可以避免总是要写出一个FROM子句来定义输出列,
方法是设置一个在其定义中硬编码所期望的输出行类型的自定义 crosstab 函数。
这会在下一节中描述。另一种可能性是在一个视图定义中嵌入所需的FROM子句。
crosstab(text source_sql, text category_sql)
crosstab
的单一参数形式的主要限制是它把一个组中的所有值都视作相似,
并且把每一个值插入到第一个可用的列中。如果你想要值列对应于特定的数据分类,并且
某些分组可能没有关于某些分类的数据,这样的形式就无法工作。
crosstab
的双参数形式
通过提供一个对应于输出列的显式分类列表来处理这种情况。
source_sql
是一个产生源数据集的 SQL 语句。这个语句必须返回一个
row_name
列、一个
category
列以及一个
value
列。
也可以有一个或者多个
"extra"
列。
row_name
列必须是第一个。
category
和
value
列必须是按照这个顺序的最后两个列。
row_name
和
category
之间的任何列都被视作
"extra"
。
对于具有相同
row_name
值的所有行,其
"extra"
列都应该相同。
例如,
source_sql
可能产生一组这样的东西:
SELECT row_name, extra_col, cat, value FROM foo ORDER BY 1;
row_name extra_col cat value
----------+------------+-----+---------
row1 extra1 cat1 val1
row1 extra1 cat2 val2
row1 extra1 cat4 val4
row2 extra2 cat1 val5
row2 extra2 cat2 val6
row2 extra2 cat3 val7
row2 extra2 cat4 val8
category_sql
是一个产生分类集合的 SQL 语句。这个语句必须只返回一列。
它必须产生至少一行,否则会生成一个错误。还有,它不能产生重复值,否则会生成一个错误。
category_sql
可能是这样的:
SELECT DISTINCT cat FROM foo ORDER BY 1;
-------
crosstab
函数被声明为返回setof record,这样输出列的实际名称和类型
就必须在调用的SELECT语句的FROM子句中被定义,例如:
SELECT * FROM crosstab('...', '...')
AS ct(row_name text, extra text, cat1 text, cat2 text, cat3 text, cat4 text);
这将产生这样的结果:
<== value columns ==>
row_name extra cat1 cat2 cat3 cat4
---------+-------+------+------+------+------
row1 extra1 val1 val2 val4
row2 extra2 val5 val6 val7 val8
FROM子句必须定义正确数量的输出列以及正确的数据类型。如果在source_sql
查询的结果中有N列,其中的前N-2 列必须匹配前N-2
个输出列。剩余的输出列必须具有source_sql查询结果的最后一列的类型,并且并且它们的数量
必须正好和source_sql查询结果中的行数相同。
crosstab
函数为具有相同row_name值的输入行形成的每一个连续分组
产生一个输出行。输出的row_name列外加任意一个"extra"列都是从分组的
第一行复制而来。输出的value列被使用具有匹配的category值的行中的
value域填充。如果一个行的category不匹配category_sql
查询的任何输出,它的value会被忽略。匹配的分类不出现于分组中任何输出行中的的
输出列会被用空值填充。
事实上,source_sql查询应该总是指定ORDER BY 1来保证
具有相同row_name的值会被放在一起。但是,一个分组内分类的顺序并不重要。
还有,确保category_sql查询的输出的顺序与指定的输出列顺序匹配是非常重要的。
这里有两个完整的例子:
create table sales(year int, month int, qty int);
insert into sales values(2007, 1, 1000);
insert into sales values(2007, 2, 1500);
insert into sales values(2007, 7, 500);
insert into sales values(2007, 11, 1500);
insert into sales values(2007, 12, 2000);
insert into sales values(2008, 1, 1000);
select * from crosstab(
'select year, month, qty from sales order by 1',
'select m from generate_series(1,12) m'
) as (
year int,
"Jan" int,
"Feb" int,
"Mar" int,
"Apr" int,
"May" int,
"Jun" int,
"Jul" int,
"Aug" int,
"Sep" int,
"Oct" int,
"Nov" int,
"Dec" int
year | Jan | Feb | Mar | Apr | May | Jun | Jul | Aug | Sep | Oct | Nov | Dec
------+------+------+-----+-----+-----+-----+-----+-----+-----+-----+------+------
2007 | 1000 | 1500 | | | | | 500 | | | | 1500 | 2000
2008 | 1000 | | | | | | | | | | |
(2 rows)
CREATE TABLE cth(rowid text, rowdt timestamp, attribute text, val text);
INSERT INTO cth VALUES('test1','01 March 2003','temperature','42');
INSERT INTO cth VALUES('test1','01 March 2003','test_result','PASS');
INSERT INTO cth VALUES('test1','01 March 2003','volts','2.6987');
INSERT INTO cth VALUES('test2','02 March 2003','temperature','53');
INSERT INTO cth VALUES('test2','02 March 2003','test_result','FAIL');
INSERT INTO cth VALUES('test2','02 March 2003','test_startdate','01 March 2003');
INSERT INTO cth VALUES('test2','02 March 2003','volts','3.1234');
SELECT * FROM crosstab
'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1',
'SELECT DISTINCT attribute FROM cth ORDER BY 1'
rowid text,
rowdt timestamp,
temperature int4,
test_result text,
test_startdate timestamp,
volts float8
rowid | rowdt | temperature | test_result | test_startdate | volts
-------+--------------------------+-------------+-------------+--------------------------+--------
test1 | Sat Mar 01 00:00:00 2003 | 42 | PASS | | 2.6987
test2 | Sun Mar 02 00:00:00 2003 | 53 | FAIL | Sat Mar 01 00:00:00 2003 | 3.1234
(2 rows)
你可以创建预定义的函数来避免在每个查询中都必须写出结果列的名称和类型。请参考前一节中的例子。
用于这种形式的crosstab
的底层 C 函数被命名为crosstab_hash。