13

PostgreSQL 9.0 を使用しています。

companyprofessionおよびのフィールドを含むテーブルがあるとしyearます。一意の会社と職業を含む結果を返したいのですが、数値シーケンスに基づいて年を集計します (配列に入れることは問題ありません)。

例の表:

+-----------------------------+
| company | profession | year |
+---------+------------+------+
| Google  | Programmer | 2000 |
| Google  | Sales      | 2000 |
| Google  | Sales      | 2001 |
| Google  | Sales      | 2002 |
| Google  | Sales      | 2004 |
| Mozilla | Sales      | 2002 |
+-----------------------------+

次のような行を出力するクエリに興味があります。

+-----------------------------------------+
| company | profession | year             |
+---------+------------+------------------+
| Google  | Programmer | [2000]           |
| Google  | Sales      | [2000,2001,2002] |
| Google  | Sales      | [2004]           |
| Mozilla | Sales      | [2002]           |
+-----------------------------------------+

重要な特徴は、連続した年のみがグループ化されることです。

4

3 に答える 3

23

連続していない値を特定することは常に少しトリッキーで、いくつかのネストされたサブクエリが必要です (少なくとも、私はより良い解決策を見つけることができません)。

最初のステップは、年の非連続値を特定することです。

ステップ 1) 連続していない値を特定する

select company, 
       profession,
       year,
       case 
          when row_number() over (partition by company, profession order by year) = 1 or 
               year - lag(year,1,year) over (partition by company, profession order by year) > 1 then 1
          else 0
       end as group_cnt
from qualification

これにより、次の結果が返されます。

会社 | 職業 | 年 | group_cnt
------+------------+------+-----------
 グーグル| プログラマー | 2000年 | 1
 グーグル| 販売 | 2000年 | 1
 グーグル| 販売 | 2001年 | 0
 グーグル| 販売 | 2002年 | 0
 グーグル| 販売 | 2004年 | 1
 モジラ | モジラ | 販売 | 2002年 | 1

group_cnt 値を使用して、連続した年を持つ各グループの「グループ ID」を作成できます。

ステップ 2) グループ ID を定義する

select company,
   profession,
   year,
   sum(group_cnt) over (order by company, profession, year) as group_nr
from ( 
select company, 
       profession,
       year,
       case 
          when row_number() over (partition by company, profession order by year) = 1 or 
               year - lag(year,1,year) over (partition by company, profession order by year) > 1 then 1
          else 0
       end as group_cnt
from qualification
) t1

これにより、次の結果が返されます。

会社 | 職業 | 年 | group_nr
------+------------+------+----------
 グーグル| プログラマー | 2000年 | 1
 グーグル| 販売 | 2000年 | 2
 グーグル| 販売 | 2001年 | 2
 グーグル| 販売 | 2002年 | 2
 グーグル| 販売 | 2004年 | 3
 モジラ | モジラ | 販売 | 2002年 | 4
(6列)

ご覧のとおり、各「グループ」には独自の group_nr があり、これを最終的に使用して、さらに別の派生テーブルを追加して集計できます。

ステップ 3) 最終クエリ

select company,
       profession,
       array_agg(year) as years
from (
  select company,
       profession,
       year,
       sum(group_cnt) over (order by company, profession, year) as group_nr
  from ( 
    select company, 
           profession,
           year,
           case 
              when row_number() over (partition by company, profession order by year) = 1 or 
                   year - lag(year,1,year) over (partition by company, profession order by year) > 1 then 1
              else 0
           end as group_cnt
    from qualification
  ) t1
) t2
group by company, profession, group_nr
order by company, profession, group_nr

これにより、次の結果が返されます。

会社 | 職業 | 年
------+------------+------------------
 グーグル| プログラマー | {2000}
 グーグル| 販売 | {2000,2001,2002}
 グーグル| 販売 | {2004}
 モジラ | モジラ | 販売 | {2002}
(4行)

私が間違っていなければ、それはまさにあなたが望んでいたものです。

于 2011-11-04T20:04:22.493 に答える
18

@a_horse_with_no_name's answerには、正しい解決策として、またコメントで既に述べたように、PostgreSQL でさまざまな種類のウィンドウ関数を使用する方法を学習するための優れた資料として、多くの価値があります。

それでも、その回答で採用されているアプローチは、このような問題に対して少しやりすぎだと感じずにはいられません。基本的に、配列で年を集計する前に、グループ化の追加基準が必要です。すでに と を取得companyしていprofessionます。必要なのは、異なるシーケンスに属する年を区別するためのものだけです。

それはまさに上記の答えが提供するものであり、それはまさに私がより簡単な方法でできると思うことです. 方法は次のとおりです。

WITH MarkedForGrouping AS (
  SELECT
    company,
    profession,
    year,
    year - ROW_NUMBER() OVER (
      PARTITION BY company, profession
      ORDER BY year
    ) AS seqID
  FROM atable
)
SELECT
  company,
  profession,
  array_agg(year) AS years
FROM MarkedForGrouping
GROUP BY
  company,
  profession,
  seqID
于 2011-11-05T00:37:14.500 に答える
4

PL/pgSQL による手続き型ソリューション

この問題は、集計/ウィンドウ関数を使用したプレーン SQL ではかなり扱いにくいものです。ループは通常、プレーン SQL を使用したセットベースのソリューションよりも遅くなりますが、plpgsql を使用した手続き型ソリューションは、テーブルに対する単一の順次スキャン(ループの暗黙のカーソル) で間に合わせることができ、この特定のケースでは大幅に高速にFORなるはずです。

テスト テーブル:

CREATE TEMP TABLE tbl (company text, profession text, year int);
INSERT INTO tbl VALUES
 ('Google',  'Programmer', 2000)
,('Google',  'Sales',      2000)
,('Google',  'Sales',      2001)
,('Google',  'Sales',      2002)
,('Google',  'Sales',      2004)
,('Mozilla', 'Sales',      2002);

関数:

CREATE OR REPLACE FUNCTION f_periods()
  RETURNS TABLE (company text, profession text, years int[]) AS
$func$
DECLARE
   r  tbl; -- use table type as row variable
   r0 tbl;
BEGIN

FOR r IN
   SELECT * FROM tbl t ORDER BY t.company, t.profession, t.year
LOOP
   IF ( r.company,  r.profession,  r.year)
   <> (r0.company, r0.profession, r0.year + 1) THEN -- not true for first row

      RETURN QUERY
      SELECT r0.company, r0.profession, years; -- output row

      years := ARRAY[r.year];     -- start new array
   ELSE
      years := years || r.year;   -- add to array - year can be NULL, too
   END IF;

   r0 := r;                       -- remember last row
END LOOP;

RETURN QUERY                      -- output last iteration
SELECT r0.company, r0.profession, years;

END
$func$ LANGUAGE plpgsql;

電話:

SELECT * FROM f_periods();

要求された結果を生成します。

于 2011-11-04T19:24:17.500 に答える