Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/sql/81.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Sql 以案例和分组为轴心的动态替代方案_Sql_Postgresql_Pivot_Crosstab_Window Functions - Fatal编程技术网

Sql 以案例和分组为轴心的动态替代方案

Sql 以案例和分组为轴心的动态替代方案,sql,postgresql,pivot,crosstab,window-functions,Sql,Postgresql,Pivot,Crosstab,Window Functions,我有一张这样的桌子: id feh bar 1 10 A 2 20 A 3 3 B 4 4 B 5 5 C 6 6 D 7 7 D 8 8 D bar val1 val2 val3 A 10 20 B 3 4 C 5 D 6 7 8 我希望

我有一张这样的桌子:

id    feh    bar
1     10     A
2     20     A
3      3     B
4      4     B
5      5     C
6      6     D
7      7     D
8      8     D
bar  val1   val2   val3
A     10     20 
B      3      4 
C      5        
D      6      7     8
我希望它看起来像这样:

id    feh    bar
1     10     A
2     20     A
3      3     B
4      4     B
5      5     C
6      6     D
7      7     D
8      8     D
bar  val1   val2   val3
A     10     20 
B      3      4 
C      5        
D      6      7     8
我有这样一个查询:

SELECT bar, 
   MAX(CASE WHEN abc."row" = 1 THEN feh ELSE NULL END) AS "val1",
   MAX(CASE WHEN abc."row" = 2 THEN feh ELSE NULL END) AS "val2",
   MAX(CASE WHEN abc."row" = 3 THEN feh ELSE NULL END) AS "val3"
FROM
(
  SELECT bar, feh, row_number() OVER (partition by bar) as row
  FROM "Foo"
 ) abc
GROUP BY bar

这是一种非常灵活的方法,如果要创建大量新列,则会变得非常笨拙。我想知道是否可以改进
CASE
语句以使此查询更具动态性?另外,我希望看到其他方法来实现这一点。

在您的情况下,我想数组是不错的


如果尚未安装附加模块,请对每个数据库运行此命令一次:

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
值仅出现在右侧的悬挂列中。并且顺序可以由值决定

如果我们有一个实际的类别列,其名称决定了结果中值的顺序,那么我们需要
crosstab()
2参数形式。在这里,我借助window函数合成了一个category列,以
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');
动态交叉表? 还不是很有活力。使用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);
电话:

返回:

 row_name | val1 | val2 | val3
----------+------+------+------
 A        | 10   | 20   |
 B        |  3   |  4   |
 C        |  5   |      |
 D        |  6   |  7   |  8
tablefunc
模块的内置功能 tablefunc模块为泛型
crosstab()
调用提供了一个简单的基础结构,而不提供列定义列表。许多用
C
编写的函数(通常非常快):

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;
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;
使用此变量重载2参数表单:

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;
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
:每个用户定义的复合类型都定义了一个行类型,以便在系统目录中列出属性(列)。获取它的快车道:将注册类型(
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);

...
对于临时呼叫,您也可以创建一个临时表,效果相同(临时):

或者使用现有表、视图或物化视图(如果可用)的类型

呼叫 使用上述行类型:

1参数形式(无缺失值):

如果您想要单独的列,那么这将是动态的。数组(例如,一个简单的文本表示形式)或封装在文档类型(例如
json
hstore
中)中的结果可以动态地用于任意数量的类别

免责声明:
当用户输入被转换成代码时,总是有潜在的危险。确保这不能用于SQL注入。不接受来自不受信任用户的输入(直接)

要求提出原始问题:
很抱歉在过去返回,但是解决方案“动态交叉表”返回错误的结果表。因此,valN值错误地“向左对齐”,并且它们与列名不对应。当输入表的值中有“孔”时,例如,“C”有val1和val3,但没有val2。这将产生一个错误:val3值将在最终表格的val2列(即下一个自由列)中进行调整

