247

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

これは可能ですか?

4

6 に答える 6

401

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

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

戻り値:

セクション | アクティブ | 非活性
------+--------+----------
 あ | 1 | 2
 ビ | 4 | 5
 シー |      7 | -- !!
  • キャストと名前変更の必要はありません。
  • の誤った結果に注意してください。最初の列にC値が入力されています。7場合によっては、この動作が望ましい場合もありますが、このユース ケースではそうではありません。
  • また、単純な形式は、指定された入力クエリの正確に3 つの列 ( row_namecategoryvalue ) に制限されています。以下の 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);

戻り値:

セクション | アクティブ | 非活性
------+--------+----------
 あ | 1 | 2
 ビ | 4 | 5
 シー | | |        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)このようにして、それぞれの属性に対してさまざまな種類の属性と outputなどtextを使用できます。マニュアルのの最後にコード例があります。datenumericcrosstab(text, text)

デシベル<>ここでフィドル

余分な入力行の影響

上記の例では、余分な入力行は異なる方法で処理されます - 同じ ("row_name", "category") の組み合わせに対して行が重複しています(section, status)

1パラメータ形式では、使用可能な値の列が左から右に入力されます。超過値は破棄されます。
以前の入力行が優先されます。

2 パラメータ形式は、各入力値を専用の列に割り当て、以前の割り当てを上書きします。
後の入力行が優先されます。

通常、最初から重複はありません。ただし、そうする場合は、並べ替え順序を要件に合わせて慎重に調整し、何が起こっているかを文書化してください。
または、気にしない場合は、任意の結果をすばやく取得します。効果だけはご了承ください。

高度な例


\crosstabviewpsqlで

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

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

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

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

于 2012-08-01T02:48:06.840 に答える
36
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
于 2010-06-09T18:31:53.737 に答える
32

追加モジュール tablefunccrosstab()の機能を使用できます。これは、データベースごとに 1インストールする必要があります。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);
于 2010-06-09T01:34:00.957 に答える
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 つの差を返します (正の差は、販売可能な価格よりも低い価格で何かを購入できることを意味します)。

于 2010-06-09T01:33:08.820 に答える