8

これが私のテーブル'tab_test'です:

year    animal  price
2000    kittens 79
2000    kittens 93
2000    kittens 100
2000    puppies 15
2000    puppies 32
2001    kittens 31
2001    kittens 17
2001    puppies 65
2001    puppies 48
2002    kittens 84
2002    kittens 86
2002    puppies 15
2002    puppies 95
2003    kittens 62
2003    kittens 24
2003    puppies 36
2003    puppies 41
2004    kittens 65
2004    kittens 85
2004    puppies 58
2004    puppies 95
2005    kittens 45
2005    kittens 25
2005    puppies 15
2005    puppies 35
2006    kittens 50
2006    kittens 80
2006    puppies 95
2006    puppies 49
2007    kittens 40
2007    kittens 19
2007    puppies 81
2007    puppies 38
2008    kittens 37
2008    kittens 51
2008    puppies 29
2008    puppies 72
2009    kittens 84
2009    kittens 26
2009    puppies 49
2009    puppies 34
2010    kittens 75
2010    kittens 96
2010    puppies 18
2010    puppies 26
2011    kittens 35
2011    kittens 21
2011    puppies 90
2011    puppies 18
2012    kittens 12
2012    kittens 23
2012    puppies 74
2012    puppies 79

行と列を入れ替えて、「子猫」と「子犬」の平均を取得するコードを次に示します。

SELECT
    year,
    AVG(CASE WHEN animal = 'kittens' THEN price END) AS "kittens",
    AVG(CASE WHEN animal = 'puppies' THEN price END) AS "puppies"
FROM tab_test
GROUP BY year
ORDER BY year;

上記のコードの出力は次のとおりです。

    year    kittens puppies
    2000    90.6666666666667    23.5
    2001    24.0    56.5
    2002    85.0    55.0
    2003    43.0    38.5
    2004    75.0    76.5
    2005    35.0    25.0
    2006    65.0    72.0
    2007    29.5    59.5
    2008    44.0    50.5
    2009    55.0    41.5
    2010    85.5    22.0
    2011    28.0    54.0
    2012    17.5    76.5

私が欲しいのは2番目のようなCOUNT()テーブルですが、最初のテーブルに少なくとも3つあるアイテムのみが含まれます。言い換えると、目標はこれを出力として持つことです。

year    kittens
2000    90.6666666666667

最初のテーブルには、少なくとも3つの「子猫」のインスタンスがありました。
これはPostgreSQLで可能ですか?

4

4 に答える 4

12

CASE

ケースが示されているように単純な場合、CASEステートメントは次のようになります。

SELECT year
     , sum(CASE WHEN animal = 'kittens' THEN price END) AS kittens
     , sum(CASE WHEN animal = 'puppies' THEN price END) AS puppies
FROM  (
   SELECT year, animal, avg(price) AS price
   FROM   tab_test
   GROUP  BY year, animal
   HAVING count(*) > 2
   ) t
GROUP  BY year
ORDER  BY year;

を使用するか、外部クエリの集計関数として使用sum()するかは関係ありません。この場合、これらはすべて同じ値になります。max()min()

SQLフィドル

crosstab()

カテゴリが多いほど、クエリが簡単になりcrosstab()ます。これは、テーブルが大きい場合にも高速になるはずです。

追加のモジュールtablefuncをインストールする必要があります(データベースごとに1回)。Postgres 9.1以降、これは次のように単純です。

CREATE EXTENSION tablefunc;

この関連する回答の詳細:

SELECT * FROM crosstab(
      'SELECT year, animal, avg(price) AS price
       FROM   tab_test
       GROUP  BY animal, year
       HAVING count(*) > 2
       ORDER  BY 1,2'

      ,$$VALUES ('kittens'::text), ('puppies')$$)
AS ct ("year" text, "kittens" numeric, "puppies" numeric);

サイトでは追加のモジュールが許可されていないため、これにはsqlfiddleはありません。

基準

私の主張を検証するために、小さなテストデータベースの実際のデータに近いものを使用して簡単なベンチマークを実行しました。PostgreSQL9.1.6。テストEXPLAIN ANALYZE、ベスト10:

10020行のテストセットアップ:

CREATE TABLE tab_test (year int, animal text, price numeric);

