web-dev-qa-db-ja.com

PostgreSQLで列を動的に生成する

このような質問がいくつかあるのを見てきましたが、自分でコーディングする方法を理解していません。私はこの分野の初心者であることを忘れないでください。

基本的に、次のようにテーブルをピボットします。

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を使用した方がよいと読みましたが、CASEthis。

Crosstab自体は列名の動的作成をサポートしていませんが、正しく理解していれば、crosstab_hashで実現できます。

これはおそらく素晴らしい解決策かもしれません: http://okbob.blogspot.ca/2008/08/using-cursors-for-generate-cross.html しかし、私はそれを自分でプログラムしようとして立ち往生しています。

私はこの種のピボットを頻繁に使用する必要があるので、その背後にあるあらゆる種類のヘルプと追加の説明を提供します。

Edit1

現在、列の動的な名前を返さずに、クロス集計が日付でどのように機能するかを理解しようとしています。後でその理由を説明します。それは主な質問に当てはまります。この例では、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が提供してくれたリンク、特にこれを読んだことがあります: 動的クロス集計クエリを実行 。私は彼の機能をコピー/貼り付けしました:

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。次の質問は、関数を自動的に実行する方法だと思います。これも同じリンクに記載されていますが、前の手順で行き詰まりました。

7
newbie_girl

この例の基本的なクロス集計クエリは単純です。

_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);
_

ただし、notは、動的な列名または動的な列数を使用します。妥協案として、列の数を固定し、先頭の列のみを埋めることができます。基本:

動的?

_crosstab_hash_は、動的な列名には役立ちません。列定義リストを入力せずに繰り返し使用するためのものですが、dynamic列名には使用できません。例:

真に動的な列名の場合、サーバーへのラウンドトリップが必要ですtwo。最初のクエリで列名を取得して2番目のクエリを作成するか、カーソル、一時テーブル、または準備済みステートメントを作成するか。何をしようとも、2回の往復が必要です。 SQLは、呼び出し時に戻り値の型を知りたいと考えています。

「動的」呼び出しに最も近いのは、この関連する回答で定義されているカスタムcrosstab_n()関数を使用することです。


または、完全に動的なクロス集計クエリのアイデアをあきらめて(ご存知のとおり、それは不可能であるため)、上記のように2段階のワークフローを使用します。

  1. 関数にクロス集計クエリテキストを生成させます。ここで提供されている機能を使用できます(そしてニーズに合わせて調整できます!):

    特に、クロス集計の前に行を集約しないため、_GROUP BY 1, 2_を削除します。

  2. 生成された関数を実行します。


完全を期すために、Postgres9.6(リリースされたばかり)に新しい _\crosstabview_ metacommand in psql があります-同様の機能で、動的な列名を表示できます(動的な名前の添付は、Postgresサーバーではなくpsqlクライアントで行われます)。

3