crosstab
等) #tablefunc
模块包含各种返回表的函数(即多个行)。这些函数本身很有用,并且作为如何编写返回多行的 C 函数的示例。
此模块被认为是“受信任的”,也就是说,非超级用户可以在当前数据库上拥有CREATE
权限的情况下安装它。
表 F.31 总结了 tablefunc
模块提供的函数。
表 F.31. tablefunc
函数
normal_rand
#normal_rand(int numvals, float8 mean, float8 stddev) returns setof float8
normal_rand
生成一组正态分布的随机值(高斯分布)。
numvals
是要从函数返回的值的数量。mean
是值的正态分布的均值,stddev
是值的正态分布的标准差。
例如,此调用请求 1000 个值,均值为 5,标准差为 3
test=# SELECT * FROM normal_rand(1000, 5, 3); normal_rand ---------------------- 1.56556322244898 9.10040991424657 5.36957140345079 -0.369151492880995 0.283600703686639 . . . 4.82992125404908 9.71308014517282 2.49639286969028 (1000 rows)
crosstab(text)
#crosstab(text sql) crosstab(text sql, int N)
crosstab
函数用于生成“透视”显示,其中数据列在页面上而不是向下。例如,我们可能拥有如下数据:
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
值的连续输入行组生成一个输出行。它从左到右使用这些行的 value
字段填充输出 value
列。如果某个组中的行数少于输出 value
列的数量,则额外的输出列将填充为 null;如果行数更多,则会跳过额外的输入行。
在实践中,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
子句。
另请参阅 psql 中的 \crosstabview
命令,它提供了类似于 crosstab()
的功能。
crosstabN
(text)
#crosstabN
(text sql)
crosstab
函数是如何为通用 N
crosstab
函数设置自定义包装器的示例,因此您无需在调用 SELECT
查询中写出列名和类型。tablefunc
模块包含 crosstab2
、crosstab3
和 crosstab4
,其输出行类型定义为:
CREATE TYPE tablefunc_crosstab_N AS ( row_name TEXT, category_1 TEXT, category_2 TEXT, . . . category_N TEXT );
因此,当输入查询生成类型为 text
的 row_name
和 value
列,并且您想要 2、3 或 4 个输出值列时,可以直接使用这些函数。在所有其他方面,它们的行为与上面针对通用 crosstab
函数所述完全相同。
例如,上一节中给出的示例也可以按以下方式工作:
SELECT * FROM crosstab3( 'select rowid, attribute, value from ct where attribute = ''att2'' or attribute = ''att3'' order by 1,2');
这些函数主要用于说明目的。您可以根据底层的 crosstab()
函数创建自己的返回类型和函数。有两种方法可以做到这一点:
创建一个复合类型来描述所需的输出列,类似于contrib/tablefunc/tablefunc--1.0.sql
中的示例。然后定义一个唯一的函数名称,接受一个text
类型的参数并返回setof your_type_name
,但链接到相同的底层crosstab
C 函数。例如,如果您的源数据生成的行名称是text
类型,值是float8
类型,并且您想要 5 个值列
CREATE TYPE my_crosstab_float8_5_cols AS ( my_row_name text, my_category_1 float8, my_category_2 float8, my_category_3 float8, my_category_4 float8, my_category_5 float8 ); CREATE OR REPLACE FUNCTION crosstab_float8_5_cols(text) RETURNS setof my_crosstab_float8_5_cols AS '$libdir/tablefunc','crosstab' LANGUAGE C STABLE STRICT;
使用OUT
参数隐式定义返回类型。同一个示例也可以这样完成
CREATE OR REPLACE FUNCTION crosstab_float8_5_cols( IN text, OUT my_row_name text, OUT my_category_1 float8, OUT my_category_2 float8, OUT my_category_3 float8, OUT my_category_4 float8, OUT my_category_5 float8) RETURNS setof record AS '$libdir/tablefunc','crosstab' LANGUAGE C STABLE STRICT;
crosstab(text, text)
#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; cat ------- cat1 cat2 cat3 cat4
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
查询结果的最后一列的类型,并且其数量必须与category_sql
查询结果中的行数完全相同。
crosstab
函数为具有相同row_name
值的每个连续的输入行组生成一个输出行。输出row_name
列以及任何““extra””列都从该组的第一行复制。输出value
列使用具有匹配category
值的行的value
字段填充。如果行的category
与category_sql
查询的任何输出都不匹配,则其value
将被忽略。其匹配类别在该组的任何输入行中都不存在的输出列将填充为null。
在实践中,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' ) AS ( 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
。
connectby
#connectby(text relname, text keyid_fld, text parent_keyid_fld [, text orderby_fld ], text start_with, int max_depth [, text branch_delim ])
connectby
函数生成存储在表中的层次数据的显示。该表必须具有一个唯一标识行的键字段,以及一个引用每一行父级(如果有)的父键字段。connectby
可以显示从任何行开始的子树。
表 F.32解释了参数。
表 F.32. connectby
参数
参数 | 描述 |
---|---|
relname |
源关系的名称 |
keyid_fld |
键字段的名称 |
parent_keyid_fld |
父键字段的名称 |
orderby_fld |
用于对同级排序的字段的名称(可选) |
start_with |
要开始的行的键值 |
max_depth |
要下降到的最大深度,或零表示无限深度 |
branch_delim |
在分支输出中使用分隔符分隔键的字符串(可选) |
键和父键字段可以是任何数据类型,但它们必须是相同的类型。请注意,start_with
值必须作为文本字符串输入,而不管键字段的类型是什么。
connectby
函数声明为返回setof record
,因此必须在调用SELECT
语句的FROM
子句中定义输出列的实际名称和类型,例如
SELECT * FROM connectby('connectby_tree', 'keyid', 'parent_keyid', 'pos', 'row2', 0, '~') AS t(keyid text, parent_keyid text, level int, branch text, pos int);
前两列用于当前行的键及其父行的键;它们必须与表的键字段的类型匹配。第三列是树中的深度,并且必须是integer
类型。如果给出了branch_delim
参数,则下一列是分支显示,并且必须是text
类型。最后,如果给出了orderby_fld
参数,则最后一列是序列号,并且必须是integer
类型。
““branch””输出列显示到达当前行所采用的键路径。键由指定的branch_delim
字符串分隔。如果不需要分支显示,则省略输出列列表中的branch_delim
参数和分支列。
如果同一父级的同级排序很重要,请包含orderby_fld
参数以指定要按哪个字段对同级排序。此字段可以是任何可排序的数据类型。如果且仅当指定了orderby_fld
时,输出列列表必须包含一个最终的整数序列号列。
表示表和字段名称的参数将原样复制到connectby
在内部生成的SQL查询中。因此,如果名称是混合大小写或包含特殊字符,请包含双引号。您可能还需要使用模式限定表名。
在大型表中,除非父键字段上有索引,否则性能会很差。
重要的是,branch_delim
字符串不应出现在任何键值中,否则connectby
可能会错误地报告无限递归错误。请注意,如果未提供branch_delim
,则默认值为~
用于递归检测目的。
这是一个示例
CREATE TABLE connectby_tree(keyid text, parent_keyid text, pos int); INSERT INTO connectby_tree VALUES('row1',NULL, 0); INSERT INTO connectby_tree VALUES('row2','row1', 0); INSERT INTO connectby_tree VALUES('row3','row1', 0); INSERT INTO connectby_tree VALUES('row4','row2', 1); INSERT INTO connectby_tree VALUES('row5','row2', 0); INSERT INTO connectby_tree VALUES('row6','row4', 0); INSERT INTO connectby_tree VALUES('row7','row3', 0); INSERT INTO connectby_tree VALUES('row8','row6', 0); INSERT INTO connectby_tree VALUES('row9','row5', 0); -- with branch, without orderby_fld (order of results is not guaranteed) SELECT * FROM connectby('connectby_tree', 'keyid', 'parent_keyid', 'row2', 0, '~') AS t(keyid text, parent_keyid text, level int, branch text); keyid | parent_keyid | level | branch -------+--------------+-------+--------------------- row2 | | 0 | row2 row4 | row2 | 1 | row2~row4 row6 | row4 | 2 | row2~row4~row6 row8 | row6 | 3 | row2~row4~row6~row8 row5 | row2 | 1 | row2~row5 row9 | row5 | 2 | row2~row5~row9 (6 rows) -- without branch, without orderby_fld (order of results is not guaranteed) SELECT * FROM connectby('connectby_tree', 'keyid', 'parent_keyid', 'row2', 0) AS t(keyid text, parent_keyid text, level int); keyid | parent_keyid | level -------+--------------+------- row2 | | 0 row4 | row2 | 1 row6 | row4 | 2 row8 | row6 | 3 row5 | row2 | 1 row9 | row5 | 2 (6 rows) -- with branch, with orderby_fld (notice that row5 comes before row4) SELECT * FROM connectby('connectby_tree', 'keyid', 'parent_keyid', 'pos', 'row2', 0, '~') AS t(keyid text, parent_keyid text, level int, branch text, pos int); keyid | parent_keyid | level | branch | pos -------+--------------+-------+---------------------+----- row2 | | 0 | row2 | 1 row5 | row2 | 1 | row2~row5 | 2 row9 | row5 | 2 | row2~row5~row9 | 3 row4 | row2 | 1 | row2~row4 | 4 row6 | row4 | 2 | row2~row4~row6 | 5 row8 | row6 | 3 | row2~row4~row6~row8 | 6 (6 rows) -- without branch, with orderby_fld (notice that row5 comes before row4) SELECT * FROM connectby('connectby_tree', 'keyid', 'parent_keyid', 'pos', 'row2', 0) AS t(keyid text, parent_keyid text, level int, pos int); keyid | parent_keyid | level | pos -------+--------------+-------+----- row2 | | 0 | 1 row5 | row2 | 1 | 2 row9 | row5 | 2 | 3 row4 | row2 | 1 | 4 row6 | row4 | 2 | 5 row8 | row6 | 3 | 6 (6 rows)
Joe Conway
如果您在文档中发现任何不正确的内容,与您对特定功能的体验不符,或者需要进一步澄清,请使用此表单报告文档问题。