結果のテーブル定義が不明なピボットCROSS JOINを生成するにはどうすればよいですか?


17

名前と値を持つ未定義の行数を持つ2つのテーブルがある場合、CROSS JOINそれらの値に対して関数のピボットをどのように表示しますか。

CREATE TEMP TABLE foo AS
SELECT x::text AS name, x::int
FROM generate_series(1,10) AS t(x);

CREATE TEMP TABLE bar AS
SELECT x::text AS name, x::int
FROM generate_series(1,5) AS t(x);

たとえば、その関数が乗算である場合、以下のような(乗算)テーブルをどのように生成しますか?

1..12の一般的な乗算表

これらの(arg1,arg2,result)行はすべて、次を使用して生成できます。

SELECT foo.name AS arg1, bar.name AS arg2, foo.x*bar.x AS result
FROM foo
CROSS JOIN bar; 

したがって、これは表示の問題にすぎません。これは、カスタム名(CASTテキストへの単なる引数ではなくテーブルに設定された名前)

CREATE TEMP TABLE foo AS
SELECT chr(x+64) AS name, x::int
FROM generate_series(1,10) AS t(x);

CREATE TEMP TABLE bar AS
SELECT chr(x+72) AS name, x::int
FROM generate_series(1,5) AS t(x);

これは、動的な戻り値型が可能なCROSSTABで簡単に実行できると思います。

SELECT * FROM crosstab(
  '
    SELECT foo.x AS arg1, bar.x AS arg2, foo.x*bar.x
    FROM foo
    CROSS JOIN bar
  ', 'SELECT DISTINCT name FROM bar'
) AS **MAGIC**

しかし、なしで**MAGIC**、私は得る

