PostgreSQLクロス集計クエリ


196

PostgreSQLでクロス集計クエリを作成する方法を知っている人はいますか?
たとえば、次の表があります。

Section    Status    Count
A          Active    1
A          Inactive  2
B          Active    4
B          Inactive  5

クエリが次のクロス集計を返すようにします。

Section    Active    Inactive
A          1         2
B          4         5

これは可能ですか?


1
私は少し異なる構造を持っていて、この例を理解するのが少し難しいと感じたので、このstackoverflow.com/q/49051959/808723の私の考え方を文書化しました。多分それは誰にとっても役に立ちます。
GameScripting 2018年

回答:


317

追加のモジュールtablefunc は、機能を提供するデータベースごとに1 インストールしますcrosstab()。Postgres 9.1以降でCREATE EXTENSIONは、次のように使用できます。

CREATE EXTENSION IF NOT EXISTS tablefunc;

テストケースの改善

CREATE TABLE tbl (
   section   text
 , status    text
 , ct        integer  -- "count" is a reserved word in standard SQL
);

INSERT INTO tbl VALUES 
  ('A', 'Active', 1), ('A', 'Inactive', 2)
, ('B', 'Active', 4), ('B', 'Inactive', 5)
                    , ('C', 'Inactive', 7);  -- ('C', 'Active') is missing

単純なフォーム-欠落している属性には適合しません

crosstab(text)1つの入力パラメータ:

SELECT *
FROM   crosstab(
   'SELECT section, status, ct
    FROM   tbl
    ORDER  BY 1,2'  -- needs to be "ORDER BY 1,2" here
   ) AS ct ("Section" text, "Active" int, "Inactive" int);

戻り値:

セクション| アクティブ| 非活性
--------- + -------- + ----------
 A | 1 | 2
 B | 4 | 5
 C |      7 | -!!
  • キャストや名前の変更は必要ありません。
  • 誤った結果に注意しください。最初の列にC7が入力されています。この動作は望ましい場合がありますが、このユースケースでは望ましくありません。
  • シンプルなフォームもに限定されて正確にします。入力クエリ内の3つの列ROW_NAMEカテゴリ。以下の2パラメータの代替のように、追加の列を配置する余地はありません。

安全なフォーム

crosstab(text, text)2つの入力パラメータ:

SELECT *
FROM   crosstab(
   'SELECT section, status, ct
    FROM   tbl
    ORDER  BY 1,2'  -- could also just be "ORDER BY 1" here

  , $$VALUES ('Active'::text), ('Inactive')$$
   ) AS ct ("Section" text, "Active" int, "Inactive" int);

戻り値:

セクション| アクティブ| 非活性
--------- + -------- + ----------
 A | 1 | 2
 B | 4 | 5
 C | |        7-   !!
  • の正しい結果に注意してくださいC

  • 第2のパラメータは 1つのを返し、任意のクエリとすることができる行の終わりに列定義の順序に一致する属性ごと。次のように、基礎となるテーブルから個別の属性をクエリすることがよくあります。

    'SELECT DISTINCT attribute FROM tbl ORDER BY 1'

    それはマニュアルにあります。

    とにかく(定義済みのバリアントを除いて)列定義リストのすべての列を入力する必要があるため、次の例のような式で短いリストを指定する方が効率的です。crosstabN()VALUES

    $$VALUES ('Active'::text), ('Inactive')$$)

    または(マニュアルにはありません):

    $$SELECT unnest('{Active,Inactive}'::text[])$$  -- short syntax for long lists
  • 見積りを簡単にするために、ドル見積りを使用ました。

  • あなたとのことさえでき、出力列の異なるデータ型を持つcrosstab(text, text)- long値列のテキスト表現は、ターゲット・タイプの有効な入力であるとして。あなたが持つかもしれない。この方法では、さまざまな種類と出力の属性textdatenumericそれぞれの属性についてなど。マニュアルのcrosstab(text, text)の最後にコード例があります

ここに db <> fiddle

高度な例


\crosstabview PSQLで

Postgres 9.6は、このメタコマンドをデフォルトのインタラクティブターミナルpsqlに追加しました。最初のcrosstab()パラメーターとして使用するクエリを実行し、それを\crosstabview(すぐにまたは次のステップで)にフィードできます。お気に入り:

