10

テーブルがusersあり、任意の 2 人のユーザー間の「友達」関係を定義したいと考えています。

これまで、これには 2 つの異なる方法を使用してきました。

  1. friendsテーブルにはuser1とが含まれていuser2ます。ユーザーの検索には のようなクエリが含まれますが
    ... WHERE @userid IN (`user1`,`user2`)、これは非常に効率的ではありません
  2. friendsテーブルにはfromtoフィールドが含まれています。友達リクエストを開始すると、その方向に行が作成され、それが受け入れられると、2 番目の行が反対方向に挿入されます。さらに、statusこれが発生したことを示す列があり、検索は次のようになります。
    ... WHERE `user1`=@userid AND `status`=1

これらのソリューションのいずれにも特に満足していません。最初のものはその使用法が乱雑に感じられIN、2 番目のものは 1 つのリンクを定義するために 2 つの行を持っているために肥大化しているように見えます。

それが私がここにいる理由です。そのようなリンクに何を提案しますか? これ以上情報を保存する必要はないことに注意してください。互いに関連付けられた 2 つのユーザー ID と、できれば のような何らかのステータスENUM('pending','accepted','blocked')が必要ですが、最適な設計が何であるかによってはオプションです。

4

5 に答える 5

8

一般に、次の 2 つのアプローチがあります。

  1. 各フレンド ペアを 1 回保存し、ID が最小のフレンドを最初に保存します。

    CREATE TABLE
            friend
            (
            l INT NOT NULL,
            g INT NOT NULL,
            PRIMARY KEY
                    (l, g),
            KEY (g)
            )
    
  2. 各フレンド ペアを 2 回、両方の方法で保存します。

    CREATE TABLE
            (
            user INT NOT NULL,
            friend INT NOT NULL,
            PRIMARY KEY
                    (user, friend)
            )
    

交友関係や承認日などの追加フィールドを保存するには、通常は 2 番目のテーブルを使用します。その理由については、以下で説明します。

各ユーザーの友達のリストを取得するには、次のようにします。

SELECT  CASE @myuserid WHEN l THEN g ELSE l END
FROM    friend
WHERE   l = @myuserid 
        OR
        g = @myuserid

また

SELECT  g
FROM    friend
WHERE   l = @myuserid
UNION
SELECT  l
FROM    friend
WHERE   g = @myuserid

最初のソリューション。と

SELECT  friend
FROM    friend
WHERE   user = @friend

2 人のユーザーが友達かどうかを確認するには、次のように発行します。

SELECT  NULL
FROM    friend
WHERE   (l, g) =
        (
        CASE WHEN @user1 < @user2 THEN @user1 ELSE @user2 END,
        CASE WHEN @user1 > @user2 THEN @user1 ELSE @user2 END
        )

また

SELECT  NULL
FROM    friend
WHERE   (user, friend) = (@user1, @user2)

ストレージに関しては、2 つのソリューションはほぼ同じです。最初の (最小/最大) ソリューションは 2 倍の数の行を格納しますが、高速に動作させるには、 にセカンダリ インデックスを作成する必要がありgますg。副次索引 (つまり、l)。したがって、各レコードは実質的に 2 回格納されます。1 回はテーブル自体に格納され、もう 1 回は のインデックスに格納されgます。

パフォーマンスに関しては、ソリューションもほぼ同じです。ただし、最初のものは 2 つのインデックス シークとそれに続くインデックス スキャン (「すべてのフレンド」の場合) を必要とし、2 つ目は 1 回のインデックス シークのみであるため、L/G ソリューションの場合、I/O 量はわずかに多くなる可能性があります。これは、1 つのインデックスが 2 つの独立したインデックスよりも 1 レベル深くなる可能性があるという事実によって少し軽減される可能性があるため、最初の検索で 1 ページ多く読む必要がある場合があります。これにより、L/G と比較して、「両方のペア」ソリューションの「are they friends」クエリが少し遅くなる可能性があります。


追加データ用の追加テーブルについては、通常は上記の 2 つのクエリよりもはるかに使用頻度が低いため (通常は履歴の目的でのみ)、おそらく必要になるでしょう。

そのレイアウトは、使用しているクエリの種類によっても異なります。たとえば、「最近の 10 人の友情を表示」したい場合は、タイムスタンプを「両方のペア」に保存して、ファイルソートなどを行う必要がないようにすることができます。

于 2013-06-17T14:01:15.083 に答える
0

パフォーマンスの考慮事項は別として、別のオプションとして、1 つの行がフレンドを表す (どちらの向きでも構いません) 「フレンド」テーブルと、任意のフレンド行に対して 2 つの結果行 (各方向に 1 行) を生成するビューを組み合わせることもできます。使用すると、「フレンドシップ」ごとに 1 つのデータ行しか必要とせずに「2 行」ソリューションと同じ方法で使用できるため、クエリが簡素化されます。

唯一の欠点はパフォーマンスです...クエリオプティマイザーの動作によって異なります。

于 2013-06-18T22:38:23.420 に答える
0

次のスキーマを検討してください。

CREATE TABLE `users` (
  `uid` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(30) NOT NULL,
  PRIMARY KEY (`uid`)
);

INSERT INTO `users` (`uid`, `username`) VALUES
(1, 'h2ooooooo'),
(2, 'water'),
(3, 'liquid'),
(4, 'wet');


CREATE TABLE `friends` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `uid_from` int(10) unsigned NOT NULL,
  `uid_to` int(10) unsigned NOT NULL,
  `status` enum('pending','accepted','blocked') NOT NULL,
  PRIMARY KEY (`id`),
  KEY `uid_from` (`uid_from`),
  KEY `uid_to` (`uid_to`)
);

INSERT INTO `friends` (`id`, `uid_from`, `uid_to`, `status`) VALUES
(1, 1, 3, 'accepted'), -- h2ooooooo sent a friend request to liquid - accepted
(2, 1, 2, 'pending'), -- h2ooooooo sent a friend request to water - pending
(3, 4, 1, 'pending'), -- wet sent a friend request to h2ooooooo - pending
(4, 4, 2, 'pending'), -- wet sent a friend request to water - pending
(5, 3, 4, 'accepted'); -- liquid sent a friend request to wet - accepted

次のようなものを使用します。

SELECT
    fu.username as `friend_username`,
    fu.uid as `friend_uid`
FROM
    `users` as `us`
LEFT JOIN
    `friends` as `fr`
ON
    (fr.uid_from = us.uid OR fr.uid_to = us.uid)
LEFT JOIN
    `users` as `fu`
ON
    (fu.uid = fr.uid_from OR fu.uid = fr.uid_to)
WHERE
    fu.uid != us.uid
AND
    fr.status = 'accepted'
AND
    us.username = 'liquid'

結果:

friend_username | friend_uid
----------------|-----------
h2ooooooo       | 1
wet             | 4

usこれは、友達を照会するユーザーであり、ユーザーの友達になりますfu。ステートメントを簡単に変更してWHERE、好きな方法でユーザーを選択できます。uid_toユーザーが応答していない友達のリクエストを見つけたい場合は、ステータスを保留に変更できます (また、にのみ参加する必要があります)。

SQLFIDDLE のデモ

ユーザーを照合するためEXPLAINに使用する場合 (インデックスが作成されているため):us.uid

ここに画像の説明を入力

于 2013-06-12T09:16:37.070 に答える