添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

F.36. tablefunc

tablefunc 模块包括多个返回表(也就是多行)的函数。这些函数都很有用,并且也可以作为如何编写返回多行的 C 函数的例子。

F.36.1. 所提供的函数

表 F-29 显示了 tablefunc 模块提供的函数。

F.36.1.1. 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)

F.36.1.2. crosstab(text)

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子句。

F.36.1.3. crosstab N (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;

F.36.1.4. 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;
  -------
   

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

F.36.1.5. 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-30 解释了参数。

键域和父亲键域可以是任意数据类型,但是它们必须是同一类型。 注意 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);
-- 带有分支,但没有 orderby_fld (不保证结果的顺序)
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)
-- 没有分支,也没有 orderby_fld (不保证结果的顺序)
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)
-- 有分支,有 orderby_fld (注意 row5 在 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)
-- 没有分支,有 orderby_fld (注意 row5 在 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)

F.36.2. 作者

Joe Conway

<