-- years with lots of rows
INSERT INTO tab_test
SELECT 2000 + ((g + random() * 300))::int/1000 
     , CASE WHEN (g + (random() * 1.5)::int) %2 = 0 THEN 'kittens' ELSE 'puppies' END
     , (random() * 200)::numeric
FROM   generate_series(1,10000) g;

-- .. and some years with only few rows to include cases with count < 3
INSERT INTO tab_test
SELECT 2010 + ((g + random() * 10))::int/2
     , CASE WHEN (g + (random() * 1.5)::int) %2 = 0 THEN 'kittens' ELSE 'puppies' END
     , (random() * 200)::numeric
FROM   generate_series(1,20) g;

結果:

@bluefeet
合計実行時間:95.401ミリ秒

@wildplasser(異なる結果、次の行を含むcount <= 3
合計実行時間:64.497ミリ秒

@Andreiy(+ ORDER BY
&@ Erwin1- CASE(どちらもほぼ同じパフォーマンス)
合計実行時間:39.105ミリ秒

@ Erwin2-crosstab()
合計実行時間:17.644ミリ秒

わずか20行で、大部分が比例している(ただし無関係な)結果。@wildplasserのCTEだけが、より多くのオーバーヘッドとスパイクを持っています。

一握り以上の行で、crosstab()すぐにリードします。@Andreiyのクエリは、私の簡略化されたバージョンとほぼ同じように実行されます。外部SELECTmin()、、、 )の集計関数はmax()sum()測定可能な違いを生じません(グループごとに2行のみ)。

すべてが期待どおりで、驚くことではありません。セットアップを行って、@homeで試してください。

于 2012-10-31T23:40:41.843 に答える
4

@bluefeetの提案の代替案は次のとおりです。これは多少似ていますが、結合を回避します(代わりに、上位レベルのグループ化がすでにグループ化された結果セットに適用されます)。

SELECT
  year,
  MAX(CASE animal WHEN 'kittens' THEN avg_price END) AS "kittens",
  MAX(CASE animal WHEN 'puppies' THEN avg_price END) AS "puppies"
FROM (
  SELECT
    animal,
    year,
    COUNT(*) AS cnt,
    AVG(Price) AS avg_price
  FROM tab_test
  GROUP BY
    animal,
    year
) s
WHERE cnt >= 3
GROUP BY
  year
;
于 2012-10-31T22:32:03.750 に答える
3

これはあなたが探しているものですか?

SELECT t1.year,
    AVG(CASE WHEN t1.animal = 'kittens' THEN t1.price END) AS "kittens",
    AVG(CASE WHEN t1.animal = 'puppies' THEN t1.price END) AS "puppies"
FROM tab_test t1
inner join 
(
  select animal, count(*) YearCount, year
  from tab_test
  group by animal, year
) t2
  on t1.animal = t2.animal 
  and t1.year = t2.year
where t2.YearCount >= 3
group by t1.year

SQL FiddlewithDemoを参照してください

于 2012-10-31T22:05:34.077 に答える
2
CREATE TABLE pussyriot(year INTEGER NOT NULL
        , animal varchar
        , price integer
        );

INSERT INTO pussyriot(year , animal , price ) VALUES
 (2000, 'kittens', 79)
, (2000, 'kittens', 93)
...
, (2007, 'puppies', 81)
, (2007, 'puppies', 38)
        ;

-- a self join is a poor man's pivot:
WITH cal AS ( -- generate calendar file
        SELECT generate_series(MIN(pr.year) , MAX(pr.year)) AS year
        FROM pussyriot pr
        )
, fur AS (
        SELECT distinct year, animal, AVG(price) AS price
        FROM pussyriot
        GROUP BY year, animal
        -- UPDATE: added next line
        HAVING COUNT(*) >= 3
        )
SELECT cal.year
        , pussy.price AS price_of_the_pussy
        , puppy.price AS price_of_the_puppy
FROM cal
LEFT JOIN fur pussy ON pussy.year=cal.year AND pussy.animal='kittens'
LEFT JOIN fur puppy ON puppy.year=cal.year AND puppy.animal='puppies'
        ;
于 2012-10-31T22:55:20.233 に答える