db=> SELECT section, status, ct FROM tbl \crosstabview

上記と同様の結果ですが、これはクライアント側の排他的な表現機能です。入力行は少し異なる方法で処理されるため、ORDER BY必要ありません。\crosstabviewマニュアルの詳細そのページの下部には、さらに多くのコード例があります。

DanielVérité(psql機能の作者)によるdba.SEの関連回答:



以前に受け入れられた答えは時代遅れです。

  • 関数のバリアントcrosstab(text, integer)が古くなっています。2番目のintegerパラメーターは無視されます。私は現在のマニュアルを引用します

    crosstab(text sql, int N) ...

    の廃止バージョンcrosstab(text)N値列の数は常に呼び出し元のクエリによって決定されるため、パラメーターは無視されます。

  • 不必要なキャストと名前の変更。

  • 行にすべての属性がない場合は失敗します。不足している属性を適切に処理するには、上記の2つの入力パラメーターを持つ安全なバリアントを参照してください。

  • ORDER BYの1パラメータ形式では必須ですcrosstab()マニュアル:

    実際には、SQLクエリは常にORDER BY 1,2入力行が適切に順序付けられるように指定する必要があります


3
+1、良い記事、通知に感謝In practice the SQL query should always specify ORDER BY 1,2 to ensure that the input rows are properly ordered
ChristopheD

$$ VALUES .. $$の使用に問題があります。私の代わりに'VALUES(' '<attrの>' ':: <タイプ>)、..'使用してきました
マルコ・ファンタジア

クロス集計クエリでパラメーターバインディングを指定できますか?このエラーが発生しています=>パラメータ$ 2のデータ型を特定できませんでした
Ashish

1
クロス集計クエリで列のデフォルト値を設定することは可能ですか?
Ashish

2
@Ashish:新しい質問を始めてください。コメントは場所ではありません。あなたはいつでもコンテキストのためにこれにリンクすることができます。
Erwin Brandstetter、2015年

30

データベースごとに1 インストールする必要がある追加モジュールtablefuncのcrosstab()機能を使用できます。PostgreSQL 9.1以降では、次のように使用できます。CREATE EXTENSION

CREATE EXTENSION tablefunc;

あなたの場合、私はそれがこのようなものになると信じています:

CREATE TABLE t (Section CHAR(1), Status VARCHAR(10), Count integer);

INSERT INTO t VALUES ('A', 'Active',   1);
INSERT INTO t VALUES ('A', 'Inactive', 2);
INSERT INTO t VALUES ('B', 'Active',   4);
INSERT INTO t VALUES ('B', 'Inactive', 5);

SELECT row_name AS Section,
       category_1::integer AS Active,
       category_2::integer AS Inactive
FROM crosstab('select section::text, status, count::text from t',2)
            AS ct (row_name text, category_1 text, category_2 text);

クロス集計クエリでパラメータを使用する場合は、適切にエスケープする必要があります。例:(上記から)アクティブなものだけが欲しいと言う場合:SELECT ... FROM crosstab( 'select section :: text、status、count :: text from t where status =' 'active' ''、2)AS。 ..(二重引用符に注意してください)。パラメーターが実行時にユーザーによって(たとえば関数パラメーターとして)渡される場合、次のように言うことができます。SELECT ... FROM crosstab( 'select section :: text、status、count :: text from t where status =' ' '|| par_active ||' '' '、2)AS ...(ここで三重引用符!)BIRTでは、これは?プレースホルダー。
Wim Verhavert 2010

26
SELECT section,
       SUM(CASE status WHEN 'Active' THEN count ELSE 0 END) AS active, --here you pivot each status value as a separate column explicitly
       SUM(CASE status WHEN 'Inactive' THEN count ELSE 0 END) AS inactive --here you pivot each status  value as a separate column explicitly

FROM t
GROUP BY section

1
誰かがtablefuncモジュールのクロス集計関数がこの答えに追加するものを説明できますか?これはどちらも手元にあり、私の頭の方が理解しやすくなっていますか?
John Powell

