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_name , category , value 。 在下面的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 ()
变体),在VALUES
expression式中提供一个简短的列表通常会更有效,如下所示:$$VALUES ('Active'::text), ('Inactive')$$)
或(不在手册中):
$$SELECT unnest('{Active,Inactive}'::text[])$$ -- shorter for long lists
-
我用美元报价使报价更容易。
-
您甚至可以使用
crosstab(text, text)
输出具有不同数据types的列 – 只要值列的文本表示forms是目标types的有效input。 这样你可能有不同types的属性,并输出text
,date
,numeric
等各自的属性。 在手册中的章节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 BY
在crosstab()
的单参数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于它可以出售)。