这是我们运行的查询的简化版本,我们需要查找子行匹配的主父表中的所有行。当其中一个子表为空时,下面的查询不返回任何结果。
主表有两个子表:
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主表是资产管理系统中的会话表,用于记录客户端查找的资产。有不同类型的查找,每种类型都有一个单独的子表,另外还有一个属性子表,用于可以搜索的会话的键/值对。
答案 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会产生大量结果。