是不是真的可以到处使用JOINS来代替SQL中的子查询

Posted

技术标签:

【中文标题】是不是真的可以到处使用JOINS来代替SQL中的子查询【英文标题】:Is it true that JOINS can be used everywhere to replace Subqueries in SQL 【发布时间】:2016-01-21 02:32:31 【问题描述】:

我听说有人说表连接可以在任何地方使用来替换子查询。我在查询中对其进行了测试,但发现仅在使用子查询时才检索到适当的数据集。我无法使用连接获得相同的数据集。我不确定我的发现是否正确,因为我是 RDBMS 的新手,因此没有那么多经验。我将尝试绘制我正在试验的数据库的架构(用文字表示):

数据库有两个表:

UsersID、姓名、城市)和友谊(IDFriend_ID )

Goal:Users表是用来存放简单的用户数据的,Friendship表代表用户之间的友谊。 Friendship 表的两列都作为外键,引用Users.ID。表之间存在多对多关系。

问题:我必须检索所有用户的 Users.ID 和 Users.Name,这些用户不是特定用户 x 的朋友,而是来自同一个城市(很像 fb 的朋友建议系统)。

通过使用子查询,我能够做到这一点。查询如下:

SELECT ID, NAME 
FROM USERS AS U
WHERE U.ID NOT IN (SELECT FRIENDS_ID
                   FROM FRIENDSHIP,
                        USERS
                   WHERE USERS.ID = FRIENDSHIP.ID AND USERS.ID = x)
  AND U.ID != x AND CITY LIKE '% A_CITY%';

示例条目:

Users

Id = 1 姓名 = Jon City = 孟买

Id=2 Name=Doe City=孟买

Id=3 Name=Arun City=孟买

Id=4 Name=Prakash City=德里

Friendship

Id= 1 Friends_Id = 2

Id = 2 Friends_Id=1

Id = 2 Friends_Id = 3

Id = 3 Friends_Id = 2

我可以通过执行联接在单个查询中获得相同的数据集吗?如何?如果我的问题不清楚,请告诉我。谢谢。

注意:我通过指定两个表在子查询中使用了内部联接:友谊、用户。省略 Users 表并从外部使用 U 会产生错误(但如果不使用表 Users 的别名,查询在语法上就可以了,但此查询的结果包括用户的 ID 和名称,这些用户有多个朋友,包括具有 ID x 的用户。有趣,但不是问题的主题)。

【问题讨论】:

【参考方案1】:

对于not in,您可以使用left join 并检查is null

select u.id, u.name 
from  Users u 
left join Friends f on u.id = f.id and f.friend_id = @person
where u.city like '%city%' and f.friend_id is null and u.id <> @person;

在某些情况下,您无法仅使用内部/左/右连接来解决问题,但您的情况不是其中之一。

请检查 sql fiddle:http://sqlfiddle.com/#!9/1c5b1/14

关于您的注释:您尝试执行的操作可以通过 lateral join 或 cross apply 实现,具体取决于您使用的引擎。

【讨论】:

我很确定 City 不是参数。 @Aheho 这与问题无关,但你是对的。 我很想知道 SQL 服务器是否会将子查询语句优化为有效的连接。就像你做这件事的方式真的有区别吗? @Josh 在大多数情况下它会,尤其是在较新版本的 sqlserver/postgre/mysql 中。但是一些旧版本的 rdbms 可能会提供截然不同的执行计划。 我认为这无济于事,因为查询将返回 f.friend_id 仅设置为 null 的行。一个用户可以有其他朋友。例如,一个用户有两个朋友 x(当前用户)和另一个有 id y 的用户,将在联接中有 2 个条目,并且将返回与此人相关的数据,其中 frienship.id = z 和friendship.id = y .如果我遗漏了什么,请通过发布答案来清除它。【参考方案2】:

您可以仅使用连接来重写您的查询。诀窍是使用内部联接加入 User 表一次以识别同一城市内的用户,并使用左联接和空检查来引用 Friendship 表以识别非朋友。

SELECT
     U1.ID,
     U1.Name
FROM
    USERS U1
INNER JOIN
    USERS U2
ON
     U1.CITY = U2.CITY
LEFT JOIN
     FRIENDSHIP F
ON
    U2.ID = F.ID AND
    U1.ID = F.FRIEND_ID
WHERE
     U2.id = X AND
     U1.ID <> U2.id AND
     F.id IS NULL

上述查询没有处理 USER x 的主键在 FRIENDSHIP 表的 FRIEND_ID 列中的情况。我假设因为您的子查询版本不能处理这种情况,也许您为每个友谊创建 2 行,或者友谊不是双向的。

【讨论】:

双向友谊的要点。我决定不追求它,因为它不在原始查询中。 F.ID 为空,我正在考虑这种情况。如果用户有两个或多个朋友,包括 ID 为 x 的用户,该怎么办。它会起作用吗? 用户 X 的任何好友都会因为 F.ID 为空条件而被排除在外。 您熟悉 LEFT JOIN 吗?这是该策略的关键。【参考方案3】:

在某些情况下,连接和子查询可用于实现类似的结果,但肯定不是全部。例如,这个带有子查询的查询无法实现相对于联接:

SELECT ID, COLUMN1, COUNT(*) FROM MYTABLE
WHERE ID IN (
    SELECT DISTINCT ID FROM MYTABLE
    WHERE COLUMN2 NOT IN (VALUES1, VALUES2)
)
GROUP BY ID;

这只是一个例子,但还有很多。

相反,如果不加入子查询,则无法从另一个表中获取信息。

你的例子

SELECT ID, NAME FROM USERS AS U 
WHERE U.ID NOT IN (
    SELECT FRIENDS_ID FROM FRIENDSHIP, USERS 
    WHERE USERS.ID = FRIENDSHIP.ID AND USERS.ID = x) 
AND U.ID != x AND CITY LIKE '% A_CITY%';

这可以构造为:

select ID, NAME from users u
join FRIENDSHIP f        on f.ID = u.ID
where u.ID = x
and u.ID != y
and CITY like '%A_CITY';

我假设将您的第二个 x 更改为 y,因此不会引起混淆。

当然,如果 FRIENDSHIP 表中可能存在多个结果,您可能还需要 LEFT JOIN aka LEFT OUTER JOIN。

【讨论】:

您的第二个查询是错误的,如果某人与该城市的两个人成为朋友会怎样? 没有y,我认为必须在内部和外部查询中使用相同的x。 另外你的第一个查询根本不需要子查询和连接:SELECT ID, COLUMN1, COUNT(*) FROM MYTABLE WHERE COLUMN2 NOT IN (VALUES1, VALUES2) GROUP BY ID; vittore,您在原始查询中缺少 DISTINCT。由于我正在运行 count(*),因此我不想将行排除在总数中...我想完全排除该 ID。 您似乎对这个查询的工作方式有一些误解。分拆 DB 并将一些数据放入其中并执行您和我的查询。

以上是关于是不是真的可以到处使用JOINS来代替SQL中的子查询的主要内容,如果未能解决你的问题,请参考以下文章

SQL SELF 和 INNER JOINS 在一个查询中

JOINS 的大型查询中的 SQL 子查询链

SQL Server JOINS:SQL Server 中是不是默认关联“JOIN”语句“LEFT OUTER”? [复制]

如何使用 JOINS 和嵌套 SELECT 优化此 SQL 查询?

sql joins

用exists代替in真的好么?