【问题标题】:Dynamically generate columns in PostgreSQL在 PostgreSQL 中动态生成列
【发布时间】:2017-02-08 08:04:11
【问题描述】:

我已经看到有一些类似的问题像这个一样,但我还没有理解如何自己编写代码。请记住,我只是这个领域的初学者。

基本上我想像这样旋转表格:

zoom |    day     | point         zoom | 2015-10-01 |  2015-10-02 | ......
------+-----------+-------  ---> ------+------------+-------------+
   1 | 2015-10-01 |   201            1 |    201     |     685     |
   2 | 2015-10-01 |    43            2 |     43     |     346     | 
   3 | 2015-10-01 |    80            3 |     80     |     534     | 
   4 | 2015-10-01 |   324            4 |    324     |     786     | 
   5 | 2015-10-01 |    25            5 |     25     |     685     |
   1 | 2015-10-02 |   685 
   2 | 2015-10-02 |   346 
   3 | 2015-10-02 |   534 
   4 | 2015-10-02 |   555 
   5 | 2015-10-02 |   786
   :
   :
   :

时间可能会有所不同。

我得到的左侧结果:

SELECT 
zoom,
to_char(date_trunc('day', time), 'YYYY-MM-DD') AS day,
count(*) as point
FROM province
WHERE time >= '2015-05-01' AND time < '2015-06-01'
GROUP BY to_char(date_trunc('day', time), 'YYYY-MM-DD'), zoom;

我读到如果我使用count 会出现一些问题,而且如果我使用CASEGROUP BY 会更好,但是我不知道如何CASE 这个。

Crosstab 本身不支持动态创建列名,但如果我理解正确的话,可以使用crosstab_hash 来实现。

这可能是一个不错的解决方案:http://okbob.blogspot.ca/2008/08/using-cursors-for-generating-cross.html 但是我坚持自己尝试编程。

我必须经常使用这种旋转,所以我会感谢任何形式的帮助和额外的解释。

编辑1

我试图弄清楚交叉表如何处理日期,目前没有返回列的动态名称。稍后我会解释原因。它与主要问题有关。对于这个例子,我只使用了 2 个日期。

基于@Erwin Brandstetter 的回答:

SELECT * FROM crosstab(
       'SELECT zoom, day, point
        FROM   province
        ORDER  BY 1, 2'
      , $$VALUES ('2015-10-01'::date), ('2015-10-02')$$)
AS ct (zoom text, day1 int, day2 int);

返回的结果是:

zoom |    day1    |    day2     | 
-----+------------+-------------+
   1 |    201     |     685     |
   2 |     43     |     346     | 
   3 |     80     |     534     | 
   4 |    324     |     786     | 

我正在努力解决这个问题

zoom | 2015-10-01 |  2015-10-02 | 
-----+------------+-------------+
   1 |    201     |     685     |
   2 |     43     |     346     | 
   3 |     80     |     534     | 
   4 |    324     |     786     | 

但我的查询不起作用:

SELECT *
FROM crosstab(
      'SELECT *
       FROM province
       ORDER  BY 1,2')
AS ct (zoom text, "2015-10-01" date, "2015-10-02" date);

ERROR:  return and sql tuple descriptions are incompatible

Edit1,Q1。为什么这不起作用,我怎样才能返回这样的结果?

我已经阅读了@Erwin Brandstetter 提供给我的链接,尤其是这个:Execute a dynamic crosstab query。我已经复制/粘贴了他的功能:

CREATE OR REPLACE FUNCTION pivottab(_tbl regclass, 
                                    _row text, _cat text, 
                                    _expr text,
                                    _type regtype)  
RETURNS text AS
$func$
DECLARE
   _cat_list text;
   _col_list text;
BEGIN
-- generate categories for xtab param and col definition list    
EXECUTE format(
 $$SELECT string_agg(quote_literal(x.cat), '), (')
        , string_agg(quote_ident  (x.cat), %L)
   FROM  (SELECT DISTINCT %I AS cat FROM %s ORDER BY 1) x$$
 , ' ' || _type || ', ', _cat, _tbl)
INTO  _cat_list, _col_list;

-- generate query string
RETURN format(
'SELECT * FROM crosstab(
   $q$SELECT %I, %I, %s
      FROM   %I
      GROUP  BY 1, 2
      ORDER  BY 1, 2$q$
 , $c$VALUES (%5$s)$c$
   ) ct(%1$I text, %6$s %7$s)'
, _row, _cat, _expr, _tbl, _cat_list, _col_list, _type
);

