一种方法是使用 JOIN 操作:
SELECT f.user2
FROM friends f
JOIN friends r
ON r.user1 = f.user2
AND r.user2 = f.user1
WHERE f.user1 = 1
假设“真正的朋友”关系由两个元组的存在来标识,即1
和之间的真正朋友关系n
将由表中的两行表示:(1,n)
和(n,1)
。
连接条件中的谓词将返回的行限制为具有匹配“反向”元组的那些行。
注意:JOIN 操作通常比等效IN (subquery)
或EXISTS (subquery)
模式执行得更好,但是对于小集合,这种性能差异可以忽略不计。使用更大的集合,性能差异变得明显。
使用 EXISTS 谓词可以返回等效结果(通常效率较低):
SELECT f.user2
FROM friends f
WHERE f.user1 = 1
AND EXISTS ( SELECT 1
FROM friends r
WHERE r.user1 = f.user2
AND r.user2 = f.user1
)
或 IN 谓词:
SELECT f.user2
FROM friends f
WHERE f.user1 = 1
AND f.user2 IN ( SELECT r.user1
FROM friends r
WHERE r.user2 = f.user1
)
(如果对朋友(user1,user2)没有唯一约束,则 JOIN 可能会返回一些其他查询可能不会返回的重复行,但没有一个查询保证不返回重复项。如果没有唯一约束,并且您不希望返回任何重复项,那么您可以在任何这些语句的开头的 SELECT 之后添加一个 DISTINCT 关键字,或者在任何这些语句的末尾添加一个 GROUP BY f.user2 。
为了使结果集更具确定性(即每次运行查询时返回相同的结果),您可以添加 ORDER BY 子句。(但 GROUP BY 不需要它,因为 MySQL 隐式地对 GROUP BY 表达式执行 ORDER BY。)
跟进
解释我如何将此结果与用户表中的名称绑定?谢谢你。我如何获得“不真实”的朋友?
要从用户表中获取名称,我们只需在用户表中添加一个 JOIN,假设 id 是主键列,user1 和 user2 列是用户表的外键...
SELECT f.user2
, u.name
FROM friends f
JOIN user u
ON u.id = f.user2
JOIN friends r
ON r.user1 = f.user2
AND r.user2 = f.user1
WHERE f.user1 = 1
“不真实”的朋友将被表示为一个元组(表中的行)(1,n)
,它没有相应的逆元组(n,1)
。为了找到这些行,我们使用反连接模式,这是一个外连接(从一侧返回所有行加上任何匹配的行),然后是一个谓词,它排除找到匹配的行(检查一个 NULL如果有匹配项,则保证不为空的列是我们这样做的方式):
这将找到(1,n)
没有匹配的所有元组(n,1)
:
SELECT f.user2
, u.name
FROM friends f
JOIN user u
ON u.id = f.user2
LEFT
JOIN friends r
ON r.user1 = f.user2
AND r.user2 = f.user1
WHERE r.user1 IS NULL
AND f.user1 = 1
我们必须将其翻转以获取另一侧,(n,1)
即没有匹配行的(1,n)
行:
SELECT f.user1
FROM friends f
JOIN user u
ON u.id = f.user1
LEFT
JOIN friends r
ON r.user2 = f.user1
AND r.user1 = f.user2
WHERE r.user2 IS NULL
AND f.user2 = 1