GROUP BY列等于或为NULL的连续行

时间:2018-08-09 08:12:12

标签: sql postgresql aggregate

在Postgres 9.2中,我试图对连续的行进行分组。它们必须至少具有一个非空匹配,并且没有非空不匹配。如果所有值均为null,则不要分组在一起。可以将Null视为通配符。

table data

这是预期的结果:
2、4、5和6归为一组,因为2和4共享column1(3为全空并跳过),4和5共享列3,4和6共享column2和{{1 }}。

desired result

Here's the SQL fiddle.

3 个答案:

答案 0 :(得分:3)

对于固定的三列,这可能是一种解决方案。

http://sqlfiddle.com/#!17/45dc7/137

免责声明: 如果不同列中的值相同,则此方法将无效。例如。一行(42, NULL, "A42", NULL)和一行(23, "A42", NULL, NULL)将以不想要的结果结尾。解决此问题的方法是将具有唯一分隔符的列标识符连接到字符串,并在通过字符串拆分操作后将其删除。

WITH test_table as (
    SELECT *, 
    array_remove(ARRAY[column1,column2,column3], null) as arr, -- A
    cardinality(array_remove(ARRAY[column1,column2,column3], null))as arr_len
FROM test_table )

SELECT 
    s.array_agg as aggregates,                                 -- G
    MAX(tt.column1) as column1, 
    MAX(tt.column2) as column2, 
    MAX(tt.column3) as column3
FROM (

    SELECT array_agg(id) FROM                                  -- E
        (SELECT DISTINCT ON (t1.id)
        t1.id, CASE WHEN t1.arr_len >= t2.arr_len THEN t1.arr ELSE t2.arr END as arr  -- C
        FROM 
        test_table as t1 
        JOIN                                                   -- B
        test_table as t2
        ON t1.arr @> t2.arr AND COALESCE(t2.column1, t2.column2, t2.column3) IS NOT NULL
        OR t2.arr @> t1.arr AND COALESCE(t1.column1, t1.column2, t1.column3) IS NOT NULL

        ORDER BY t1.id, GREATEST(t1.arr_len, t2.arr_len) DESC -- D
        ) s
    GROUP BY arr  

    UNION

    SELECT 
        ARRAY[id] 
    FROM test_table tt 
    WHERE COALESCE(tt.column1, tt.column2, tt.column3) IS NULL) s -- F

JOIN test_table tt ON tt.id = ANY (s.array_agg)
GROUP BY s.array_agg

A:汇总列值并除去NULL值。原因是我稍后会检查不适用于NULL的子集。这是您应该添加上面的免责声明中提到的列标识符的地方。

B:CROSS JOIN反对自己的桌子。在这里,我正在检查一个列聚合是否是另一个的子集。仅包含NULL值的行将被忽略(这是COALESCE函数)

C:从第一张表或第二张表中获取具有最大长度的列数组。这取决于其ID。

D:使用ORDER BY最长的数组和DISTINCT可以确保每个ID仅给出最长的数组

E:现在有许多具有相同列数组集的id。数组集用于汇总ID。这些ID放在这里。

F:添加所有NULL行。

G:所有列的倒数JOIN。这些行是(E)的id聚合的一部分。之后,MAX值将按列分组。

编辑: PostgreSQL 9.3的提琴手(用array_length代替cardinality函数),并添加了测试用例(8, 'A2', 'A3', 'A8')

http://sqlfiddle.com/#!15/8800d/2

答案 1 :(得分:1)

我想到了另一个想法,该想法可以更动态地涉及列数。这只是一个想法,我真的不知道它是否有效。但这值得一试。

也许您可以旋转表格,使您的栏成为您的行:

https://www.postgresql.org/docs/9.1/static/tablefunc.html

http://www.vertabelo.com/blog/technical-articles/creating-pivot-tables-in-postgresql-using-the-crosstab-function

之后,应该很容易进行分组,或者可以使用窗口功能对列内容进行分区。

只是一个草图,可以稍后再试。

答案 2 :(得分:1)

SQL是一种功能强大的声明性语言(4GL)-大部分都是。声明式(基于集合)解决方案通常最快。

但是根据定义,某些工作负载非常“程序化”,难以实现。这是一种罕见的情况:一种过程解决方案可以通过一次顺序扫描完成,并且应该比同等的​​纯SQL解决方案快很多 :

>
CREATE OR REPLACE FUNCTION f_my_grouping()
  RETURNS SETOF int[] AS
$func$
DECLARE
   r  tbl; -- use table type as row variable
   r0 tbl;
   ids int[];
BEGIN
   FOR r IN
      SELECT * FROM tbl t ORDER BY t.id
   LOOP
      IF (r.column1, r.column2, r.column3) IS NULL THEN     -- all NULL
         RETURN NEXT ARRAY[r.id];  -- return and ignore

      ELSIF (r.column1 <> r0.column1 OR                     -- continue
             r.column2 <> r0.column2 OR
             r.column3 <> r0.column3) IS NOT TRUE  -- no mismatch
        AND (r.column1 =  r0.column1 OR
             r.column2 =  r0.column2 OR
             r.column3 =  r0.column3) THEN         -- 1+ match

         ids := ids || r.id;     -- add to array

         IF r0.column1 IS NULL AND r.column1 IS NOT NULL OR
            r0.column2 IS NULL AND r.column2 IS NOT NULL OR
            r0.column3 IS NULL AND r.column3 IS NOT NULL THEN

            SELECT INTO r0.column1, r0.column2, r0.column3 
                   COALESCE(r0.column1, r.column1)
                 , COALESCE(r0.column2, r.column2)
                 , COALESCE(r0.column3, r.column3);
         END IF;

      ELSE                                                  -- new grp
         IF r0 IS NULL THEN      -- skip 1st row
            -- do nothing
         ELSE
            RETURN NEXT ids;
         END IF;
         ids := ARRAY[r.id];     -- start new array
         r0  := r;               -- remember last row
      END IF;
   END LOOP;

   IF ids IS NOT NULL THEN  -- all NULL
      RETURN NEXT ids;  -- output last iteration
   END IF;
END
$func$  LANGUAGE plpgsql;

致电:

SELECT * FROM f_my_grouping();

如果需要排序的输出:

SELECT * FROM f_my_grouping() ORDER BY 1;

db<>fiddle在这里(运行Postgres 9.4)

比较效果与EXPLAIN ANALYZE

相关: