unpivot和PostgreSQL

28 postgresql unpivot

PostgreSQL中是否有一个unpivot等效函数?

Ste*_*tew 85

创建示例表:

CREATE TEMP TABLE foo (id int, a text, b text, c text);
INSERT INTO foo VALUES (1, 'ant', 'cat', 'chimp'), (2, 'grape', 'mint', 'basil');
Run Code Online (Sandbox Code Playgroud)

你可以使用UNION ALL'unpivot'或'uncrosstab':

SELECT id,
       'a' AS colname,
       a AS thing
FROM foo
UNION ALL
SELECT id,
       'b' AS colname, 
       b AS thing
FROM foo
UNION ALL
SELECT id, 
       'c' AS colname,
       c AS thing
FROM foo
ORDER BY id;
Run Code Online (Sandbox Code Playgroud)

这将运行3个不同的子查询foo,每个子列对应我们要取消的每个列,并在一个表中返回每个子查询的每个记录.

但是这将扫描表N次,其中N是您想要取消的列数.这是低效的,例如,当您使用需要很长时间扫描的非常大的表时,这是一个很大的问题.

相反,使用:

SELECT id,
       unnest(array['a', 'b', 'c']) AS colname,
       unnest(array[a, b, c]) AS thing
FROM foo
ORDER BY id;
Run Code Online (Sandbox Code Playgroud)

这更容易编写,它只会扫描一次表.

array[a, b, c]返回一个数组对象,其值为a,b和c作为元素. unnest(array[a, b, c])将结果分解为每个数组元素的一行.

希望有所帮助!

  • 使用本博客中描述的`hstore`模块还有另一种解决方案:http://www.postgresonline.com/journal/archives/283-Unpivoting-data-in-PostgreSQL.html (6认同)
  • “ unnest”策略在具有255列的大型表上非常有用且高效,谢谢! (2认同)

小智 8

这里找到了 Thomas Kellerer 的精彩文章

使用 Postgres 旋转

有时需要规范化非规范化的表 - 与“交叉表”或“透视”操作相反。Postgres 不支持像 Oracle 或 SQL Server 这样的 UNPIVOT 运算符,但是模拟它非常简单。

下面的表格存储了每季度的汇总值:

create table customer_turnover
(
  customer_id   integer,
  q1            integer,
  q2            integer,
  q3            integer,
  q4            integer
);
Run Code Online (Sandbox Code Playgroud)

以及以下示例数据:

customer_id | q1  | q2  | q3  | q4 
------------+-----+-----+-----+----
          1 | 100 | 210 | 203 | 304
          2 | 150 | 118 | 422 | 257
          3 | 220 | 311 | 271 | 269
Run Code Online (Sandbox Code Playgroud)

但是我们希望季度是行(因为它们应该在标准化数据模型中)。

在 Oracle 或 SQL Server 中,这可以通过 UNPIVOT 运算符实现,但在 Postgres 中不可用。然而,Postgres 能够像使用表一样使用 VALUES 子句,这使得这实际上非常容易:

select c.customer_id, t.*
from customer_turnover c
  cross join lateral (
     values 
       (c.q1, 'Q1'),
       (c.q2, 'Q2'),
       (c.q3, 'Q3'),
       (c.q4, 'Q4')
  ) as t(turnover, quarter)
order by customer_id, quarter;
Run Code Online (Sandbox Code Playgroud)

将返回以下结果:

customer_id | turnover | quarter
------------+----------+--------
          1 |      100 | Q1     
          1 |      210 | Q2     
          1 |      203 | Q3     
          1 |      304 | Q4     
          2 |      150 | Q1     
          2 |      118 | Q2     
          2 |      422 | Q3     
          2 |      257 | Q4     
          3 |      220 | Q1     
          3 |      311 | Q2     
          3 |      271 | Q3     
          3 |      269 | Q4     
Run Code Online (Sandbox Code Playgroud)

使用标准 UNPIVOT 运算符的等效查询是:

select customer_id, turnover, quarter
from customer_turnover c
  UNPIVOT (turnover for quarter in (q1 as 'Q1', 
                                    q2 as 'Q2', 
                                    q3 as 'Q3',
                                    q4 as 'Q4'))
order by customer_id, quarter;
Run Code Online (Sandbox Code Playgroud)


Luk*_*zda 7

您可以使用VALUES()JOIN LATERAL取消透视列。

