GROUP BY和聚合的顺序数值
使用PostgreSQL 9.0。
比方说,我有一张包含company
, profession
和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] | +-----------------------------------------+
其基本特征是只有连续的几年才能组合在一起。
对于@ a_horse_with_no_name的答案有很大的价值,作为一个正确的解决scheme,就像我已经在评论中所说的那样,作为学习如何在PostgreSQL中使用不同types的窗口函数的一个很好的材料。
然而,我不禁觉得在这个问题上所采取的方法对于像这样的问题来说有点太过分了。 基本上,你需要的是一个额外的分组标准,然后再进行数组聚合。 你已经有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
识别非连续的值总是有点棘手,涉及到几个嵌套的子查询(至less我不能提出更好的解决scheme)。
第一步是确定年份的非连续值:
步骤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 --------- + ------------ + ------ + ----------- Google | 程序员| 2000 | 1 Google | 销售| 2000 | 1 Google | 销售| 2001 | 0 Google | 销售| 2002 | 0 Google | 销售| 2004 | 1 Mozilla | 销售| 2002 | 1
现在使用group_cnt值,我们可以为连续年份的每个组创build“组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 --------- + ------------ + ------ + ---------- Google | 程序员| 2000 | 1 Google | 销售| 2000 | 2 Google | 销售| 2001 | 2 Google | 销售| 2002 | 2 Google | 销售| 2004 | 3 Mozilla | 销售| 2002 | 4 (6行)
正如你所看到的,每个“group”都有它自己的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
这将返回以下结果:
公司| 职业| 年份 --------- + ------------ + ------------------ Google | 程序员| {} 2000 Google | 销售| {} 2000,2001,2002 Google | 销售| {} 2004年 Mozilla | 销售| {} 2002 (4行)
如果我没有弄错,那正是你想要的。
使用PL / pgSQL的程序解决scheme
对于具有聚合/窗口函数的普通SQL,这个问题相当难以处理。 虽然循环通常比使用普通SQL的基于集合的解决scheme慢,但是使用plpgsql的过程解决scheme可以对表( FOR
循环的隐式游标)进行单次顺序扫描 ,并且在这种特定情况下应该快得多 :
testing表:
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);
function:
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();
产生请求的结果。