CREATE TEMP TABLE tbl (row_name text, attrib text, val int); 
INSERT INTO tbl (row_name, attrib, val) VALUES ('C', 'val1', 5) ('C', 'val3', 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
 C      |   5|  7 |

为了返回右列中带有“孔”的正确单元格,交叉表查询需要在交叉表中进行第二次选择,类似于“交叉表”(“按1,2的顺序从tbl中选择行名称、属性、值”,“按1的顺序从tbl中选择不同的行名称”)”

尽管这是一个老问题,我想添加另一个解决方案,这是PostgreSQL最近的改进所带来的。此解决方案实现了从动态数据集返回结构化结果的相同目标,而根本不使用交叉表功能。换句话说,这是一个重新检查无意和隐含假设的好例子,这些假设阻止我们发现旧问题的新解决方案。

为了举例说明,您要求提供一种使用以下结构转置数据的方法:

id    feh    bar
1     10     A
2     20     A
3      3     B
4      4     B
5      5     C
6      6     D
7      7     D
8      8     D
转换为以下格式:

bar  val1   val2   val3
A     10     20 
B      3      4 
C      5        
D      6      7     8
传统的解决方案是一种创建动态交叉表查询的聪明(而且知识渊博)方法,在Erwin Brandstetter的回答中详细解释了这种方法

然而,如果您的特定用例足够灵活,可以接受稍微不同的结果格式,那么可以使用另一种解决方案漂亮地处理动态枢轴。这是我在这里学到的技巧

使用PostgreSQL的新函数以JSON对象的形式动态构造数据透视

我将使用Brandstetter先生的“更简单的测试用例”来说明:

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);
使用
jsonb_object_agg
函数,我们可以创建所需的数据透视结果集,其精妙之处如下:

SELECT
  row_name AS bar,
  json_object_agg(attrib, val) AS data
FROM tbl
GROUP BY row_name
ORDER BY row_name;
哪些产出:

 bar |                  data                  
-----+----------------------------------------
 A   | { "val1" : 10, "val2" : 20 }
 B   | { "val1" : 3, "val2" : 4 }
 C   | { "val1" : 5 }
 D   | { "val3" : 8, "val1" : 6, "val2" : 7 }
如您所见,此函数通过从
attrib
value
在JSON对象中创建键/值对来工作
SELECT * FROM crosstab_n(
   'SELECT row_name, attrib, val FROM tbl ORDER BY 1,2'
 , NULL::tablefunc_crosstab_int_3);
SELECT * FROM crosstab_n(
   'SELECT row_name, attrib, val FROM tbl ORDER BY 1'
 , $$VALUES ('val1'), ('val2'), ('val3')$$
 , NULL::tablefunc_crosstab_int_3);
SELECT max(count(*)) OVER () FROM tbl  -- returns 3
GROUP  BY row_name
LIMIT  1;
SELECT * FROM crosstab_n('SELECT bar, 1, feh FROM tbl_org ORDER BY 1,2'
                       , NULL::tablefunc_crosstab_int_3);