END
$func$ LANGUAGE plpgsql;

并通过查询调用它

SELECT pivottab('province','zoom','day','point','date');

函数返回给我:

                         pivottab                         
----------------------------------------------------------
 SELECT * FROM crosstab(                                 +
    $q$SELECT zoom, day, point                           +
       FROM   province                                   +
       GROUP  BY 1, 2                                    +
       ORDER  BY 1, 2$q$                                 +
  , $c$VALUES ('2015-10-01'), ('2015-10-02')$c$          +
    ) ct(zoom text, "2015-10-01" date, "2015-10-02" date)
(1 row)

所以当我编辑查询并添加时; (如果 ; 已经在那里就好了)我得到了:

ERROR:  column "province.point" must appear in the GROUP BY clause or be used in an aggregate function

Edit1,Q2。任何想法如何解决这个问题?

Edit1,Q3。我想下一个问题将是如何自动执行函数,在同一个链接上也提到过,但是卡在前面的步骤上。

【问题讨论】:

  • 通过循环记录并附加到支持查询的字符串来构建动态 SQL。这可以使用plpgsql 来实现。然后从函数中返回一个表,你就可以开始了:-)
  • 什么应该是动态的?结果列数?结果列的名称或数据类型?
  • @ErwinBrandstetter 抱歉,我刚刚在表格中添加了点。所以我需要动态添加列的名称......它就像 2015-10-01, 2015-10-02, 2015-10-03, 2015-10-04 ......取决于首先的 WHERE 部分询问。这应该是在不手动列出它们的情况下生成的,因为有时我可能有 75 天的间隔

标签: postgresql plpgsql dynamic-sql postgresql-9.3 crosstab


【解决方案1】:

您的示例的基本交叉表查询很简单:

SELECT * FROM crosstab(
       'SELECT zoom, day, point
        FROM   province
        ORDER  BY 1, 2'

     , $$VALUES ('2015-10-01'::date), ('2015-10-02')$$)
AS ct (zoom text, day1 int, day2 int);

具有动态列名或动态列数。 作为一种折衷方案,您可以拥有固定数量的列,并且只填充前导列。基础知识:

动态?

crosstab_hash 不会帮助您使用动态列名。它用于重复使用而无需键入列定义列表,但不适用于 dynamic 列名称。例子:

对于真正动态的列名,您需要两次 往返服务器。无论是使用第一个查询检索列名以构建第二个查询,还是创建游标、临时表或准备好的语句。无论您尝试什么,都需要两次往返。 SQL 想知道调用时的返回类型。

最接近“动态”调用的是我在此相关答案中定义的自定义 crosstab_n() 函数:


或者您放弃完全动态的交叉表查询的想法(因为,您知道,这是不可能的)并使用如上所述的两步工作流程。

  1. 让函数生成交叉表查询文本。您可以使用此处提供的功能(并根据您的需要进行调整!):

    特别是删除GROUP BY 1, 2,因为您不会在交叉表之前聚合行。

  2. 执行生成的函数。


为了完整起见,Postgres 9.6(刚刚发布)中还有新的\crosstabview metacommand in psql - 具有类似的功能,它可以显示动态列名(附加动态名称发生在 psql 客户端中,不在 Postgres 服务器中)。

【讨论】:

  • 谢谢,明天我会彻底浏览所有这些链接。所以基本上你是说很难动态返回这样的列,我应该寻找其他解决方案?
  • 完全动态的列在 SQL 中是绝对不可能的。绕过此限制的方法取决于您的要求。我在上面添加了另一种选择:改用两步工作流程...
  • 感谢您的快速回复。我被困在婴儿台阶上。请查看我在 Edit1 下编辑的问题!
  • @newbie_girl:根据您的需要调整功能。我在上面添加了一些提示。你需要对你在做什么有一个基本的了解。请将任何新问题作为新问题提出。
  • 抱歉,我认为打开一个新主题不合适。感谢您的额外指导。但我想我将不得不提出新问题。
猜你喜欢
  • 2012-10-04
  • 2016-04-02
  • 2015-05-08
  • 1970-01-01
  • 1970-01-01
  • 2012-07-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多