空值上的 SQL 内连接

Posted

技术标签:

【中文标题】空值上的 SQL 内连接【英文标题】:SQL Inner Join On Null Values 【发布时间】:2011-01-13 05:11:25 【问题描述】:

我有一个加入

SELECT * FROM Y
INNER JOIN X ON ISNULL(X.QID, 0) = ISNULL(y.QID, 0) 

Isnull 在这样的 Join 中使其变慢。这就像有条件的加入。 有没有办法解决这样的事情? 我有很多记录,其中QID 为空

任何人都有不需要修改数据的解决方法

【问题讨论】:

适用于什么数据库(包括版本)? 只有 SQL Server 和 Access 有 ISNULL(),所以我假设 SQL Server 是否可以假设在 X 和 Y 中 QID 可以是任何值,包括 NULL 和 0,但 NULL 和 0 被视为相等? 你的意思是在AND之前有X.QID = Y.QID吗? QID 列是否在两个表中都包含大量空值?如果是这样,您将有效地在空列上获得交叉连接,这似乎是一个有趣的结果。 【参考方案1】:

你有两个选择

INNER JOIN x
   ON x.qid = y.qid OR (x.qid IS NULL AND y.qid IS NULL)

或者更简单

INNER JOIN x
  ON x.qid IS NOT DISTINCT FROM y.qid

【讨论】:

是我遗漏了什么,还是 SQL Server 中没有“IS NOT DISTINCT FROM”功能?据我所知,它可能会进入下一个版本,但它不在 SQL Server 2008 中。 @Garland 是的,SQL Server 中没有IS NOT DISTINCT FROM -1 那么为什么这是投票和评分最高的答案?第一个选项似乎也不起作用,因为我得到“无法绑定多部分标识符“dbo.tbl_MyTable”。” 快!像它一样告诉它 IS NULL x当且仅当 x 为空时才计算为真。 true 是一个和其他所有值一样的值。左侧的trues 和右侧的true 将交叉连接。【参考方案2】:

This article has a good discussion on this issue。你可以使用

SELECT * 
FROM Y
INNER JOIN X ON EXISTS(SELECT X.QID 
                       INTERSECT 
                       SELECT y.QID);

【讨论】:

ISNULL() 的查询计划有什么比较?我在示例数据集上看不出太大的差异,但我喜欢多列连接的简单性。 @ThomasG.Mayfield - ISNULL 杀死索引使用。例如比较CREATE TABLE #T(X INT UNIQUE); SELECT * FROM #T T1 INNER MERGE JOIN #T T2 ON EXISTS(SELECT T1.X INTERSECT SELECT T2.X);SELECT * FROM #T T1 INNER MERGE JOIN #T T2 ON ISNULL(T1.X,-1) = ISNULL(T2.X,-1);SELECT * FROM #T T1 INNER LOOP JOIN #T T2 ON EXISTS(SELECT T1.X INTERSECT SELECT T2.X);SELECT * FROM #T T1 INNER LOOP JOIN #T T2 ON ISNULL(T1.X,-1) = ISNULL(T2.X,-1);DROP TABLE #T 的计划 - 合并版本不能使用索引顺序,需要排序,嵌套循环扫描内部输入而不是寻找它。 我一直在使用这种相交技巧,但似乎 SQL Server 2016 优化器不太喜欢它。不过,我必须进行更多调查, @RomanPekar - 如果你找到一个优化得很糟糕的例子,我很想看看它 @MartinSmith 如果我设法找到一些时间来做一个很好的例子,我将在 SO 上创建一个问题。【参考方案3】:

您也可以使用coalesce 函数。我在PostgreSQL 中对此进行了测试,但它也应该适用于mysql 或MS SQL server。

INNER JOIN x ON coalesce(x.qid, -1) = coalesce(y.qid, -1)

这将在评估之前将NULL 替换为-1。因此qid 中一定没有-1

【讨论】:

【参考方案4】:

如果您希望从 Y.QID 中包含空值,那么最快的方法是

SELECT * FROM Y LEFT JOIN X ON y.QID = X.QID

注意:此解决方案仅适用于您需要左表中的空值,即 Y(在上述情况下)。

否则 INNER JOIN x ON x.qid IS NOT DISTINCT FROM y.qid 是正确的做法

【讨论】:

【参考方案5】:

我很确定这个连接甚至没有做你想做的事。如果表 a 中有 100 条记录的 qid 为空,表 b 中有 100 条记录的 qid 为空,那么所写的连接应该进行交叉连接并为这些记录提供 10,000 个结果。如果您查看以下代码并运行示例,我认为最后一个可能更符合您的预期:

create table #test1 (id int identity, qid int)
create table #test2 (id int identity, qid int)

Insert #test1 (qid)
select null
union all
select null
union all
select 1
union all
select 2
union all
select null

Insert #test2 (qid)
select null
union all
select null
union all
select 1
union all
select 3
union all
select null


select * from #test2 t2
join #test1 t1 on t2.qid = t1.qid

select * from #test2 t2
join #test1 t1 on isnull(t2.qid, 0) = isnull(t1.qid, 0)


select * from #test2 t2
join #test1 t1 on 
 t1.qid = t2.qid OR ( t1.qid IS NULL AND t2.qid IS NULL )


select t2.id, t2.qid, t1.id, t1.qid from #test2 t2
join #test1 t1 on t2.qid = t1.qid
union all
select null, null,id, qid from #test1 where qid is null
union all
select id, qid, null, null from #test2  where qid is null

【讨论】:

我当前的加入做我想做的事,只是做得不够好。 出于好奇,为什么要对空 ID 进行交叉连接?【参考方案6】:

您是否致力于使用内连接语法?

如果不是,您可以使用这种替代语法:

SELECT * 
FROM Y,X
WHERE (X.QID=Y.QID) or (X.QUID is null and Y.QUID is null)

【讨论】:

不过,使用这种语法而不是在接受的答案中显式加入没有任何好处。【参考方案7】:

基本上,您希望将两个表的 QID 列都为空,对吗?但是,您没有强制执行任何其他条件,例如两个 QID 值(这对我来说似乎很奇怪,但没关系)。像以下这样简单的东西(在 MySQL 中测试)似乎可以满足您的需求:

SELECT * FROM `Y` INNER JOIN `X` ON (`Y`.`QID` IS NOT NULL AND `X`.`QID` IS NOT NULL);

这使您可以将 Y 中的每个非空行连接到 X 中的每个非空行。

更新: Rico 说他也想要具有 NULL 值的行,为什么不只是:

SELECT * FROM `Y` INNER JOIN `X`;

【讨论】:

这是不正确的.. 如果是 y.qid = x.qid 它只会给我不为空的行.. 我也想要它们都为空的行。跨度> 让我看看我是否能想出一个更精确的例子来解决问题

以上是关于空值上的 SQL 内连接的主要内容,如果未能解决你的问题,请参考以下文章

SQL中的左连接与右连接,内连接有啥区别

选择语句上的 SQL 内连接

左外连接和右外连接的区别

基于另一列的最大值的列上的 SQL 内连接 [重复]

连接内网服务器上的mysql报错

空值上的PHP合并数组