【问题标题】:How to unpivot a table in PostgreSQL如何在 PostgreSQL 中取消透视表
【发布时间】:2014-10-26 20:00:52
【问题描述】:

我在编写 Postgres 函数时遇到了困难,因为我不熟悉它。我有多个表要以这种格式导入 Postgres:

id | 1960 | 1961 | 1962 | 1963 | ...
____________________________________
 1    23     45     87     99
 2    12     31    ...

我需要转换成这种格式:

id | year | value
_________________
 1   1960    23
 1   1961    45
 1   1962    87
 ...
 2   1960    12
 2   1961    31
 ...

我也想像这样读取函数:

SELECT all-years FROM imported_table;
CREATE a new_table;
FROM min-year TO max-year LOOP
     EXECUTE "INSERT INTO new_table (id, year, value) VALUES (id, year, value)";
END LOOP;

但是,我在为此编写详细的细节时遇到了麻烦。在 PHP 中这样做对我来说会更容易,但我相信直接在 Postgres 函数中这样做会更干净。

年份(开始和结束)因表而异。有时,我什至可以每五年左右才有几年……

【问题讨论】:

  • 版本? select version();
  • 所有列NOT NULL?此外,以数字开头的列名如果没有双引号是不可能的。
  • 非常感谢您的建议!我正在运行 9.2.6 版。重要问题:有没有办法以更自动化的方式读取年份列?一次,我需要手动输入五十年,其次,开始和结束年份可能因表格而异。

标签: sql postgresql pivot dynamic-sql unpivot


【解决方案1】:

完全动态的版本需要动态 SQL。使用带有EXECUTE的plpgsql函数:

对于Postgres 9.2 或更早版本(在LATERAL 实施之前):

CREATE OR REPLACE FUNCTION f_unpivot_years92(_tbl regclass, VARIADIC _years int[])
  RETURNS TABLE(id int, year int, value int) AS
$func$
BEGIN
   RETURN QUERY EXECUTE '
   SELECT id
        , unnest($1) AS year
        , unnest(ARRAY["'|| array_to_string(_years, '","') || '"]) AS val
   FROM   ' || _tbl || '
   ORDER  BY 1, 2'
   USING _years;
END
$func$  LANGUAGE plpgsql;

对于 Postgres 9.3 或更高版本(使用 LATERAL):

CREATE OR REPLACE FUNCTION f_unpivot_years(_tbl regclass, VARIADIC _years int[])
  RETURNS TABLE(id int, year int, value int) AS
$func$
BEGIN
   RETURN QUERY EXECUTE (SELECT
     'SELECT t.id, u.year, u.val
      FROM  ' || _tbl || ' t
      LEFT   JOIN LATERAL (
         VALUES ' || string_agg(format('(%s, t.%I)', y, y), ', ')
     || ') u(year, val) ON true
      ORDER  BY 1, 2'
      FROM   unnest(_years) y
      );
END
$func$  LANGUAGE plpgsql;

关于VARIADIC

调用任意年份:

SELECT * FROM f_unpivot_years('tbl', 1961, 1964, 1963);

同样,传递一个实际的数组:

SELECT * FROM f_unpivot_years('tbl', VARIADIC '{1960,1961,1962,1963}'::int[]);

对于连续年份的长列表:

SELECT * 
FROM f_unpivot_years('t', VARIADIC ARRAY(SELECT generate_series(1950,2014)));

对于定期间隔的长列表(例如每 5 年):

SELECT *
FROM f_unpivot_years('t', VARIADIC ARRAY(SELECT generate_series(1950,2010,5)));

按要求输出。

该函数采用:
1. 一个有效的表名 - 如果它是非法的(如 '"CaMeL"'),则用双引号引起来。使用对象标识符类型regclass 来断言正确性并防御 SQL 注入。您可能希望模式限定故事名称是明确的(如'public."CaMeL"')。更多:

2. 任何与(双引号)列名对应的数字列表。
一个实际的数组,前缀为关键字VARIADIC

列数组不必以任何方式排序,但表和列必须存在,否则会引发异常。

输出按idyear 排序(如integer)。如果您希望根据输入数组的排序顺序对年份进行排序,请将其设为 ORDER BY 1。根据数组的排序顺序没有得到严格保证,但在当前实现中有效。更多信息:

也适用于 NULL 值。

SQL Fiddle 两个都有例子。

参考资料:

【讨论】:

  • 非常感谢。看起来很棒。但是,尽管它在 SQLFiddle 中正确运行,但它会在我的数据库中创建一个错误警告:“在“CREATE”或附近出现语法错误”。不确定我是否正确输入了这些字段:NAME unpivot;返回数字;模式输入;键入数字...或者这是什么。你有什么想法吗?
  • @luftikus143: RETURNS numeric?为什么?这是什么?只需复制/粘贴我的代码,它应该可以工作。
  • 啊,现在明白了。对不起!完美运行!非常感谢!
【解决方案2】:

PostgreSQL 9.3 提供了简洁的JSON functions,可用于此类任务,而无需定义新功能或了解许多列。

SELECT id, (k).key as year, (k).value as value FROM
  (SELECT j->>'id' as id, json_each_text(j) as k
    FROM (
       SELECT row_to_json(tbl) as j FROM tbl) 
    as q)
    as r
WHERE (k).key <> 'id';

http://sqlfiddle.com/#!15/1714b/13

【讨论】:

  • 这太棒了:我有“时期”需要取消。对于某些客户季度(即 4 个周期),有时是数周(即 52 或 53 周)。他们需要进入 1 DWH。这样就可以了。
【解决方案3】:

并行取消嵌套可能更容易

select
    id,
    unnest(array[1960, 1961, 1962]) as year,
    unnest(array["1960", "1961", "1962"]) as value
from (values
    (1,23,45,87), (2,12,31,53)
) s(id, "1960", "1961", "1962")
;
 id | year | value 
----+------+-------
  1 | 1960 |    23
  1 | 1961 |    45
  1 | 1962 |    87
  2 | 1960 |    12
  2 | 1961 |    31
  2 | 1962 |    53

【讨论】:

    【解决方案4】:

    最简单的方法是union all

    select id, 1960 as year, "1960" as value
    from table t
    union all
    select id, '1960', "1961"
    from table t
    . . .;
    

    一种更复杂的方法是:

    select t.id, s.yr,
           (case when s.yr = 1960 then "1960"
                 when s.yr = 1961 then "1961"
                 . . .
            end) as value
    from table t cross join
         generate_series(1960, 1980) s(yr);
    

    您可以使用insertcreate table as 将其放入另一个表中。

    【讨论】:

      猜你喜欢
      • 2022-01-23
      • 1970-01-01
      • 2014-08-20
      • 2021-04-01
      • 2021-11-19
      • 2015-03-06
      • 2019-10-19
      • 2021-11-08
      • 1970-01-01
      相关资源
      最近更新 更多