ERROR:  a column definition list is required for functions returning "record"
LINE 1: SELECT * FROM crosstab(

参考のために、名前で上記の例を使用して、これはより多くの何のようなものであるtablefunccrosstab()を望んでいます。

SELECT * FROM crosstab(
  '
    SELECT foo.x AS arg1, bar.x AS arg2, foo.x*bar.x
    FROM foo
    CROSS JOIN bar
  '
) AS t(row int, i int, j int, k int, l int, m int);

しかし、今度はbar、この例のテーブルの内容とサイズについての仮定に戻ります。だから、

  1. テーブルの長さは未定義ですが、
  2. 次に、クロス結合は未定義の次元のキューブを表します(上記のため)。
  3. カテゴリ名(クロスタブ用語)は表にあります

そのようなプレゼンテーションを生成するために、「列定義リスト」なしでPostgreSQLでできることは何ですか?


1
JSONの結果は良いアプローチでしょうか?ARRAYは良いアプローチでしょうか?この方法では、「出力テーブル」の定義はすでに知られています(そして修正されます)。JSONまたはARRAY内に柔軟性を置きます。情報を処理するために後で使用される多くのツールに依存すると思います。
ジョアノロ16

可能であれば、上記のようにすることをお勧めします。
エヴァンキャロル

回答:


11

単純なケース、静的SQL

単純な場合の非動的ソリューションcrosstab()

SELECT * FROM crosstab(
  'SELECT b.x, f.name, f.x * b.x AS prod
   FROM   foo f, bar b
   ORDER  BY 1, 2'
   ) AS ct (x int, "A" int, "B" int, "C" int, "D" int, "E" int
                 , "F" int, "G" int, "H" int, "I" int, "J" int);

結果の列をfoo.nameでなくで並べますfoo.x。どちらも並行して並べ替えられますが、それは単純なセットアップです。ケースに適したソート順を選択してください。2番目の列の実際のは、このクエリでは無関係です(1パラメータ形式のcrosstab())。

crosstab()定義上欠損値がないため、2つのパラメーターは必要ありません。見る:

(質問のクロスタブクエリは、後の編集で置換fooすることbarで修正しました。これによりクエリも修正されますが、からの名前は引き続き使用できますfoo。)

不明な戻りタイプ、動的SQL

列の名前と型を動的にすることはできません。SQLでは、呼び出し時に結果の列の数、名前、およびタイプを知る必要があります。明示的な宣言によって、またはシステムカタログ内の情報から(これは、SELECT * FROM tblPostgresが登録されたテーブル定義を検索することで起こります)

Postgres に、ユーザーテーブルのデータから結果の列を派生させたいとします。 起こらない

いずれにしても、サーバーへの往復が2回必要です。カーソルを作成してから、ウォークスルーします。または、一時テーブルを作成してから選択します。または、型を登録し、呼び出しで使用します。

または、1つのステップでクエリを生成し、次のステップで実行します。

SELECT $$SELECT * FROM crosstab(
  'SELECT b.x, f.name, f.x * b.x AS prod
   FROM   foo f, bar b
   ORDER  BY 1, 2'
   ) AS ct (x int, $$
 || string_agg(quote_ident(name), ' int, ' ORDER BY name) || ' int)'
FROM   foo;

これにより、上記のクエリが動的に生成されます。次のステップで実行します。

$$ネストされた引用符の処理を簡単にするために、ドル引用符()を使用しています。見る:

quote_ident() そうでなければ、不正な列名をエスケープするために不可欠です(そして、おそらくSQLインジェクションに対して防御します)。

関連:


「不明な戻り値の型、動的SQL」と呼ばれるクエリを実行すると、実際には別のクエリを表す文字列が返され、「次のステップで実行する」と言うことに気付きました。これは、たとえば、これからマテリアライズドビューを作成することが難しいことを意味しますか?
コリンD

@ColinD:難しくはありませんが、まったく不可能です。生成されたSQLから既知の戻り値のタイプでMVを作成できます。ただし、リターンタイプが不明なMVを使用することはできません。
アーウィンブランドステッター

10

そのようなプレゼンテーションを生成するために、「列定義リスト」なしでPostgreSQLでできることは何ですか?

これをプレゼンテーションの問題としてフレーム化する場合は、クエリ後のプレゼンテーション機能を検討してください。

psql(9.6)の新しいバージョンには\crosstabview、SQLサポートなしでクロスタブ表現で結果が表示されます(@Erwinの答えで述べられているように、SQLはこれを直接生成できないため:SQLは呼び出し時に結果の列の数、名前、およびタイプを要求する

たとえば、最初のクエリは次を提供します。

SELECT foo.name AS arg1, bar.name AS arg2, foo.x*bar.x AS result
FROM foo
CROSS JOIN bar
\crosstabview

 arg1 | 1  | 2  | 3  | 4  | 5  
------+----+----+----+----+----
 1    |  1 |  2 |  3 |  4 |  5
 2    |  2 |  4 |  6 |  8 | 10
 3    |  3 |  6 |  9 | 12 | 15
 4    |  4 |  8 | 12 | 16 | 20
 5    |  5 | 10 | 15 | 20 | 25
 6    |  6 | 12 | 18 | 24 | 30
 7    |  7 | 14 | 21 | 28 | 35
 8    |  8 | 16 | 24 | 32 | 40
 9    |  9 | 18 | 27 | 36 | 45
 10   | 10 | 20 | 30 | 40 | 50
(10 rows)

ASCII列名の2番目の例は次のとおりです。

SELECT foo.name AS arg1, bar.name AS arg2, foo.x*bar.x
    FROM foo
    CROSS JOIN bar
  \crosstabview

 arg1 | I  | J  | K  | L  | M  
------+----+----+----+----+----
 A    |  1 |  2 |  3 |  4 |  5
 B    |  2 |  4 |  6 |  8 | 10
 C    |  3 |  6 |  9 | 12 | 15
 D    |  4 |  8 | 12 | 16 | 20
 E    |  5 | 10 | 15 | 20 | 25
 F    |  6 | 12 | 18 | 24 | 30
 G    |  7 | 14 | 21 | 28 | 35
 H    |  8 | 16 | 24 | 32 | 40
 I    |  9 | 18 | 27 | 36 | 45
 J    | 10 | 20 | 30 | 40 | 50
(10 rows)

詳細については、psqlのマニュアルhttps://wiki.postgresql.org/wiki/Crosstabviewご覧ください


1
これは本当にクールです。
エヴァンキャロル

1
最もエレガントな回避策。
アーウィンブランドステッター16

1

これは決定的な解決策ではありません

これが今までの私の最善のアプローチです。最終的な配列を列に変換する必要があります。

最初に、両方のテーブルのデカルト積を取得しました。

select foo.name xname, bar.name yname, (foo.x * bar.x)::text as val,
       ((row_number() over ()) - 1) / (select count(*)::integer from foo) as row
 from bar
     cross join foo
 order by bar.name, foo.name

しかし、最初のテーブルのすべての行を識別するために行番号を追加しました。

((row_number() over ()) - 1) / (select count(*)::integer from foo)

次に、この形式で結果を表示しました。

[Row name] [Array of values]


select col_name, values
from
(
select '' as col_name, array_agg(name) as values from foo
UNION
select fy.name as col_name,
    (select array_agg(t.val) as values
    from  
        (select foo.name xname, bar.name yname, (foo.x * bar.x)::text as val,
              ((row_number() over ()) - 1) / (select count(*)::integer from foo) as row
        from bar
           cross join foo
        order by bar.name, foo.name) t
    where t.row = fy.row)
from
    (select name, (row_number() over(order by name)) - 1 as row from bar) fy
) a
order by col_name;

+---+---------------------+
|   |      ABCDEFGHIJ     |
+---+---------------------+
| I |     12345678910     |
+---+---------------------+
| J |   2468101214161820  |
+---+---------------------+
| K |  36912151821242730  |
+---+---------------------+
| L |  481216202428323640 |
+---+---------------------+
| M | 5101520253035404550 |
+---+---------------------+ 

コマースで区切られた文字列に変換する:

select col_name, values
from
(
select '' as col_name, array_to_string(array_agg(name),',') as values from foo
UNION
select fy.name as col_name,
    (select array_to_string(array_agg(t.val),',') as values
    from  
        (select foo.name xname, bar.name yname, (foo.x * bar.x)::text as val,
              ((row_number() over ()) - 1) / (select count(*)::integer from foo) as row
        from bar
           cross join foo
        order by bar.name, foo.name) t
    where t.row = fy.row)
from
    (select name, (row_number() over(order by name)) - 1 as row from bar) fy
) a
order by col_name;


+---+------------------------------+
|   | A,B,C,D,E,F,G,H,I,J          |
+---+------------------------------+
| I | 1,2,3,4,5,6,7,8,9,10         |
+---+------------------------------+
| J | 2,4,6,8,10,12,14,16,18,20    |
+---+------------------------------+
| K | 3,6,9,12,15,18,21,24,27,30   |
+---+------------------------------+
| L | 4,8,12,16,20,24,28,32,36,40  |
+---+------------------------------+
| M | 5,10,15,20,25,30,35,40,45,50 |
+---+------------------------------+

(あとで試してみてください:http : //rextester.com/NBCYXA2183


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.