如何计算值不同的列?

时间:2014-08-18 08:21:39

标签: sql postgresql plpgsql

我有一张大桌子,我需要检查类似的行。我不需要所有列值都相同,只是类似。行不能“远”(由对其他表的查询确定),没有值可能太不同(我已经完成了对这些条件的查询),并且大多数其他值必须相同。我必须期待一些歧义,所以一个或两个不同的值不应该打破“相似性”(好吧,我可以通过只接受“完全相等”的行来获得更好的性能,但这种简化可能会导致错误;我会这样做选项)。

我要解决这个问题的方法是通过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个不同的值 - 超出最多两个差异的限制)。

2 个答案:

答案 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(建立在你的身上)。