【问题标题】:Why does this query only return results with non-empty child tables?为什么这个查询只返回非空子表的结果?
【发布时间】:2009-05-09 08:01:20
【问题描述】:

这是我们正在运行的查询的简化版本,我们需要在主父表中查找子行匹配的所有行。当其中一个子表为空时,下面的查询不返回任何结果。

主表有两个子表:

CREATE TABLE main (id INT PRIMARY KEY, name VARCHAR(8));

CREATE TABLE child1(id INT PRIMARY KEY, main_id int, name VARCHAR(8));
ALTER TABLE child1 add constraint fk_child1_main foreign key (main_id) references main (id);

CREATE TABLE child2(id INT PRIMARY KEY, main_id int, name VARCHAR(8));
ALTER TABLE child2 add constraint fk_child2_main foreign key (main_id) references main (id);

INSERT INTO main (id, name) VALUES (1, 'main');
INSERT INTO child1 (id, main_id, name) VALUES (2, 1, 'child1');

child2 中没有行,以下查询为空时不返回任何行:

SELECT
  main.*
FROM
  main
INNER JOIN
  child1
ON
  main.id = child1.main_id
INNER JOIN
  child2
ON
  main.id = child2.main_id
WHERE
  child1.name = 'child1' OR
  child2.name = 'DOES NOT EXIST';

如果将一行添加到 child2,即使它与 WHERE 子句不匹配,SELECT 也会返回主表中的行。

INSERT INTO child2 (id, main_id, name) VALUES (4, 1, 'child2');

我已经在 Derby 和 SQLite 上对此进行了测试,所以这看起来与数据库通用。

为什么会这样?

我能做些什么来解决它?

我可以更改为 UNION 单独的 SELECT,但这更加冗长,而且,我们正在动态生成 SQL,我宁愿不必更改我们的代码。

另一个解决方法是在数据库中添加一个愚蠢的行,但这很混乱。

PS 主表是资产管理系统中的一个会话表,用于记录客户查找的资产。有不同类型的查找,每种类型都有一个单独的子表,此外还有一个属性子表,用于可以搜索会话的键/值对。

【问题讨论】:

    标签: sql database database-agnostic


    【解决方案1】:

    当 child2 没有行时,由于对 child2 表的内部连接,查询不会返回任何行。如果您对没有行的表进行内部联接,则永远不会得到任何结果 - 如果您想在 child2 为空时获得结果,则必须外部联接到 child2。

    当 child2 确实有一行时,您的查询返回结果的原因是因为 where 子句:

    在哪里 child1.name = 'child1' 或 child2.name = '不存在';

    inner join 表示 child2 中必须有具有匹配 ID 的内容,但 where 子句中有一个 OR,因此您将得到结果,因为 child1.name = 'child1'。之后,数据库就不必费心查看 child2 表了。

    修复它:

    我有预感,您只想在满足某些条件时返回子行。您应该对它们都进行外连接,并且也许还将您的额外条件从 where 子句移动到 join 子句,如下所示:

    选择 主要的。* 从 主要的 左外连接 孩子1 在 main.id = child1.main_id AND child1.name = 'child1' 左外连接 孩子2 在 main.id = child2.main_id AND child2.name = '随便'
    • 外连接意味着即使一张表是空的,您也有机会获得结果。

    • 将额外条件 (child1.name = ...) 从 WHERE 子句移动到外部连接意味着您只有在条件为真时才能获得表信息。 (我认为这可能是您正在尝试做的事情,但可能不是,在这种情况下,将条件留在您最初拥有它们的 WHERE 子句中。)

    【讨论】:

      【解决方案2】:

      它没有返回任何内容,因为您使用的是内部连接。

      将内部联接更改为左联接

      【讨论】:

      • 那为什么给child2添加一个不匹配的行会改变查询的结果呢?根据此语句,即使在 child2 中添加了不匹配的行后,查询仍应不返回任何行。
      • 没关系,我看到是 main.id = child2.main_id 阻止了查询返回任何结果,即使 child2.name 不匹配。我只是忽略了这部分查询。
      【解决方案3】:

      当您说 INNER JOIN 时,您是在要求查询返回在连接两边都有结果的行。这意味着任何没有匹配子行的行都将被删除。

      听起来您正在寻找的是 LEFT JOIN,它将包括连接(主)左侧的所有行,即使它们在右侧没有匹配的条目(child1,child2)。

      这是标准行为,对于不熟悉 SQL 的人来说是一个非常常见的问题。 Wikipedia 包含所有详细信息,否则快速 Google search 会带来大量结果。

      【讨论】:

      • 那为什么给child2添加一个不匹配的行会改变查询的结果呢?根据此语句,即使在 child2 中添加了不匹配的行后,查询仍应不返回任何行。
      • 没关系,我看到是 main.id = child2.main_id 阻止了查询返回任何结果,即使 child2.name 不匹配。我只是忽略了这部分查询。
      猜你喜欢
      • 1970-01-01
      • 2013-03-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-24
      • 2019-12-11
      • 1970-01-01
      相关资源
      最近更新 更多