没有pl / sql

时间:2016-06-29 15:42:41

标签: sql postgresql group-by window-functions

我有这个表(简短示例),有两列

1 a
2 a
3 a3
4 a
5 a
6 a6
7 a
8 a8
9 a

我希望将它们分组/分成由前导“a”分隔的组,理想情况是添加这样的另一列,这样我就可以轻松地解决这些组。

1 a  0
2 a  0
3 a3 3
4 a  3
5 a  3
6 a6 6
7 a  6
8 a8 8
9 a  8

问题是表的设置是动态的,所以我不能使用静态延迟或引导函数,任何想法如何在postgres版本9.5中没有pl / sql这样做

4 个答案:

答案 0 :(得分:2)

假设前导部分是单个字符。因此,表达式right(data, -1)用于提取组名。适应您的实际前缀。

该解决方案使用两个window functions,它不能嵌套。所以我们需要子查询或CTE。

SELECT id, data
     , COALESCE(first_value(grp) OVER (PARTITION BY grp_nr ORDER BY id), '0') AS grp
FROM (
   SELECT *, NULLIF(right(data, -1), '') AS grp
        , count(NULLIF(right(data, -1), '')) OVER (ORDER BY id) AS grp_nr
   FROM   tbl
   ) sub;

准确地生成您想要的结果。

NULLIF(right(data, -1), '')获取有效的群组名称,如果没有,则为NULL

count()仅计算非空值,因此我们为子查询中的每个新组获得更高的计数。

在外部查询中,我们将每个grp的{​​{1}}值作为组名称,默认为“0”,grp_nr为第一个没有名称的组(具有{到目前为止,{1}}为组名。

我们也可以使用COALESCENULL作为外部窗口函数,因为每个分区只有一个非空值。 min()可能是最便宜的,因为行已经排序了。

请注意,群组名称max()是数据类型first_value()。如果那些是干净的(可靠的)整数,你可能想要转换为整数。

答案 1 :(得分:1)

这可以通过将包含a的行设置为特定值而将所有其他行设置为不同的值来实现。然后使用累积和来获得所需的行数。当遇到val列中的新值时,组号将设置为下一个数字,并且所有带有a的前进行将具有与之前相同的组号,并且这将继续。

我认为你需要为每个组提供一个不同的数字,这个数字并不重要。

select id, val, sum(ex) over(order by id) cm_sum
from (select t.*
      ,case when val = 'a' then 0 else 1 end ex
      from t) x

上述查询与所讨论数据的结果将是

id  val cm_sum
--------------
1   a   0
2   a   0
3   a3  1
4   a   1
5   a   1
6   a6  2
7   a   2
8   a8  3
9   a   3

答案 2 :(得分:0)

使用给定数据,您可以使用累积最大值:

select . . .,
       coalesce(max(substr(col2, 2)) over (order by col1), 0)

如果你没有严格要求最大值,那么它会变得更加困难。 ANSI解决方案是使用IGNORE NULL上的LAG()选项。然而,Postgres(尚未)支持这一点。另一种选择是:

select . . ., coalesce(substr(reft.col2, 2), 0)
from (select . . .,
             max(case when col2 like 'a_%' then col1 end) over (order by col1) as ref_col1
      from t
     ) tt join
     t reft
     on tt.ref_col1 = reft.col1

答案 3 :(得分:0)

你也可以试试这个:

 with mytable as (select split_part(t,' ',1)::integer id,split_part(t,' ',2) myvalue 
       from (select unnest(string_to_array($$1 a;2 a;3 a3;4 a;5 a;6 a6;7 a;8 a8;9 a$$,
    ';'))t) a)

  select id,myvalue,myresult from mytable join (
     select COALESCE(NULLIF(substr(myvalue,2),''),'0') myresult,idmin id_down
            ,COALESCE(lead(idmin) over (order by myvalue),999999999999) id_up 
   from (
     select myvalue,min(id) idmin from mytable group by 1
    ) a) b 
  on id between id_down and id_up-1