样本数据:

CREATE TABLE test(id int, a INT, b INT, c INT);
INSERT INTO test(id,a,b,c) VALUES (1,11,12,13),(2,21,22,23),(3,31,32,33);
Run Code Online (Sandbox Code Playgroud)

查询:

SELECT t.id, s.col_name, s.col_value
FROM test t
JOIN LATERAL(VALUES('a',t.a),('b',t.b),('c',t.c)) s(col_name, col_value) ON TRUE;
Run Code Online (Sandbox Code Playgroud)

DBFiddle演示

使用这种方法可以一次取消多列列的旋转。

编辑

使用Zack的建议:

SELECT t.id, col_name, col_value
FROM test t
CROSS JOIN LATERAL (VALUES('a', t.a),('b', t.b),('c',t.c)) s(col_name, col_value);

<=>

SELECT t.id, col_name, col_value
FROM test t
,LATERAL (VALUES('a', t.a),('b', t.b),('c',t.c)) s(col_name, col_value);
Run Code Online (Sandbox Code Playgroud)

db <> fiddle演示

  • @Vinicius 是的,LATERAL 是非常强大的构造。这里另一个[用法](/sf/ask/618815991/#36530228) (2认同)

小智 5

仅供我们这些寻找如何在 RedShift 中逆向旋转的人使用。

Stew 给出长格式解决方案似乎是实现这一目标的唯一方法。


对于那些看不到它的人,这里是粘贴在下面的文字:

我们没有可以执行旋转或反旋转的内置函数。但是,您始终可以编写 SQL 来执行此操作。

create table sales (regionid integer, q1 integer, q2 integer, q3 integer, q4 integer);
insert into sales values (1,10,12,14,16), (2,20,22,24,26);

select * from sales order by regionid;

 regionid | q1 | q2 | q3 | q4
----------+----+----+----+----
 1        | 10 | 12 | 14 | 16
 2        | 20 | 22 | 24 | 26
(2 rows)
Run Code Online (Sandbox Code Playgroud)

透视查询

create table sales_pivoted (regionid, quarter, sales)
as
select regionid, 'Q1', q1 from sales
UNION ALL
select regionid, 'Q2', q2 from sales
UNION ALL
select regionid, 'Q3', q3 from sales
UNION ALL
select regionid, 'Q4', q4 from sales
;

select * from sales_pivoted order by regionid, quarter;

 regionid | quarter | sales 
----------+---------+-------
 1        | Q1      | 10
 1        | Q2      | 12
 1        | Q3      | 14
 1        | Q4      | 16
 2        | Q1      | 20
 2        | Q2      | 22
 2        | Q3      | 24
 2        | Q4      | 26
(8 rows)
Run Code Online (Sandbox Code Playgroud)

逆向查询

select regionid, sum(Q1) as Q1, sum(Q2) as Q2, sum(Q3) as Q3, sum(Q4) as Q4
from
(select regionid, 
case quarter when 'Q1' then sales else 0 end as Q1,
case quarter when 'Q2' then sales else 0 end as Q2,
case quarter when 'Q3' then sales else 0 end as Q3,
case quarter when 'Q4' then sales else 0 end as Q4
from sales_pivoted)

group by regionid
order by regionid;

 regionid | q1 | q2 | q3 | q4 
----------+----+----+----+----
 1        | 10 | 12 | 14 | 16
 2        | 20 | 22 | 24 | 26
(2 rows)
Run Code Online (Sandbox Code Playgroud)

希望这会有所帮助,尼尔


Luk*_*der 5

只需使用 JSON:

with data (id, name) as (
  values (1, 'a'), (2, 'b')
)
select t.*
from data, lateral jsonb_each_text(to_jsonb(data)) with ordinality as t
order by data.id, t.ordinality;
Run Code Online (Sandbox Code Playgroud)

这产生

|key |value|ordinality|
|----|-----|----------|
|id  |1    |1         |
|name|a    |2         |
|id  |2    |1         |
|name|b    |2         |
Run Code Online (Sandbox Code Playgroud)

数据库小提琴


小智 2

我为 PostgreSQL 编写了一个可怕的 unpivot 函数。它相当慢,但它至少返回像您期望的逆透视操作一样的结果。

https://cgsrv1.arrc.csiro.au/blog/2010/05/14/unpivotuncrosstab-in-postgresql/

希望你能发现它有用..