PostgreSQL 交叉表查询
Posted
技术标签:
【中文标题】PostgreSQL 交叉表查询【英文标题】:PostgreSQL Crosstab Query 【发布时间】:2010-06-09 01:13:03 【问题描述】:有人知道如何在 PostgreSQL 中创建交叉表查询吗? 例如我有下表:
Section Status Count
A Active 1
A Inactive 2
B Active 4
B Inactive 5
我希望查询返回以下交叉表:
Section Active Inactive
A 1 2
B 4 5
这可能吗?
【问题讨论】:
我的结构略有不同,发现这个例子有点难以理解,所以我记录了我对这个***.com/q/49051959/808723 的思考方式。也许它对任何人都有帮助。 【参考方案1】:为每个数据库安装additional module tablefunc
一次,它提供了函数crosstab()
。从 Postgres 9.1 开始,您可以为此使用 CREATE EXTENSION
:
CREATE EXTENSION IF NOT EXISTS tablefunc;
改进的测试用例
CREATE TABLE tbl (
section text
, status text
, ct integer -- "count" is a reserved word in standard SQL
);
INSERT INTO tbl VALUES
('A', 'Active', 1), ('A', 'Inactive', 2)
, ('B', 'Active', 4), ('B', 'Inactive', 5)
, ('C', 'Inactive', 7); -- ('C', 'Active') is missing
简单的形式 - 不适合缺少的属性
crosstab(text)
带 1 输入参数:
SELECT *
FROM crosstab(
'SELECT section, status, ct
FROM tbl
ORDER BY 1,2' -- needs to be "ORDER BY 1,2" here
) AS ct ("Section" text, "Active" int, "Inactive" int);
返回:
部分 |活跃 |不活跃 ---------+--------+---------- 一个 | 1 | 2 乙| 4 | 5 C | 7 | ——!! 无需转换和重命名。 注意C
的不正确结果:值7
被填入第一列。有时,这种行为是可取的,但不适用于此用例。
在提供的输入查询中,简单形式也仅限于恰好三列:row_name、category、value时间>。 额外的列没有空间,就像下面的 2 参数替代方案一样。
安全表单
crosstab(text, text)
带有 2 输入参数:
SELECT *
FROM crosstab(
'SELECT section, status, ct
FROM tbl
ORDER BY 1,2' -- could also just be "ORDER BY 1" here
, $$VALUES ('Active'::text), ('Inactive')$$
) AS ct ("Section" text, "Active" int, "Inactive" int);
返回:
部分 |活跃 |不活跃 ---------+--------+---------- 一个 | 1 | 2 乙| 4 | 5 C | | 7——!!注意C
的正确结果。
第二个参数 可以是任何查询,每个属性返回一个行,匹配最后列定义的顺序。通常,您会希望像这样从基础表中查询不同的属性:
'SELECT DISTINCT attribute FROM tbl ORDER BY 1'
手册上有。
由于无论如何您都必须拼出列定义列表中的所有列(预定义的crosstab<i>N</i>()
变体除外),因此在VALUES
表达式中提供简短列表通常更有效,如下所示:
$$VALUES ('Active'::text), ('Inactive')$$)
或者(不在手册中):
$$SELECT unnest('Active,Inactive'::text[])$$ -- short syntax for long lists
我使用dollar quoting 来简化引用。
您甚至可以使用crosstab(text, text)
输出具有不同 数据类型 的列 - 只要值列的文本表示是目标类型的有效输入。这样,您可能拥有不同种类的属性,并为各自的属性输出text
、date
、numeric
等。 chapter crosstab(text, text)
in the manual末尾有代码示例。
db小提琴here
过多输入行的影响
多余的输入行以不同的方式处理 - 相同(“row_name”,“category”)组合的重复行 - 在上面的示例中为(section, status)
。
1-parameter 表单从左到右填充可用值列。多余的值将被丢弃。较早的输入行获胜。
2 参数 表单将每个输入值分配给其专用列,覆盖任何先前的分配。以后输入的行获胜。
通常,您一开始就没有重复项。但如果您这样做了,请根据您的要求仔细调整排序顺序 - 并记录发生的情况。 如果您不在乎,也可以快速获得任意结果。请注意效果。
高级示例
Pivot on Multiple Columns using Tablefunc - 还展示了提到的“额外列”
Dynamic alternative to pivot with CASE and GROUP BY
\crosstabview
在 psql 中
Postgres 9.6 将此元命令添加到其默认交互式终端 psql。您可以运行将用作第一个 crosstab()
参数的查询并将其提供给 \crosstabview
(立即或在下一步中)。喜欢:
db=> SELECT section, status, ct FROM tbl \crosstabview
与上面的结果类似,但它是客户端的表示功能。输入行的处理方式略有不同,因此不需要ORDER BY
。 \crosstabview
in the manual. 的详细信息该页面底部有更多代码示例。
Daniel Vérité(psql 功能的作者)对 dba.SE 的相关回答:
How do I generate a pivoted CROSS JOIN where the resulting table definition is unknown?【讨论】:
+1,写得很好,感谢关注In practice the SQL query should always specify ORDER BY 1,2 to ensure that the input rows are properly ordered
我在使用 $$VALUES .. $$ 时遇到了一些问题。我改用了 'VALUES ('''':: SELECT section,
SUM(CASE status WHEN 'Active' THEN count ELSE 0 END) AS active, --here you pivot each status value as a separate column explicitly
SUM(CASE status WHEN 'Inactive' THEN count ELSE 0 END) AS inactive --here you pivot each status value as a separate column explicitly
FROM t
GROUP BY section
【讨论】:
有人能解释一下 tablefunc 模块中的交叉表函数在这个答案中增加了什么,它既能完成手头的工作,又在我看来更容易理解? @JohnBarça:像这样的简单案例可以通过 CASE 语句轻松解决。但是,如果属性和/或其他数据类型不仅仅是整数,这会很快变得笨拙。顺便说一句:此表单使用聚合函数sum()
,最好使用min()
或max()
,而不是ELSE
,它也适用于text
。但这与corosstab()
的效果略有不同,corosstab()
仅使用每个属性的“第一个”值。没关系,只要只有一个。最后,性能也很重要。 crosstab()
用 C 语言编写并针对任务进行了优化。
考虑添加解释而不是仅仅添加一段代码
在我的 postgresql 中由于某种原因未定义 tablefunc 和 crosstab,我也不允许定义它们。这个直观的解决方案对我有用,所以赞一个!【参考方案3】:
您可以使用additional module tablefunc 的crosstab()
功能 - 您必须为每个数据库安装一次一次。从 PostgreSQL 9.1 开始,您可以使用CREATE EXTENSION
:
CREATE EXTENSION tablefunc;
在你的情况下,我相信它看起来像这样:
CREATE TABLE t (Section CHAR(1), Status VARCHAR(10), Count integer);
INSERT INTO t VALUES ('A', 'Active', 1);
INSERT INTO t VALUES ('A', 'Inactive', 2);
INSERT INTO t VALUES ('B', 'Active', 4);
INSERT INTO t VALUES ('B', 'Inactive', 5);
SELECT row_name AS Section,
category_1::integer AS Active,
category_2::integer AS Inactive
FROM crosstab('select section::text, status, count::text from t',2)
AS ct (row_name text, category_1 text, category_2 text);
【讨论】:
如果您在交叉表查询中使用参数,则必须正确转义它。示例:(从上面)说你只想要活动的: SELECT ... FROM crosstab('select section::text, status, count::text from t where status=''active''', 2) AS 。 ..(注意双引号)。如果用户在运行时传递参数(例如作为函数参数),您可以说: SELECT ... FROM crosstab('select section::text, status, count::text from t where status='' ' || par_active || '''', 2) AS ...(此处为三引号!)。在 BIRT 中,这也适用于 ?占位符。【参考方案4】:使用 JSON 聚合的解决方案:
CREATE TEMP TABLE t (
section text
, status text
, ct integer -- don't use "count" as column name.
);
INSERT INTO t VALUES
('A', 'Active', 1), ('A', 'Inactive', 2)
, ('B', 'Active', 4), ('B', 'Inactive', 5)
, ('C', 'Inactive', 7);
SELECT section,
(obj ->> 'Active')::int AS active,
(obj ->> 'Inactive')::int AS inactive
FROM (SELECT section, json_object_agg(status,ct) AS obj
FROM t
GROUP BY section
)X
【讨论】:
谢谢,这帮助我解决了一个相关问题。【参考方案5】:抱歉,这并不完整,因为我无法在此处对其进行测试,但它可能会让您朝着正确的方向前进。我正在翻译我使用的类似查询的东西:
select mt.section, mt1.count as Active, mt2.count as Inactive
from mytable mt
left join (select section, count from mytable where status='Active')mt1
on mt.section = mt1.section
left join (select section, count from mytable where status='Inactive')mt2
on mt.section = mt2.section
group by mt.section,
mt1.count,
mt2.count
order by mt.section asc;
我正在使用的代码是:
select m.typeID, m1.highBid, m2.lowAsk, m1.highBid - m2.lowAsk as diff, 100*(m1.highBid - m2.lowAsk)/m2.lowAsk as diffPercent
from mktTrades m
left join (select typeID,MAX(price) as highBid from mktTrades where bid=1 group by typeID)m1
on m.typeID = m1.typeID
left join (select typeID,MIN(price) as lowAsk from mktTrades where bid=0 group by typeID)m2
on m1.typeID = m2.typeID
group by m.typeID,
m1.highBid,
m2.lowAsk
order by diffPercent desc;
这将返回一个 typeID、最高出价和最低要价以及两者之间的差异(正数差异意味着可以以低于可以出售的价格购买某物)。
【讨论】:
您缺少一个 from 子句,否则这是正确的。解释计划在我的系统上大不相同——交叉表函数的成本为 22.5,而 LEFT JOIN 方法的成本大约是 91.38 的 4 倍。它还产生大约两倍的物理读取并执行散列连接 - 与其他连接类型相比,这可能非常昂贵。 谢谢耶利米,很高兴知道。我赞成另一个答案,但你的评论值得保留,所以我不会删除这个。【参考方案6】:Crosstab
函数在 tablefunc
扩展名下可用。您必须为数据库创建一次此扩展。
创建扩展tablefunc
;
您可以使用以下代码通过交叉表创建数据透视表:
create table test_Crosstab( section text,
<br/>status text,
<br/>count numeric)
<br/>insert into test_Crosstab values ( 'A','Active',1)
<br/>,( 'A','Inactive',2)
<br/>,( 'B','Active',4)
<br/>,( 'B','Inactive',5)
select * from crosstab(
<br/>'select section
<br/>,status
<br/>,count
<br/>from test_crosstab'
<br/>)as ctab ("Section" text,"Active" numeric,"Inactive" numeric)
【讨论】:
这个答案没有添加任何已经存在的答案。以上是关于PostgreSQL 交叉表查询的主要内容,如果未能解决你的问题,请参考以下文章