具有动态列名和多个输入列的 PostgreSQL 交叉表

Posted

技术标签:

【中文标题】具有动态列名和多个输入列的 PostgreSQL 交叉表【英文标题】:PostgreSQL crosstab with dynamic column names and multiple input columns 【发布时间】:2017-05-09 14:17:08 【问题描述】:

问题

我有一个 PostgreSQL 9.6 数据库,其中包含一个根据具有不同类型值的 EAV 模型设计的表。示例摘录如下所示:

 name |arrivalTime | boolValue | intValue | floatValue | stringValue
------+------------+-----------+----------+------------+------------
 a1   |  10:00:00  |   true    |          |            |
 c3   |  10:00:00  |           |   12     |            |
 d4   |  10:00:00  |           |          |            | hello
 e5   |  15:00:00  |           |          |    45.67   |
 c3   |  15:00:00  |           |   45     |            |
 b2   |  20:00:00  |           |          |    4.567   |
 a1   |  20:00:00  |   false   |          |            |
 d4   |  22:00:00  |           |          |            |  bye
 b2   |  22:00:00  |           |          |    12.34   |

空单元格表示数据库中的null 值。

现在我想要一个数据透视表,新列是arrivalTimename 的内容。对于上面的示例,它应该如下所示:

arrivalTime |  a1   |  b2   |  c3   |  d4   |  e5
------------+-------+-------+-------+-------+-------
  10:00:00  | true  |       |  12   | hello |
  15:00:00  |       |       |  45   |       | 45.67
  20:00:00  | false | 4.567 |       |       |
  22:00:00  |       | 12.34 |       |  bye  |

作为检索此结果的查询输入,我得到一个与names 匹配的模式以及指定arrivalTime 范围的开始和结束时间。

原表的属性:

名称列中的条目是不稳定的,即新名称进入 旧名字经常消失。 namearrivalTime 的每个组合都是唯一的。 每个namearrivalTime 组合在其中一个值列中只有一个条目。

想法

我已经给出了一些考虑:

我想了很多,应该使用crosstab 函数 由于列是动态的,因此需要两个查询,如 Dynamically generate columns in PostgreSQL 或 Execute a dynamic crosstab query 中所述。 使用format() 函数生成第一个查询可能是个好主意。

示例表

这里是创建示例表的 SQL 代码:

CREATE TABLE IF NOT EXISTS playTable (
  name TEXT NOT NULL,
  arrivalTime TIME NOT NULL,
  floatValue REAL NULL,
  intValue INT NULL,
  boolValue BOOLEAN NULL,
  stringValue TEXT NULL,
  PRIMARY KEY (name, arrivalTime),
  CONSTRAINT single_value CHECK(
    (boolValue IS NOT NULL)::INT + 
    (intValue IS NOT NULL)::INT + 
    (floatValue IS NOT NULL)::INT +
    (stringValue IS NOT NULL)::INT = 1
  )
);

并插入值:

INSERT INTO playTable ( name, arrivalTime, boolValue ) VALUES ( 'a1', '10:00:00', true );
INSERT INTO playTable ( name, arrivalTime, intValue ) VALUES ( 'c3', '10:00:00', 12 );
INSERT INTO playTable ( name, arrivalTime, stringValue ) VALUES ( 'd4', '10:00:00', 'hello' );
INSERT INTO playTable ( name, arrivalTime, floatValue ) VALUES ( 'e5', '15:00:00', 45.67 );
INSERT INTO playTable ( name, arrivalTime, intValue ) VALUES ( 'c3', '15:00:00', 45 );
INSERT INTO playTable ( name, arrivalTime, floatValue ) VALUES ( 'b2', '20:00:00', 4.567 );
INSERT INTO playTable ( name, arrivalTime, boolValue ) VALUES ( 'a1', '20:00:00', false );
INSERT INTO playTable ( name, arrivalTime, stringValue ) VALUES ( 'd4', '22:00:00', 'bye' );
INSERT INTO playTable ( name, arrivalTime, floatValue ) VALUES ( 'b2', '22:00:00', 12.34 );

数据透视表,非动态

klin 提供了解决方案的起点,我猜:

SELECT *
FROM crosstab(
    $ct$
        SELECT 
            arrivalTime, name, concat(boolValue, intValue, floatValue, stringValue)
        FROM playTable
        ORDER BY 1, 2
    $ct$,
    $ct$
        SELECT DISTINCT name 
        FROM playTable
        ORDER BY 1
    $ct$) 
AS ct("arrivalTime" time, "a1" BOOLEAN, "b2" REAL, "c3" INT, "d4" TEXT, "e5" REAL);

此解决方案缺少动态方面。作为输入,提供nameLIKE 模式和arrivalTime 的范围(即最小值和最大值)。这使得as ct(...) 的论点动态化。

【问题讨论】:

【参考方案1】:

在最后四列中使用 coalesce()。您必须将列转换为 text 才能执行此操作:

select *
from crosstab(
    $ct$
        select 
            arrivaltime, name, 
            coalesce(boolvalue::text, intvalue::text, floatvalue::text, stringvalue)
        from my_table
        order by 1, 2
    $ct$,
    $ct$
        select distinct name 
        from my_table
        order by 1
    $ct$) 
as ct("arrivalTime" time, "a1" text, "b2" text, "c3" text, "d4" text, "e5" text);

 arrivalTime |  a1   |  b2   | c3 |  d4   |  e5   
-------------+-------+-------+----+-------+-------
 10:00:00    | true  |       | 12 | hello | 
 15:00:00    |       |       | 45 |       | 45.67
 20:00:00    | false | 4.567 |    |       | 
 22:00:00    |       | 12.34 |    | bye   | 
(4 rows)

由于示例数据的格式,我使用arrivalTime time,将其更改为timestamp

【讨论】:

我尝试了一些解决方案并稍微扩展了我的问题。我将您的代码从使用coalesce 更改为concat。这很好用并且克服了重要的障碍,但我还没有完成:仍然缺少输入指定namearrivalTime 范围的模式的能力。这使得as ct(...) 的论点是动态的。 user711270 您需要任何类型的编程语言,它会为您生成 SQL 查询,处理输入模式。如果你在 SQL 中只使用 plpgsql 来创建这样的动态查询。

以上是关于具有动态列名和多个输入列的 PostgreSQL 交叉表的主要内容,如果未能解决你的问题,请参考以下文章

具有可变列名的动态更新语句

如何在postgresql中查找具有特定列的表

PostgreSQL:具有选择性列的 row_to_json [重复]

在 PostgreSQL 中选择具有特定列名的列

数千列的动态枢轴

PostgreSQL 动态列选择