web-dev-qa-db-ja.com

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

2つのテーブルndefined row countに名前と値を指定した場合、関数のピボットされた_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);
_

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

Common multiplication table of 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; 
_

したがって、これはプレゼンテーションの問題にすぎませんこれもカスタム名で機能させたい-テキストへの単なる引数CASTedではなく、テーブルに設定された名前、

_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でできることは何ですか?

18
Evan Carroll

単純なケース、静的SQL

単純な場合のcrosstab()を使用したnon-dynamicソリューション:

_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番目の列の実際のは、このクエリでは関係ありません(crosstab()の1パラメータ形式)。

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

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

不明な戻り型、動的SQL

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

Postgresがユーザーテーブルのdataから結果の列を導出するようにします。 起こりません

いずれにしても、サーバーへのtwoラウンドトリップが必要です。カーソルを作成してから、それをウォークスルーします。または、一時テーブルを作成してからそれを選択します。または、タイプを登録して、呼び出しで使用します。

または、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インジェクションから守るためかもしれません)。

関連:

12

そのようなプレゼンテーションを生成するために「列定義リスト」なしで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 manual および https://wiki.postgresql.org/wiki/Crosstabview を参照してください。

11
Daniel Vérité

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

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

まず、両方のテーブルのデカルト積を取得します。

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/NBCYXA218

1
McNets

補足として、SQL:2016は ポリモーフィックテーブル関数(ISO/IEC TR 19075-7:2017) でこれに対応するようです

私はリンク What's New in SQL:2016 を見つけましたが、著者はこれについて詳しく説明していません。

0
Evan Carroll