【发布时间】:2020-12-07 22:35:22
【问题描述】:
我在使用左外连接查询时遇到了一些非常奇怪的行为。
编辑:我提供了一个 Access 数据库,其中包含重现此问题所需的表和数据。请注意,这是重现问题的最佳方法,因为如果将数据大小减少到小样本,则不会出现问题。下载地址:https://joek.com/etc/left_join_odd_behavior.accdb
编辑:我的核心问题是多年来我一直以这种方式使用左外连接,之前没有遇到过这样的问题。此外,我可能在十几个应用程序中还有数百个像这样的其他左外连接,并且没有遇到过这样的问题,或者更糟糕的是,如果这个问题发生在其他一些查询中但尚未被注意到。
编辑:左外连接根据不应影响结果的因素提供不同的结果(例如,将不同数量的列插入另一个表,或为特定结果集包含 WHERE 子句,或减少数据到一个小样本)。我的根本问题不在于修复此查询(我可以通过几种不同的方式来提供正确的结果)。我的问题是理解为什么这个应该工作的查询在某些情况下不起作用,以便我可以理解它是否是:数据问题(以便我可以在这些表中修复它并检查它是否没有在任何其他表中发生)、索引问题(以便我可以对这些表应用正确的索引并确保其他表正确)或 MS Access 中固有的问题(以便我可以围绕它编写代码,更改所有其他类似的左外连接, 并在未来使用不同的方法 [因为我不得不处理 Access 和 SQL Server 之间的一些其他差异,这是我在 6 年前开始使用 Access 以来已经习惯的])。
我已将其简化为以下查询:
SELECT
*x*
FROM TableA
LEFT OUTER JOIN TableB
ON (TableB.IntA = TableA.IntA
AND TableB.IntB = TableA.IntB
AND TableB.DateA IS NULL)
我对所有这些正在使用的列都有索引。 编辑:(请注意,在我的示例数据库中,我已将生产代码中的列重命名以匹配此处的示例查询。)
如果 x 是:
TableA.IntA INTO TempTableX
那么我得到的结果缺少一些符合左连接条件的记录。
如果 x 是:
TableA.* INTO TempTableX
然后我得到所有预期的记录。
如果我只是返回所有记录而不将它们插入到表中,那么返回 TableA.IntA 或 TableA.* 甚至 TableA.IntA、TableA.IntB 都没有关系;每种方式都会排除一些匹配的记录。
每张表有数千条记录(TableA 50K+,TableB 8,000+),但是对于在 IntA 和 IntB 上都匹配的记录,只有大约 200 条匹配。其中,只有 17 个将 DateA 设置为 Null。
以下是未正确返回的数据示例:
TableA
-----------
IntA IntB
1 10
2 22
3 33
4 44
TableB
-------
IntA IntB DateA
2 20 1/1/2020
3 31 2/1/2020
4 44 3/1/2020
如您所见,这是:
SELECT
TableA.IntA, TableA.IntB
FROM TableA
LEFT OUTER JOIN TableB
ON (TableB.IntA = TableA.IntA
AND TableB.IntB = TableA.IntB
AND TableB.DateA IS NULL)
应该返回以下内容(编辑:因为这是一个在 ON 中具有特定条件的左外连接,并且没有 WHERE 子句来约束结果,它应该返回 TableA 中的所有记录):
TableA.IntA TableA.IntB
-------------------------
1 10
2 22
3 33
4 44
但我得到以下信息(编辑:它排除了与左连接条件匹配的一条记录,尽管没有 WHERE 子句指定排除 TableA 中与 TableB 不匹配的记录):
TableA.IntA TableA.IntB
-------------------------
1 10
2 22
3 33
但是,如果我将 TableA.* 插入到表中,那么我会得到预期的 4 条记录(编辑:请注意,当使用 SELECT TableA.* INTO TempTableX 时会发生这种情况,这会导致 Access 基于TableA 中的列的数据结构,并且我没有返回 TableB 中的任何列,因此不会因为表字段要求而没有填充数据的任何问题。
此外,如果我在查询中添加 WHERE 条件以专门获取预期的记录,那么我会在结果中得到它们。或者,如果我清空除这些示例记录之外的所有其他记录的 TableA 和 TableB,那么我会正确获取它们。
编辑:
使用提供的数据库,您可以通过以下方式测试这些情况:
第一:
SELECT
TableA.*
FROM TableA
LEFT OUTER JOIN TableB
ON (TableB.IntA = TableA.IntA
AND TableB.IntB = TableA.IntB
AND TableB.DateA IS NULL)
您将获得 58,160 条记录,当您将这些结果放入电子表格并在 CUSTID 列中过滤“5616”时,您会发现 25 条记录(这是一个不完整的结果)。
第二:
SELECT
TableA.*
FROM TableA
LEFT OUTER JOIN TableB
ON (TableB.IntA = TableA.IntA
AND TableB.IntB = TableA.IntB
AND TableB.DateA IS NULL)
WHERE
TableA.CustId = "5616"
您现在会找到正确的 26 条记录。
如果您比较这些结果,您会发现在不完整结果中排除的一条记录(IntA = 25093 & IntB = 59797)是 TableB 中 CustId 5616 中唯一同时匹配 IntA 和 IntB 的记录。但是虽然它与 DateA IS NULL 的条件不匹配,但即使在第一个查询中,它仍然应该从 TableA 返回,因为没有 WHERE 子句排除与左连接条件不匹配的 TableA 记录。
或者,如果您将第一个查询更改为:
SELECT
TableA.*
INTO TempTableX
FROM TableA
LEFT OUTER JOIN TableB
ON (TableB.IntA = TableA.IntA
AND TableB.IntB = TableA.IntB
AND TableB.DateA IS NULL)
然后您将获得 TableA 中存在的所有 58,348 条记录,并过滤 CUSTID“5616”您将找到所有 26 条记录。
或者,如果您不更改第一个查询,但清空 TableA 中除 CustId = "5616" 之外的所有记录,那么您将获得完整的 26 条记录,而不是 25 条。
同样,我的问题是,为什么 Access 会根据通常不会影响结果的更改(数据库中的记录数、返回数据与插入表等)提供不同的结果?
另外,关于我为什么使用这样的左连接的更多详细信息:
- 此查询不是最终目标,它只是我发现问题发生的生产代码的最小缩减。
- 生产用途是从 TableA 收集所有在 TableB 中没有匹配记录的记录,其中 DateA 为空(又名 TableB 是 TableA 中已报告的记录的日志,并且在问题出现时填充了 DateA已解决并因此从报告中消失,因此所有 DateA 空值都是在报告上仍然打开的那些,因此生产查询包括 WHERE TableB.LogId IS NULL 以便从 TableA 中排除在 TableB 中仍未解决的所有记录 [再次如此有关更多详细信息,如果我添加 WHERE TableB.LogId IS NULL,则 CustId "5616" 的记录应返回全部 26,因为 CustId "5616" 的 TableB 中的 0 条记录在 DateA 中有一个空值,因此 0 应该加入,因此 CustId 的所有 26 条记录在 TableA 中将有 TableB.LogId = null,但不会发生这种正确的行为,除非我使用更多条件限制结果,将所有列插入表中,或减少表中的数据等])。
- 左连接在两列 IntA 和 IntB 上,因为 TableB 是 TableA 中记录出现的日志。换句话说,IntA 可能在 TableB 中出现了 5 次(IntB 的 5 个不同值),但我只想匹配 TableA 中记录的每个 IntA 的特定 IntB 出现次数。
而且,正如我上面提到的,这些列在表中被索引,我什至尝试过压缩和修复数据库。
有什么想法吗?
【问题讨论】:
-
您看到的是一个错误。外部加入 MS Access 对我来说很像赌博。无论在
LEFT OUTER JOIN的ON子句中放入什么,当然都必须保留左侧表中的所有行。但是将AND TableA.IntA = -1添加到您的ON子句中,不再返回任何行!我的建议很简单:当你想要一个好的、可靠的 DBMS 时,远离 MS Access。 -
@ThorstenKettner 谢谢,一个错误可以更好地解释它并减轻代码或数据或我可以控制的东西有问题的压力。
-
谷歌搜索“外部加入 MS Access 站点:stackoverflow.com 左表缺少行无处”我的第一次点击:stackoverflow.com/q/12573561/3404097