13

使用 PostgreSQL 9.0。

假设我有一个包含以下字段的表companyprofessionyear。我想返回一个包含独特公司和专业的结果,但基于数字序列聚合(到一个数组中很好)年份:

示例表:

+-----------------------------+
| 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
 Mozilla | 销售 | 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
 Mozilla | 销售 | 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}
 Mozilla | 销售 | {2002}
(4 行)

如果我没记错的话,这正是你想要的。

于 2011-11-04T20:04:22.493 回答
18

@a_horse_with_no_name 的答案很有价值,既可以作为正确的解决方案,也可以作为学习如何在 PostgreSQL 中使用不同类型的窗口函数的好材料,就像我在评论中已经说过的那样。

然而,我不禁感到,在这个答案中采取的方法对于像这样的问题来说有点太过分了。基本上,在您继续在数组中聚合年份之前,您需要一个额外的分组标准。你已经有了companyprofession,现在你只需要一些东西来区分属于不同序列的年份。

这正是上述答案所提供的,而这正是我认为可以以更简单的方式完成的。就是这样:

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 回答