4
@JohnBarça:このような単純なケースは、CASEステートメントで簡単に解決できます。ただし、これは、単なる整数よりも多くの属性や他のデータ型を使用すると、非常に扱いにくくなります。余談ですが、このフォームは集約関数sum()を使用しているため、min()orまたはmax()no ELSEを使用することをお勧めしますtext。ただし、これはとは微妙に異なる効果がありcorosstab()、属性ごとに「最初の」値のみを使用します。1つしか存在できなければ問題ありません。最後に、パフォーマンスも重要です。crosstab()Cで記述され、タスク用に最適化されています。
Erwin Brandstetter、2012

これは私にとって、postgresqlでは機能しません。エラーが表示されるERROR: 42803: aggregate function calls may not be nested
オードリー

1
@オードリーあなたは同じSQLを実行していないのですか?

2
説明を追加するか、コードのブロックを追加することを検討してください
ダニエルL.ヴァンデンボッシュ2017年

10

JSON集約を使用したソリューション:

CREATE TEMP TABLE t (
  section   text
, status    text
, ct        integer  -- don't use "count" as column name.
);

INSERT INTO t VALUES 
  ('A', 'Active', 1), ('A', 'Inactive', 2)
, ('B', 'Active', 4), ('B', 'Inactive', 5)
                   , ('C', 'Inactive', 7); 


SELECT section,
       (obj ->> 'Active')::int AS active,
       (obj ->> 'Inactive')::int AS inactive
FROM (SELECT section, json_object_agg(status,ct) AS obj
      FROM t
      GROUP BY section
     )X

ありがとう、これは関連する問題で私を助けました。
JeffCharter

1

ここではテストできないため、これは完了していませんが、正しい方向に進むことができます。同様のクエリを作成するために使用するものから翻訳しています。

select mt.section, mt1.count as Active, mt2.count as Inactive
from mytable mt
left join (select section, count from mytable where status='Active')mt1
on mt.section = mt1.section
left join (select section, count from mytable where status='Inactive')mt2
on mt.section = mt2.section
group by mt.section,
         mt1.count,
         mt2.count
order by mt.section asc;

私が働いているコードは:

select m.typeID, m1.highBid, m2.lowAsk, m1.highBid - m2.lowAsk as diff, 100*(m1.highBid - m2.lowAsk)/m2.lowAsk as diffPercent
from mktTrades m
   left join (select typeID,MAX(price) as highBid from mktTrades where bid=1 group by typeID)m1
   on m.typeID = m1.typeID
   left join (select typeID,MIN(price) as lowAsk  from mktTrades where bid=0 group by typeID)m2
   on m1.typeID = m2.typeID
group by m.typeID, 
         m1.highBid, 
         m2.lowAsk
order by diffPercent desc;

これは、typeID、要求された最高価格と最低価格、およびその2つの差を返します(正の差は、何かが売れるよりも安い価格で買えることを意味します)。


1
from句がありません。それ以外の場合は正しいです。Explainプランは私のシステムで大きく異なります-クロス集計関数のコストは22.5ですが、LEFT JOINアプローチは91.38のコストで約4倍のコストがかかります。また、約2倍の物理読み取りを生成し、ハッシュ結合を実行します。これは、他の結合タイプと比較して非常に高価になる可能性があります。
エレミヤペシュカ

ジェレミアに感謝します。他の回答に賛成しましたが、あなたのコメントは残しておく価値があるので、私はこれを削除しません。
LanceH

-1

Crosstab機能はtablefunc拡張機能の下で利用可能です。データベースに対してこの拡張機能を1回作成する必要があります。

CREATE EXTENSION tablefunc;

以下のコードを使用して、クロス集計を使用してピボットテーブルを作成できます。

create table test_Crosstab( section text,
<br/>status text,
<br/>count numeric)

<br/>insert into test_Crosstab values ( 'A','Active',1)
                <br/>,( 'A','Inactive',2)
                <br/>,( 'B','Active',4)
                <br/>,( 'B','Inactive',5)

select * from crosstab(
<br/>'select section
    <br/>,status
    <br/>,count
    <br/>from test_crosstab'
    <br/>)as ctab ("Section" text,"Active" numeric,"Inactive" numeric)

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