【问题标题】:PostgreSQL JOIN without matching NULL values不匹配 NULL 值的 PostgreSQL JOIN
【发布时间】:2018-03-03 02:26:07
【问题描述】:

我称之为“虚无之战”,因为多年来我一直在为这个问题苦苦挣扎。

我有一个名为People 和另一个名为Stuff 的大表(250,000+ 行,100+ 列),它可能包含也可能不包含相应的记录。我可以使用三列来查找可能的匹配项:个人 ID、电话号码或电子邮件地址。这些列中可能有也可能没有值,甚至可能包含空值。

我多年前为此编写的原始查询如下:

SELECT *
  FROM People
  LEFT OUTER JOIN Stuff
    ON People.PersonID = Stuff.PersonID
    OR People.CellNumber = Stuff.PhoneNumber
    OR People.Email = Stuff.WorkEmail;

当我第一次尝试运行此查询时,它在连接表中产生了 数百万 条记录,这与我的预期完全不同。 经过几天的故障排除后,我终于确定是空值和空单元格的存在导致了结果的大幅增加。 对于那些可能不知道的人,PostgreSQL 对待 null 和空单元格的方式与对待其中包含数据的单元格相同。 结果是它获取 People 表中每个带有空单元格的记录,并将其与 Stuff 表中带有空单元格的每个记录连接起来。 它对 null 以及所有三个比较都执行相同的操作。

我搜索了数周,但从未找到优雅或简单的解决方法,因此我最终不得不将其分解为一系列单独的查询,如下所示。

SELECT *
FROM People
    LEFT OUTER JOIN Stuff
      ON People.PersonID = Stuff.PersonID
    WHERE (People.PersonID != ''
      AND People.PersonID IS NOT NULL);

将匹配的记录转储到临时表中,然后通过第二个查询运行不匹配的记录:

SELECT *
FROM People
    LEFT OUTER JOIN Stuff
      ON People.CellNumber = Stuff.PhoneNumber
    WHERE (People.CellNumber != ''
      AND People.CellNumber IS NOT NULL);

将匹配的记录转储到临时表中,然后通过第三个查询运行剩余的不匹配记录:

SELECT *
FROM People
    LEFT OUTER JOIN Stuff
      ON People.Email = Stuff.WorkEmail
    WHERE (People.Email != ''
      AND People.Email IS NOT NULL);

将结果(匹配和不匹配)转储到临时表中,然后继续。

多年来,我一直在使用这种非常不优雅的方法,并且它没有任何问题。但是现在我需要修改这个脚本以适应业务需求的变化,并且我试图再次找到一个更简单的解决方案。当前方法的问题是,每当我必须对查询进行更改时,我必须在代码中的多个位置进行更改,这会成为维护的噩梦。

在这次迭代中,我提出了以下几点:

SELECT *
  FROM People
  LEFT OUTER JOIN Stuff
    ON (People.PersonID = Stuff.PersonID
        WHERE People.PersonID != ''
          AND People.PersonID IS NOT NULL)
    OR (People.CellNumber = Stuff.PhoneNumber
        WHERE People.CellNumber != ''
          AND People.CellNumber IS NOT NULL)
    OR (People.Email = Stuff.WorkEmail)
        WHERE People.Email != ''
          AND People.Email IS NOT NULL);

这看起来应该可以工作,但它在第一个 WHERE 子句处死了。

我在正确的轨道上吗?我怎样才能使这项工作?还是有其他更好的方法?

必须有一种方法来运行原始的三条件查询,它与空值或空值不匹配,但我还没有找到它。

狗走了!我要赢得这场空虚之战! (当然是在你的帮助下!)

【问题讨论】:

  • 我与 NULL 的战斗通常通过简单地使用 coalesce(int,0) 或 coalesce(text,'') 来赢得。但肯定还有很多其他方法。
  • “对待空值 [...] 与包含数据的单元格一样” 不。(null = something)为空。 “空单元格”不清楚。 '' 不为空,但在 Oracle 中除外。但是(真或什么)是真的。如果您希望在某些输入为 null 或 '' 时 OR 为假,请这样说。您没有给出约束或您希望在什么条件下返回行,因此很难给出正确的代码或缩短的代码。当 id 不匹配时,您想要带有 = non-ids 的行似乎也很奇怪,因此您可能会遇到其他问题。但在任何事情之前:minimal reproducible example.

标签: postgresql join null


【解决方案1】:

Postgres 将“空”单元格与NULL 值匹配。 NULL 不匹配任何东西,使用典型的比较运算符。但是,一个空字符串将匹配一个空字符串。l

我怀疑你真的想要这样的东西:

SELECT p.*, COALESCE(sp.?, sc.?, se.?) as ?
FROM People p LEFT OUTER JOIN
     Stuff sp
     ON p.PersonID = sp.PersonID LEFT OUTER JOIN
     Stuff sc
     ON p.CellNumber = sc.PhoneNumber AND sp.personID IS NULL LEFT OUTER JOIN
     stuff se
     ON p.Email = se.WorkEmail AND sc.personID is null;

这将从三个表中获取people 中每一行的第一个匹配项。

【讨论】:

  • 天哪,看起来不错,我一定会试一试。您对空值和空值的 cmets 给了我另一个想法,即在我运行导入脚本时将所有空单元格设置为空值。非常感谢,我明天会继续努力,让你知道进展如何。
【解决方案2】:

如果布尔表达式中的右侧字段为空字符串,则使用NULLIF函数将其视为null,然后对于左右表都至少有1的行,连接条件将不会返回true '' == ''的情况。

SELECT *
  FROM People
  LEFT OUTER JOIN Stuff
    ON People.PersonID = NULLIF(Stuff.PersonID, '')
    OR People.CellNumber = NULLIF(Stuff.PhoneNumber, '')
    OR People.Email = NULLIF(Stuff.WorkEmail, '');

【讨论】:

    【解决方案3】:

    如果您确保消除空字符串(如您所记)并防止插入新的空字符串,则可以使用上面的查询。见:Prevent empty strings in CHARACTER VARYING field

    【讨论】:

      猜你喜欢
      • 2018-12-14
      • 2022-08-18
      • 1970-01-01
      • 2011-12-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-07-13
      • 2013-06-04
      相关资源
      最近更新 更多