CREATE TEMP TABLE tbl (row_name text, attrib text, val int); 
INSERT INTO tbl (row_name, attrib, val) VALUES ('C', 'val1', 5) ('C', 'val3', 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
 C      |   5|  7 |
id    feh    bar
1     10     A
2     20     A
3      3     B
4      4     B
5      5     C
6      6     D
7      7     D
8      8     D
bar  val1   val2   val3
A     10     20 
B      3      4 
C      5        
D      6      7     8
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
  row_name AS bar,
  json_object_agg(attrib, val) AS data
FROM tbl
GROUP BY row_name
ORDER BY row_name;
 bar |                  data                  
-----+----------------------------------------
 A   | { "val1" : 10, "val2" : 20 }
 B   | { "val1" : 3, "val2" : 4 }
 C   | { "val1" : 5 }
 D   | { "val3" : 8, "val1" : 6, "val2" : 7 }
select
    row_name as bar,
    json_object_agg(attrib, val order by attrib) as data
from
    tbl
    right join
    (
        (select distinct row_name from tbl) a
        cross join
        (select distinct attrib from tbl) b
    ) c using (row_name, attrib)
group by row_name
order by row_name
;
 bar |                     data                     
-----+----------------------------------------------
 a   | { "val1" : 10, "val2" : 20, "val3" : null }
 b   | { "val1" : 3, "val2" : 4, "val3" : null }
 c   | { "val1" : 5, "val2" : null, "val3" : null }
 d   | { "val1" : 6, "val2" : 7, "val3" : 8 }
/** build a dataset **/
DROP TABLE IF EXISTS tmpT ;
CREATE TEMP TABLE tmpT AS
SELECT
 NULL::INT AS key
 ,NULL::INT AS ints
 ,NULL::VARCHAR(1) AS chars
 ,NULL::VARCHAR(3) AS unnest
LIMIT 0 ;

insert into tmpT (key, ints, chars, unnest)
values   (1 , 1   , 'o',  CHR( 130 - 10 ) )       
        ,(2 , 2   , 'n',  CHR( 130 - 11 ) )       
        ,(3 , 3   , 'm',            NULL  )       
        --,(4 , 4   , 'l',  CHR( 130 - 13 ) ) -- missing set       
        ,(5 , 5   , null, CHR( 130 - 14 ) )        
        ,(6 , null, 'j',  CHR( 130 - 15 ) )        
        ,(7 , 7   , null, CHR( 130 - 16 ) )         
        ,(8 , null, 'h',  CHR( 130 - 17 ) )        
        ,(9 , 9   , null, CHR( 130 - 18 ) )         
        ,(10, null, 'f' ,           NULL  )        
        ,(11, null, 'a',  CHR( 130 - 20 ) )        
        ,(12, 12  , null, CHR( 130 - 21 ) )         
 ; /** end of build a dataset **/

/** set up full set of pivotal column positions, to backfill any missing  **/
DROP TABLE IF EXISTS tGenSer ; 
CREATE TEMP TABLE tGenSer AS SELECT generate_series( 1, 1000 )::INT AS key ;
/* Pivot 10 columns */
SELECT *
FROM     /* name the columns*/
(    SELECT a a ,a b ,a c ,a d ,a e ,a f ,a g ,a h ,a i ,a j /*,a k ,a l ,a m ,a n ,a o ,a p ,a q ,a r ,a s ,a t*/ /* ,a u ,a v ,a w ,a x ,a y ,a z*/ FROM ( SELECT NULL::VARCHAR(3) AS a /**seed the typed columns **/) a  
    
    UNION /** union is just a helper, to assign names to unnamed columns **/
    
    /** 20 columns **/
    SELECT * FROM
    (
        /* enumerate columns, no name */
        SELECT t1.x[1 ] ,t1.x[2 ] ,t1.x[3 ] ,t1.x[4 ] ,t1.x[5 ] ,t1.x[6 ] ,t1.x[7 ] ,t1.x[8 ] ,t1.x[9 ] ,t1.x[10] 
        FROM ( SELECT  ARRAY( SELECT  a.ints::TEXT  AS v   
                        FROM tGenSer tg /**backfill missing keys**/ 
                        LEFT JOIN tmpT a ON tg.key = a.key ORDER BY tg.key 
                        ) AS x 
             ) t1
        
        UNION ALL
        
        SELECT t1.x[1 ] ,t1.x[2 ] ,t1.x[3 ] ,t1.x[4 ] ,t1.x[5 ] ,t1.x[6 ] ,t1.x[7 ] ,t1.x[8 ] ,t1.x[9 ] ,t1.x[10] 
        FROM ( SELECT  ARRAY( SELECT  a.chars::TEXT AS v 
                        FROM tGenSer tg /**backfill missing keys**/ 
                        LEFT JOIN tmpT a ON tg.key = a.key ORDER BY tg.key 
                        ) AS x 
            ) t1
        
        UNION ALL
        
        SELECT t1.x[1 ] ,t1.x[2 ] ,t1.x[3 ] ,t1.x[4 ] ,t1.x[5 ] ,t1.x[6 ] ,t1.x[7 ] ,t1.x[8 ] ,t1.x[9 ] ,t1.x[10] 
        FROM ( SELECT  ARRAY( SELECT  a.unnest      AS v 
                        FROM tGenSer tg /**backfill missing keys**/ 
                        LEFT JOIN tmpT a ON tg.key = a.key 
                        ORDER BY tg.key 
                        ) AS x 
           ) t1
     ) a
)b
WHERE ( a,b,c,d,e,f,g,h,i,j) IS DISTINCT FROM ( NULL ,NULL ,NULL ,NULL ,NULL ,NULL ,NULL ,NULL ,NULL ,NULL )        

;
    
+---+---+--+--+--+--+--+--+--+--+
| a | b |c |d |e |f |g |h |i |j |
+---+---+--+--+--+--+--+--+--+--+
| x | w |  |  |t |s |r |q |p |  |
| o | n |m |  |  |j |  |h |  |f |
| 1 | 2 |3 |  |5 |  |7 |  |9 |  |
+---+---+--+--+--+--+--+--+--+--+