在Postgres 9.2中,我试图对连续的行进行分组。它们必须至少具有一个非空匹配,并且没有非空不匹配。如果所有值均为null,则不要分组在一起。可以将Null视为通配符。
这是预期的结果:
2、4、5和6归为一组,因为2和4共享column1
(3为全空并跳过),4和5共享列3,4和6共享column2
和{{1 }}。
答案 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')
答案 1 :(得分:1)
我想到了另一个想法,该想法可以更动态地涉及列数。这只是一个想法,我真的不知道它是否有效。但这值得一试。
也许您可以旋转表格,使您的栏成为您的行:
https://www.postgresql.org/docs/9.1/static/tablefunc.html
之后,应该很容易进行分组,或者可以使用窗口功能对列内容进行分区。
只是一个草图,可以稍后再试。
答案 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
。
相关: