web-dev-qa-db-ja.com

PostgreSQLクロス集計クエリ

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

これは可能ですか?

166
schone

追加モジュールをインストールしますtablefunconceデータベースごとに、関数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) with1入力パラメーター:

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 | -!! 
  • キャストと名前変更の必要はありません。
  • Cincorrectの結果に注意してください:最初の列には値7が入力されます。この動作が望ましい場合もありますが、このユースケースには適していません。
  • 単純な形式も、指定された入力クエリのexactly3列に制限されます:row_namecategory。下記の2パラメータの代替のように、extra columnsの余地はありません。

安全なフォーム

crosstab(text, text) with2入力パラメーター:

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の正しい結果に注意してください。

  • secondパラメーターは、最後に列定義の順序と一致する属性ごとに1つrowを返す任意のクエリにすることができます。多くの場合、次のように、基になるテーブルから個別の属性を照会する必要があります。

    'SELECT DISTINCT attribute FROM tbl ORDER BY 1'
    

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

    とにかく列定義リストのすべての列を綴る必要があるため(事前定義されたcrosstabN()バリアントを除く)、通常、次のようにVALUES式で短いリストを提供する方が効率的です。

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

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

    $$SELECT unnest('{Active,Inactive}'::text[])$$  -- short syntax for long lists
    
  • dollar quoting を使用して、引用を簡単にしました。

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

db <>フィドル ここ

高度な例


psqlの\crosstabview

Postgres9.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パラメーターは無視されます。 currentマニュアルを引用します

    crosstab(text sql, int N) ...

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

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

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

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

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

279

追加モジュールtablefunc -のcrosstab()関数を使用できます。これは、データベースごとにインストールする必要がありますonce。 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);
27
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
20
araqnid

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
4
Milos

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

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、最高値、最低値、および両者の差を返します(差が正の場合は、販売できる金額より少ない金額で購入できる可能性があることを意味します)。

1
LanceH