假设我有一个表mytable
:
a b c d
------------------------
1 2 3 4
1 1 1 null
1 2 3 4
1 null null null
1 2 null null
1 null 1 null
null null null null
现在这个表的第一行和第三行是完全重复的。但是,我们还可以将第五行视为复制第一行中包含的信息,因为1 2 null null
只是1 2 3 4
的副本,但缺少某些数据。假设1 2 null null
由 1 2 3 4
覆盖。
“覆盖”是类似<=
的关系,而“精确复制”是类似==
的关系。在上表中,我们还得到第六行被第二行覆盖,第四行被除了最后一行以外的所有其他行覆盖,最后一行被所有其他行覆盖,第一行和第三行是相互遮盖。
现在我想使用这种覆盖概念对mytable
进行重复数据删除。换句话说,我想要“最小的封面”。这意味着每当row1&lt; = row2时,应从结果中删除row1。在这种情况下,结果是
a b c d
------------------------
1 2 3 4
1 1 1 null
这与SELECT DISTINCT
类似,但具有增强的空值处理行为。
更正式地说,我们可以将deduplicate(table)
定义为table
行的子集,以便:
table
的每一行 r ,deduplicate(table)
存在一行 c ,以便 r &lt; ; = c 和deduplicate(table)
中的任意两个单独行,则 c1 &lt; = c2 < / em> 不持有。或算法:
def deduplicate(table):
outcome = set()
for nextRow in table:
if any(nextRow <= o for o in outcome):
continue
else:
for possiblyNowADuplicate in outcome:
if possiblyNowADuplicate <= nextRow:
# it is now a duplicate
outcome.remove(possiblyNowADuplicate)
outcome.add(nextRow)
return outcome
如何在SQL中执行此操作?
(我在Presto工作,据说它实现了现代ANSI SQL;而且,我正在使用的表有更多的列和比mytable
更多的行,所以解决方案必须合理地扩展,代码复杂度(理想情况下不应要求列数为O(n ^ 2)!),以及执行时间。)
编辑:根据@ toonice的回复,我进行了以下改进:
进一步思考,如果查询代码长度为列数O(1)(可能不包括要在子表选择中操作的列的单个显式命名,则为可维护性)。对于group by和order by中的每一列都有一个复杂的布尔条件有点多。我必须编写一个python脚本来生成我的SQL查询。但是,这可能是不可避免的。
我正在运行至少数百万行。我不能在O(n ^ 2)时间内做到这一点。所以:
答案 0 :(得分:2)
请尝试以下方法......
SELECT DISTINCT leftTable.a,
leftTable.b,
leftTable.c,
leftTable.d
FROM tblTable AS leftTable
JOIN tblTable AS rightTable ON ( ( leftTable.a = rightTable.a OR
rightTable.a IS NULL ) AND
( leftTable.b = rightTable.b OR
rightTable.b IS NULL ) AND
( leftTable.c = rightTable.c OR
rightTable.c IS NULL ) AND
( leftTable.d = rightTable.d OR
rightTable.d IS NULL ) )
GROUP BY rightTable.a,
rightTable.b,
rightTable.c,
rightTable.d
ORDER BY ISNULL( leftTable.a ),
leftTable.a DESC,
ISNULL( leftTable.b ),
leftTable.b DESC,
ISNULL( leftTable.c ),
leftTable.c DESC,
ISNULL( leftTable.d ),
leftTable.d DESC;
此声明首先在INNER JOIN
的两个副本上执行tblTable
,我已经为leftTable
和rightTable
提供了别名。此联接会将rightTable
中每条记录的副本附加到leftTable
中leftTable
的记录涵盖来自rightTable
}的每条记录p>
然后对结果数据集进行分组,以消除leftTable
字段中的任何重复条目。
然后,分组数据集按降序排列,幸存的NULL
值放在非NULL
值之后。
扩展
如果您对从SELECT DISTINCT leftTable.*
选择所有字段感到满意,可以在第一行使用leftTable
- 我刚刚养成了列出字段的习惯。在这种情况下,两者都可以正常工作。如果你正在处理大量的字段,leftTable.*
可能会更加明确。我不确定这两种方法的执行时间是否存在差异。
我无法通过说WHERE
或类似内容来找到说明leftTable.* = rightTable.*
子句中所有字段相等的方法。我们的情况更加复杂,因为我们没有测试等价,而是覆盖。虽然我喜欢它,如果有一种方法来测试覆盖整体,我担心你只需要进行大量复制,粘贴和仔细更改字母,以便我的答案中的每个字段使用的测试适用于您的每个字段。
此外,我无法找到GROUP BY
所有字段的方式,无论是按照它们出现在表中的顺序还是以任何顺序排列,而不是指定要分组的每个字段。这也很好知道,但是现在我认为你必须从rightTable
指定每个字段。寻找荣耀,并注意复制,粘贴和编辑的危险!
如果您不关心是在第一个订购行还是在订购它的值为NULL
时最后一行,那么您可以通过从ISNULL()
条件中删除ORDER BY
条件来略微加快语句的速度。 ORDER BY
条款。
如果您根本不关心订购,可以通过完全删除ORDER BY NULL
子句来进一步加快声明。根据您语言的怪癖,您可能希望将其替换为空或GROUP BY
。某些语言(如MySQL)会自动按ORDER BY
子句中指定的字段排序,除非指定了ORDER BY NULL
子句。 SELECT DISTINCT leftTable.userid,
leftTable.a,
leftTable.b,
leftTable.c,
leftTable.d
FROM tblTable AS leftTable
JOIN tblTable AS rightTable ON ( leftTable.userid = rightTable.userid AND
( leftTable.a = rightTable.a OR
rightTable.a IS NULL ) AND
( leftTable.b = rightTable.b OR
rightTable.b IS NULL ) AND
( leftTable.c = rightTable.c OR
rightTable.c IS NULL ) AND
( leftTable.d = rightTable.d OR
rightTable.d IS NULL ) )
GROUP BY rightTable.userid,
rightTable.a,
rightTable.b,
rightTable.c,
rightTable.d
ORDER BY leftTable.userid,
ISNULL( leftTable.a ),
leftTable.a DESC,
ISNULL( leftTable.b ),
leftTable.b DESC,
ISNULL( leftTable.c ),
leftTable.c DESC,
ISNULL( leftTable.d ),
leftTable.d DESC;
实际上是一种告诉它不要进行任何排序的方法。
如果我们只为每个用户重复删除已覆盖的记录(即每个用户的记录与其他用户的记录无关),则应使用以下语句...
ORDER BY
通过在数据集中消除大量需要将其他用户的记录加入到每个用户的记录中,您将删除大量的处理开销, more 比现在需要选择另一个字段输入和,在加入和时测试另一对字段,方法是添加另一个分组和,方法是MySQL
另一个字段。
我担心我无法想到任何其他方式来使这个陈述更有效率。如果有人知道某种方式,那么我想听听它。
如果您有任何问题或意见,请随时发表评论。
附录
此代码在CREATE TABLE tblTable
(
a INT,
b INT,
c INT,
d INT
);
INSERT INTO tblTable ( a,
b,
c,
d )
VALUES ( 1, 2, 3, 4 ),
( 1, 1, 1, NULL ),
( 1, 2, 3, 4 ),
( 1, NULL, NULL, NULL ),
( 1, 2, NULL, NULL ),
( 1, NULL, NULL, NULL ),
( NULL, NULL, NULL, NULL );
中使用使用以下脚本创建的数据集进行了测试...
{{1}}