PostgreSQL交叉表查询

有没有人知道如何在PostgreSQL中创build交叉表查询?
例如,我有下面的表格:

Section Status Count A Active 1 A Inactive 2 B Active 4 B Inactive 5 

我想查询返回以下交叉表:

 Section Active Inactive A 1 2 B 4 5 

这可能吗?

每个数据库安装一个额外的模块tablefunc ,提供函数crosstab() 。 自从Postgres 9.1以后,你可以使用CREATE EXTENSION

 CREATE EXTENSION tablefunc; 

改进的testing用例

 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); -- 'C' with 'Active' is missing 
  • count是标准SQL中的保留字 。 Postgres允许它,但我宁愿避免这些作为标识符。

简单的forms – 不适合缺less的属性

crosstab(text)1个input参数:

 SELECT * FROM crosstab( 'SELECT section, status, ct FROM t ORDER BY 1,2' -- needs to be "ORDER BY 1,2" here ) AS ct ("Section" text, "Active" int, "Inactive" int); 

返回:

  Section | 活动| 待用
 --------- + -------- + ----------
  A |  1 |  2
  B |  4 | 五
  C |  7 |  - !
  • 不需要铸造和重命名。
  • 注意C错误结果:第一列填入数值7 。 有时候,这种行为是可取的,但不是这个用例。
  • 简单的forms也仅限于提供的input查询中的三列: row_namecategoryvalue 。 在下面的2参数替代中没有额外的列的空间。

安全的forms

crosstab(text, text)2个input参数:

 SELECT * FROM crosstab( 'SELECT section, status, ct FROM t ORDER BY 1,2' -- could also just be "ORDER BY 1" here ,$$VALUES ('Active'::text), ('Inactive')$$) AS ct ("Section" text, "Active" int, "Inactive" int); 

返回:

  Section | 活动| 待用
 --------- + -------- + ----------
  A |  1 |  2
  B |  4 | 五
  C |  |  7 - !
  • 注意C的正确结果。

  • 第二个参数可以是任何查询,每个属性返回一行 ,匹配最后列定义的顺序。 通常你会想要像这样从基础表中查询不同的属性:

     'SELECT DISTINCT attribute FROM tbl ORDER BY 1' 

    这在手册中。

    由于无论如何您必须列出列定义列表中的所有列(除了预定义的crosstab N ()变体),在VALUESexpression式中提供一个简短的列表通常会更有效,如下所示:

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

    或(不在手册中):

     $$SELECT unnest('{Active,Inactive}'::text[])$$ -- shorter for long lists 
  • 我用美元报价使报价更容易。

  • 您甚至可以使用crosstab(text, text)输出具有不同数据types的列 – 只要值列的文本表示forms是目标types的有效input。 这样你可能有不同types的属性,并输出textdatenumeric等各自的属性。 在手册中的章节crosstab(text, text)末尾有一个代码示例。

先进的例子

  • 透视使用Tablefunc多列 – 也演示提到“额外的列”

  • 使用CASE和GROUP BY进行dynamic替代

\crosstabview在psql中的\crosstabview

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

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

与上面类似的结果,但是它仅仅是客户端上的表示特征 。 input行的处理方式稍有不同,因此不需要ORDER BY 。 手册中\crosstabview详细信息。 该页面底部有更多的代码示例。

关于dba.SE的相关答案DanielVérité(psql特性的作者):

  • 如何在生成的表定义未知的情况下生成一个pivoted的CROSS JOIN?


以前接受的答案已经过时。

  • 函数crosstab(text, integer)的变体已过时。 第二个integer参数被忽略。 我引用当前手册 :

    crosstab(text sql, int N)

    已过时的crosstab(text)版本crosstab(text) 。 参数N现在被忽略,因为值列的数量总是由调用查询决定的

  • 不必要的铸造和重命名。

  • 如果一行没有全部属性,则失败。 请参阅上面两个input参数的安全变体,以正确处理缺失的属性。

  • ORDER BYcrosstab()的单参数forms中是必需的。 手册:

    在实践中,SQL查询应始终指定ORDER BY 1,2以确保input行已正确sorting

您可以使用附加模块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 section, SUM(CASE status WHEN 'Active' THEN count ELSE 0 END) AS active, SUM(CASE status WHEN 'Inactive' THEN count ELSE 0 END) AS inactive FROM t GROUP BY section 

JSON聚合解决scheme:

 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 

对不起,这是不完整的,因为我不能在这里testing,但它可能会让你在正确的方向。 我正在翻译从我使用的东西,使类似的查询:

 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,最高的价格出价和最低的价格要求和两者之间的差异(积极的差异将意味着可以买的东西less于它可以出售)。