これを取得する 1 つの方法は、JOIN 操作を使用することです。
SELECT f.user2
FROM friends f
JOIN friends r
ON r.user1 = f.user2
AND r.user2 = f.user1
WHERE f.user1 = 1
「真の友人」関係が 2 つのタプルの存在によって識別される場合、つまり、 と の間の真の友人関係は1
、表のとn
の 2 つの行で表されます。(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
)
(friends(user1,user2) に一意の制約がない場合、JOIN は他のクエリでは返されない重複行を返す可能性がありますが、重複が返されないことを保証するクエリはありません。一意の制約がない場合は、重複を返したくない場合は、これらのステートメントの先頭にある SELECT の後に DISTINCT キーワードを追加するか、これらのステートメントの末尾に GROUP BY f.user2 を追加します。
結果セットをより確定的にする (つまり、クエリを実行するたびに同じ結果を返す) には、ORDER BY 句を追加できます。(ただし、MySQL は暗黙的に GROUP BY 式で ORDER BY を実行するため、GROUP BY では必要ありません。)
ファローアップ
この結果をユーザーテーブルの名前にバインドする方法を説明してください。ありがとうございました。そして、どうすれば「本物ではない」友達を得ることができますか?
ユーザー テーブルから名前を取得するには、id が主キー列であり、user1 列と user2 列がユーザー テーブルへの外部キーであると仮定して、ユーザー テーブルに JOIN を追加するだけです...
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)
。これらの行を見つけるために、OUTER 結合 (一方の側からすべての行と一致する行を返す) であるアンチ結合パターンを使用し、次に、一致が見つかった行を除外する述語を使用します (一致する場合に 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