是不是真的可以到处使用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 的新手,因此没有那么多经验。我将尝试绘制我正在试验的数据库的架构(用文字表示):
数据库有两个表:
Users
(ID、姓名、城市)和友谊(ID、Friend_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 Server JOINS:SQL Server 中是不是默认关联“JOIN”语句“LEFT OUTER”? [复制]