选择不同的行“modulo null”

时间:2017-04-28 04:52:33

标签: sql presto

假设我有一个表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
  • 如果 c1 c2 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)时间内做到这一点。所以:

    • 是否可以更快地完成这项工作?
    • 如果没有,我应该提一下,在我的真实数据集中,我有一个非空列“userid”,这样每个用户ID最多可以说有100行与之关联。我们是否可以利用此分段仅在每个用户ID上执行二次方,然后将所有数据重新组合在一起? (并且有60k用户,所以我绝对不能在查询中明确命名它们。)

1 个答案:

答案 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,我已经为leftTablerightTable提供了别名。此联接会将rightTable中每条记录的副本附加到leftTableleftTable 的记录涵盖来自rightTable

然后对结果数据集进行分组,以消除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}}