具有动态列名和多个输入列的 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
值。
现在我想要一个数据透视表,新列是arrivalTime
和name
的内容。对于上面的示例,它应该如下所示:
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 |
作为检索此结果的查询输入,我得到一个与name
s 匹配的模式以及指定arrivalTime
范围的开始和结束时间。
原表的属性:
名称列中的条目是不稳定的,即新名称进入 旧名字经常消失。name
和 arrivalTime
的每个组合都是唯一的。
每个name
和arrivalTime
组合在其中一个值列中只有一个条目。
想法
我已经给出了一些考虑:
我想了很多,应该使用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);
此解决方案缺少动态方面。作为输入,提供name
的LIKE
模式和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
。这很好用并且克服了重要的障碍,但我还没有完成:仍然缺少输入指定name
和arrivalTime
范围的模式的能力。这使得as ct(...)
的论点是动态的。
user711270 您需要任何类型的编程语言,它会为您生成 SQL 查询,处理输入模式。如果你在 SQL 中只使用 plpgsql 来创建这样的动态查询。以上是关于具有动态列名和多个输入列的 PostgreSQL 交叉表的主要内容,如果未能解决你的问题,请参考以下文章