带空值的SQL复制

时间:2018-10-25 00:30:18

标签: sql database postgresql algorithm

出于与从客户那里获取混乱数据有关的一些疯狂的业务原因,我遇到以下问题;

1)我有一张桌子,上面有6个半唯一标识符和一个自动递增的唯一ID。该表具有更多字段。但是,这些对于这次讨论并不重要。字段所保存的数据也不是确切类型。

2)我想获得参与至少一个重复关系的所有行的唯一ID的列表。 (识别所有表示重复的行对没有任何附加价值。但是,如果解决方案提供了该功能,则检索重复行的集合是相当琐碎的。所以,这也很好)

3)重复项定义为;

3a)对于这6个字段,记录A必须与记录B匹配,或者其中一个必须为空

3b)至少一个字段必须完全匹配(即都不为空)

4)所有可能感兴趣的重复字段都是字符串,而不是空字符串。许多行中至少有一个感兴趣的字段为空,但是(至少假设我们的摄取逻辑正常工作)它们都不可以有超过3个为空。

5)精确的字符串内容匹配就可以了。我们不需要任何基于正则表达式,不区分大小写的匹配项。

6)表中的实际重复项很少见。

7)我们正在运行PostgreSQL9。可以使用特定于数据库的功能。

8)该表有500,000行。因此,下面提供的我刚开始使用的幼稚查询花费了太长的时间才变得可行。据推测,它主要在指数时间内运行。理想情况下,结果应在不到一分钟的时间内返回,并在中端服务器上运行。

SELECT a.id
FROM myTable a
JOIN myTable b ON a.id < b.id
AND (a.field1 = b.field1 OR a.field1 IS NULL OR b.field1 IS NULL )
AND (a.field2 = b.field2 OR a.field2 IS NULL OR b.field2 IS NULL)
....
WHERE 
a.field1 = b.field1 OR a.field2 = b.field2 ...

9)我也研究了使用“分组依据”。但是,如果“分组依据”中的一个分组列中包含空值,而另一列中包含一个值,则“分组依据”不会认为两行相等。除非有一种方法可以实现该行为,否则group by不能满足我的“等于或至少一个为null”的逻辑。

10)可以假定每行可能出现的一组值与其他列不重叠。即,除了null以外,您不会期望字段1的值出现在字段2的任何行中。

更新:很抱歉缺少信息。我将尽可能提供表模式的近似值。不幸的是,该项目处于防御状态,甚至表的字段名称也可能泄露有关操作安全性的信息。

CREATE TABLE a (
id serial NOT NULL PRIMARY KEY,
f1 character varying,
f2 character varying,
f3 character varying,
f4 character varying,
f5 character varying,
f6 character varying,
...Other columns that aren't really relevant
)

CREATE INDEX f1_idx
  ON public.a
  USING btree
  (f1 COLLATE pg_catalog."default");

...Same index for the other 5 fields.

为便于参考,我将复制Lorenze Albe的问题并在此处回答。

如果有三行      (1、2、3、4,NULL,6)

(1, 2, 3, NULL, 5, NULL)

(1, 2, 3, 4, 7, NULL)

哪些重复?

(1, 2, 3, NULL, 5, NULL)

(1, 2, 3, 4, 7, NULL)

不是重复项,因为字段5在两个字段中都不为空并且不相等。其他两个重复。

