我有一张大桌子,我需要检查类似的行。我不需要所有列值都相同,只是类似。行不能“远”(由对其他表的查询确定),没有值可能太不同(我已经完成了对这些条件的查询),并且大多数其他值必须相同。我必须期待一些歧义,所以一个或两个不同的值不应该打破“相似性”(好吧,我可以通过只接受“完全相等”的行来获得更好的性能,但这种简化可能会导致错误;我会这样做选项)。
我要解决这个问题的方法是通过PL / pgSQL:使FOR LOOP迭代前面的查询结果。对于每一列,我都有一个IF测试它是否有所不同;如果是的话,我会增加差价计数器并继续。在每个循环结束时,我将该值与阈值进行比较,看看我是否应该将该行保持为“相似”。
与纯SQL查询或涉及某些PL / pgSQL函数的SQL查询相比,这种PL / pgSQL重的方法似乎很慢。如果我知道哪些行应该不同,那么测试具有除X等效行之外的所有行的行将很容易,但差异可能发生在大约40行中的任何行。 有没有办法通过单个查询解决这个问题?如果没有,有没有比检查所有行更快的方法?
编辑:我提到了一张表,实际上它是一组由1:1关系链接的六个表。我不想解释什么是什么,那是a different question。从一张桌子到我的情况进行推断对我来说很容易。所以我简化了它(但没有过分简化它 - 它应该展示我在那里遇到的所有困难),并做了一个展示我需要的例子。 Null和其他任何东西应该算作“不同”。不需要对脚本进行全部测试 - 我只需要找出是否有可能以比我想象的更高效的方式进行。重点是我不需要计算行(像往常一样),但是列。
EDIT2:previous fiddle - 这不是那么短暂,所以我只是出于存档原因而放在这里。
EDIT3:简化示例here - 只是NOT NULL整数,省略了预处理。当前的数据状态:
select * from foo;
id | bar1 | bar2 | bar3 | bar4 | bar5
----+------+------+------+------+------
1 | 4 | 2 | 3 | 4 | 11
2 | 4 | 2 | 4 | 3 | 11
3 | 6 | 3 | 3 | 5 | 13
当我运行select similar_records( 1 );
时,我应该只获得第2行(2列具有不同的值;这在限制范围内),而不是3(4个不同的值 - 超出最多两个差异的限制)。
答案 0 :(得分:1)
代替将每行与所有其他行进行比较的循环,进行自我加入
select f0.id, f1.id
from foo f0 inner join foo f1 on f0.id < f1.id
where
f0.bar1 = f1.bar1 and f0.bar2 = f1.bar2
and
@(f0.bar3 - f1.bar3) <= 1
and
f0.bar4 = f1.bar4 and f0.bar5 = f1.bar5
or
f0.bar4 = f1.bar5 and f0.bar5 = f1.bar4
and
@(f0.bar6 - f1.bar6) <= 2
and
f0.bar7 is not null and f1.bar7 is not null and @(f0.bar7 - f1.bar7) <= 5
or
f0.bar7 is null and f1.bar7 <= 3
or
f1.bar7 is null and f0.bar7 <= 3
and
f0.bar8 = f1.bar8
and
@(f0.bar11 - f1.bar11) <= 5
;
id | id
----+----
1 | 4
1 | 5
4 | 5
(3 rows)
select * from foo;
id | bar1 | bar2 | bar3 | bar4 | bar5 | bar6 | bar7 | bar8 | bar9 | bar10 | bar11
----+------+------+------+------+------+------+------+------+------+-------+-------
1 | abc | 4 | 2 | 3 | 4 | 11 | 7 | t | t | f | 42.1
2 | abc | 5 | 1 | 6 | 2 | 8 | 39 | t | t | t | 19.6
3 | xyz | 4 | 2 | 3 | 5 | 14 | 82 | t | f | | 95
4 | abc | 4 | 2 | 4 | 3 | 11 | 7 | t | t | f | 42.1
5 | abc | 4 | 2 | 3 | 4 | 13 | 6 | t | t | | 37.7
您是否知道and
运算符的优先级高于or
?我问,因为你的函数中的where
子句看起来不像你想要的那样。我的意思是在你的表达式中,f0.bar7 is null and f1.bar7 <= 3
足以true
包含该对
答案 1 :(得分:1)
要查找仅在给定的最大列数上有所不同的行:
WITH cte AS (
SELECT id
,unnest(ARRAY['bar1', 'bar2', 'bar3', 'bar4', 'bar5']) AS col -- more
,unnest(ARRAY[bar1::text, bar2::text, bar3::text
, bar4::text, bar5::text]) AS val -- more
FROM foo
)
SELECT b.id, count(a.val <> b.val OR NULL) AS cols_different
FROM (SELECT * FROM cte WHERE id = 1) a
JOIN (SELECT * FROM cte WHERE id <> 1) b USING (col)
GROUP BY b.id
HAVING count(a.val <> b.val OR NULL) < 3 -- max. diffs allowed
ORDER BY 2;
我忽略了你问题中所有其他令人分心的细节。
使用5列进行演示。根据需要添加更多。
如果列可以是NULL
,您可以使用IS DISTINCT FROM
代替<>
。
这是使用一些非常规的,但非常方便的并行unnest()
。两个数组必须具有相同数量的元素才能工作。详细说明:
SQL Fiddle(建立在你的身上)。