为什么此查询仅返回非空子表的结果?

时间:2009-05-09 08:01:20

标签: sql database database-agnostic

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

主表有两个子表:

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

3 个答案:

答案 0 :(得分:4)

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

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

WHERE
  child1.name = 'child1' OR
  child2.name = 'DOES NOT EXIST';

内连接表示child2中必须有匹配ID的内容,但where子句中包含OR,因此只能因为child1.name ='child1'而获得结果。之后,数据库不必费心查看child2表。

修复它:

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

SELECT
  main.*
FROM
  main
LEFT OUTER JOIN
  child1
ON
  main.id = child1.main_id
  AND child1.name = 'child1'
LEFT OUTER JOIN
  child2
ON
  main.id = child2.main_id
  AND child2.name = 'whatever'
  • 外部联接意味着即使一个表为空,您也有机会获得结果。

  • 将WHERE子句中的额外条件(child1.name = ...)移动到外部联接意味着只有条件为true才能获取表信息。 (我认为这可能是你想要做的,但也许不是,在这种情况下,在WHERE子句中保留你原来拥有它们的条件。)

答案 1 :(得分:2)

它没有返回,因为你正在使用内连接。

将内部联接更改为左联接

答案 2 :(得分:2)

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

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

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