为清楚起见,我将再举几个例子。 (仅出于完整性考虑,我将行示例作为字符串提供。但是,就像我说的那样,它们的字符串形式并不十分重要,因为我们需要精确的字符串匹配。

 ("1", "2", "3", "4", NULL, NULL)

AND

 ("1","2","3",NULL,"9",NULL)

是重复的,因为在至少一个字段中第4、5和6列为空,而其他所有字段都相等。

("1", "2", "3", "4", NULL, "6")

AND

("1","2","3",NULL,"9","7")

不是重复项,因为字段6不同且都不为空

还有两个更典型的实际数据示例;

(NULL, NULL, "3",   NULL, "5",  "6")

("1", "2",    NULL, "4",  NULL, "6")

是重复的,因为所有字段都不同,至少一侧为空。

(NULL, NULL, "3",   NULL, "5",  "6")

("1", "2",    NULL, "4",  NULL, "6")

是的,这确实意味着

(NULL, NULL, NULL, "4", "5", "6")

("1", "2", "3", NULL, NULL, NULL)
如果不是为了至少一个字段完全匹配的要求,

将是重复的。哪些字段为null,哪些字段不是几乎是随机的。我们需要数据提供者提供的所有信息是,必须至少提供6个字段中的2个。

另一次更新:我已经更新了第2点,以反映我希望所有参与至少一个重复对的行的事实。因此,对于三行     (1、2、3、4,NULL,6)

(1, 2, 3, NULL, 5, NULL)

(1, 2, 3, 4, 7, NULL)

将返回所有三个,因为即使第2行和第3行不会被视为彼此重复,第1,2行对也是重复的,而第1,3对是重复的,因此所有这三个对都属于重复关系,因此将返回。

1 个答案:

答案 0 :(得分:0)

使用count() over(partition by ...),然后针对大于1的任何计数过滤结果。

CREATE TABLE mytable(
   ID   INTEGER  NOT NULL PRIMARY KEY 
  ,col1 VARCHAR(2) NOT NULL
  ,col2 VARCHAR(2)
  ,col3 VARCHAR(2) NOT NULL
  ,col4 VARCHAR(2)
  ,col5 VARCHAR(2)
  ,col6 VARCHAR(2)
);
INSERT INTO mytable(ID,col1,col2,col3,col4,col5,col6) VALUES (1001,'a1','b1','c1','d1','e1','f1');
INSERT INTO mytable(ID,col1,col2,col3,col4,col5,col6) VALUES (1002,'a1',NULL,'c1','d1','e1','f1');
INSERT INTO mytable(ID,col1,col2,col3,col4,col5,col6) VALUES (1003,'a1','b1','c1','d1','e1','f1');
INSERT INTO mytable(ID,col1,col2,col3,col4,col5,col6) VALUES (1004,'b1','c1','d1','e1','f1',NULL);
INSERT INTO mytable(ID,col1,col2,col3,col4,col5,col6) VALUES (1005,'a1','b1','c1',NULL,'e1','f1');
INSERT INTO mytable(ID,col1,col2,col3,col4,col5,col6) VALUES (1006,'b1','c1','d1','e1','f1',NULL);
INSERT INTO mytable(ID,col1,col2,col3,col4,col5,col6) VALUES (1007,'f1',NULL,'b1','c1','d1','e1');
INSERT INTO mytable(ID,col1,col2,col3,col4,col5,col6) VALUES (1008,'b1','c1','d1','e1','f1',NULL);
INSERT INTO mytable(ID,col1,col2,col3,col4,col5,col6) VALUES (1009,'c1','d1','e1','f1',NULL,NULL);
INSERT INTO mytable(ID,col1,col2,col3,col4,col5,col6) VALUES (1010,'c1','d1','e1','f1',NULL,'a1');
INSERT INTO mytable(ID,col1,col2,col3,col4,col5,col6) VALUES (1011,'a1','b1','c1','d1','e1','f1');
select
       *
     , count(*) over(partition by
                       coalesce(col1,'NULL')
                     , coalesce(col2,'NULL')
                     , coalesce(col3,'NULL')
                     , coalesce(col4,'NULL')
                     , coalesce(col5,'NULL')
                     , coalesce(col6,'NULL')
                     ) cv
from mytable
  id | col1 | col2 | col3 | col4 | col5 | col6 | cv
---: | :--- | :--- | :--- | :--- | :--- | :--- | -:
1001 | a1   | b1   | c1   | d1   | e1   | f1   |  3
1003 | a1   | b1   | c1   | d1   | e1   | f1   |  3
1011 | a1   | b1   | c1   | d1   | e1   | f1   |  3
1005 | a1   | b1   | c1   | null | e1   | f1   |  1
1002 | a1   | null | c1   | d1   | e1   | f1   |  1
1008 | b1   | c1   | d1   | e1   | f1   | null |  3
1004 | b1   | c1   | d1   | e1   | f1   | null |  3
1006 | b1   | c1   | d1   | e1   | f1   | null |  3
1010 | c1   | d1   | e1   | f1   | null | a1   |  1
1009 | c1   | d1   | e1   | f1   | null | null |  1
1007 | f1   | null | b1   | c1   | d1   | e1   |  1

使用上述方法作为子查询,然后使用where cv > 1在这6列中查找所有具有“重复项”的行。

db <>提琴here


请注意可以使用一些示例数据的功能。确实,您有责任提供带有问题的样本数据(因为您已经拥有该数据了)。不要试图单独用言语来解释,而要使用数据来说明“现状”和“将要存在”,您会发现自己的问题更容易准备且回答更快。参见Why should I provide a MCVE