PostgreSQL交叉表查询


196

有谁知道如何在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

这可能吗?


1
我的结构略有不同,发现此示例有点难以理解,因此我记录了我对stackoverflow.com/q/49051959/808723的看法。也许对任何人都有帮助。
GameScripting'Mar

Answers:


317

每个数据库安装一次附加模块tablefunc ,该模块提供功能。从Postgres 9.1开始,您可以使用:crosstab()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);

返回值:

栏目| 活跃 不活跃
--------- + -------- + ----------
 A | 1 | 2
 B | 4 | 5
 C |      7 | -!!
  • 无需强制转换和重命名。
  • 请注意以下错误结果C7在第一列中填写了该值。有时,此行为是理想的,但对于此用例而言并非如此。
  • 在简单的形式也被限制到恰好在所提供的输入查询三列:ROW_NAME类别。像下面的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);

返回值:

栏目| 活跃 不活跃
--------- + -------- + ----------
 A | 1 | 2
 B | 4 | 5
 C | |        7-   !!
  • 请注意的正确结果C

  • 所述第二参数可以是任何查询,返回一个每属性匹配在端列定义的顺序。通常,您将需要从基础表中查询不同的属性,如下所示:

    'SELECT DISTINCT attribute FROM tbl ORDER BY 1'

    在手册中。

    由于您仍然必须拼写出列定义列表中的所有列(预定义的变体除外),因此在类似所示的表达式中提供简短列表通常更为有效:crosstabN()VALUES

    $$VALUES ('Active'::text), ('Inactive')$$)

    或(不在手册中):

    $$SELECT unnest('{Active,Inactive}'::text[])$$  -- short syntax for long lists
  • 我使用美元报价来简化报价。

  • 您甚至可以使用- 输出具有不同数据类型的列,crosstab(text, text)只要值列的文本表示形式是目标类型的有效输入即可。这样,你可能有不同的属性种类和产量textdatenumeric等,为各自的属性。本手册本章crosstab(text, text)结尾处有一个代码示例。

db <> 在这里拨弄

进阶范例


\crosstabview 在psql中

Postgres 9.6将此元命令添加到其默认的交互式终端psql中。您可以运行将用作第一个crosstab()参数的查询,并将其输入\crosstabview(立即或在下一步中)。喜欢:

db=> SELECT section, status, ct FROM tbl \crosstabview

与上面类似的结果,但是它是客户端专用的表示功能。输入行的处理略有不同,因此ORDER BY不是必需的。\crosstabview手册中的详细信息该页面底部还有更多代码示例。

DanielVérité(psql功能的作者)对dba.SE的相关回答:



以前接受的答案是过时的。

  • 该功能的变型crosstab(text, integer)已过时。第二个integer参数被忽略。我引用了当前手册

    crosstab(text sql, int N) ...

    的过时版本crosstab(text)N现在将忽略该参数,因为值列的数量始终由调用查询确定

  • 不必要的转换和重命名。

  • 如果一行没有所有属性,它将失败。请参阅上面具有两个输入参数的安全变量,以正确处理缺少的属性。

  • ORDER BY是一参数形式的crosstab()手册:

    在实践中,SQL查询应始终指定ORDER BY 1,2以确保输入行正确排序


3
+1,写得很好,感谢您的注意In practice the SQL query should always specify ORDER BY 1,2 to ensure that the input rows are properly ordered
ChristopheD

我在使用$$ VALUES .. $$时遇到了一些问题。我用代替'VALUES(' '<ATTR>' ':: <类型>),...'
马可花样

我们可以在交叉表查询中指定参数绑定吗?我收到此错误=>无法确定参数$ 2的数据类型
Ashish 2015年

1
可以在交叉表查询中为列设置默认值吗?
Ashish

2
@Ashish:请开始一个新问题。评论不是地方。您可以始终链接到此上下文。
Erwin Brandstetter,2015年

30

您可以使用附加模块tablefunccrosstab()功能-您必须为每个数据库安装一次。从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中,这也适用吗?占位符。
Wim Verhavert,2010年

26
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

1
有人可以解释一下tablefunc模块中的交叉表功能为该答案添加了哪些内容吗?手头的工作以及我认为更容易理解的功能?
约翰·鲍威尔

4
@JohnBarça:可以使用CASE语句轻松解决此类简单情况。但是,使用更多的属性和/或其他数据类型(不仅仅是整数)会很快变得难以处理。顺便说一句:这种形式使用了聚合函数sum(),最好使用min()or,max()并且no也不ELSE适合text。但这与的效果略有不同corosstab(),后者仅对每个属性使用“ first”值。只要只有一个就没关系。最后,性能也很重要。crosstab()用C语言编写并针对任务进行了优化。
Erwin Brandstetter

对于PostgreSQL,这对我不起作用。我收到了错误ERROR: 42803: aggregate function calls may not be nested
Audrey

1
@Audrey您不是在运行相同的SQL?

2
考虑添加说明而不是代码块
Daniel L. VanDenBosch

10

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

谢谢,这为我解决了一个相关的问题。
JeffCharter

1

抱歉,此操作尚不完整,因为我无法在此处进行测试,但这可能会使您朝正确的方向前进。我正在从我使用的翻译成类似查询的内容中进行翻译:

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,最高出价和最低要求价以及两者之间的差额(正差额表示某物的价格可以低于其出售价格)。


1
您缺少from子句,否则这是正确的。在我的系统上,解释计划有很大不同-交叉表功能的成本为22.5,而LEFT JOIN方法的成本约为91.38的4倍。它还产生大约两倍的物理读取并执行哈希联接-与其他联接类型相比,这可能是非常昂贵的。
Jeremiah Peschka 2010年

谢谢耶利米,很高兴知道。我赞成其他答案,但是您的评论值得保留,因此我不会删除它。
LanceH 2010年

-1

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)

1
该答案与现有答案无关。
Erwin Brandstetter
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.