Первое, что бросается в глаза - это настройка индекса friends
.
У вас есть это на данный момент:
friends
-------
userid
friendid
primary key (`userid`,`friendid`),
key `friendid` (`friendid`)
При перекрестной проверке взаимной дружбы это может потребовать небольших затрат, поскольку идентификатор пользователя может быть получен из таблицы при обходе friendid
индекса. Возможно, вы могли бы индексировать следующим образом:
friends
-------
userid
friendid
primary key (`userid`,`friendid`),
unique key `friendid` (`friendid`,`userid`)
Это может устранить любую необходимость доступа к таблице и поиска только по индексу.
Теперь, с точки зрения запросов, они оба могут улучшиться с новым уникальным индексом. Создание уникального индекса также устраняет необходимость вставить (A,B)
и (B,A)
в таблицу , так (A,B)
и (B,A)
будет индекс в любом случае. Таким образом, второй запрос не должен был бы пролистывать таблицу, чтобы увидеть, является ли кто-то другом другого, потому что другой человек инициировал дружбу. Таким образом, если дружба нарушается только одним человеком, не бывает односторонних дружеских отношений, которые были бы односторонними (в наши дни это похоже на жизнь, не так ли?)
Ваш первый запрос выглядит так, как будто бы он получил больше пользы от уникального индекса. Даже с миллионами строк поиск друзей с использованием только индексов позволит избежать прикосновения к таблице. Тем не менее, поскольку вы не представили запрос UNION, я бы порекомендовал запрос UNION:
SET @givenuserid = ?;
SELECT B.name "Friend's Name"
FROM
(
SELECT userid FROM friends WHERE friendid=@givenuserid
UNION
SELECT friendid FROM friends WHERE userid=@givenuserid
) A INNER JOIN user B USING (userid);
Это позволит вам увидеть, кто является друзьями каждого пользователя
Чтобы увидеть все дружеские отношения, запустите это:
SELECT A.userid,A.name,B.friendid,C.name
FROM user A
INNER JOIN friends B ON A.userid=B.userid
INNER JOIN user C on B.friendid=C.userid;
Во-первых, вот несколько примеров данных:
mysql> drop database if exists key_ilyuk;
Query OK, 2 rows affected (0.01 sec)
mysql> create database key_ilyuk;
Query OK, 1 row affected (0.00 sec)
mysql> use key_ilyuk
Database changed
mysql> create table user
-> (
-> userid INT NOT NULL AUTO_INCREMENT,
-> name varchar(20),
-> primary key(userid)
-> ) ENGINE=MyISAM;
Query OK, 0 rows affected (0.04 sec)
mysql> insert into user (name) values
-> ('rolando'),('pamela'),('dominique'),('carlik'),('diamond');
Query OK, 5 rows affected (0.01 sec)
Records: 5 Duplicates: 0 Warnings: 0
mysql> create table friends
-> (
-> userid INT NOT NULL,
-> friendid INT NOT NULL,
-> primary key (userid,friendid),
-> unique key (friendid,userid)
-> ) ENGINE=MyISAM;
Query OK, 0 rows affected (0.03 sec)
mysql> insert into friends values (1,2),(2,5),(1,3);
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0
mysql> select * from user;
+--------+-----------+
| userid | name |
+--------+-----------+
| 1 | rolando |
| 2 | pamela |
| 3 | dominique |
| 4 | carlik |
| 5 | diamond |
+--------+-----------+
5 rows in set (0.00 sec)
mysql> select * from friends;
+--------+----------+
| userid | friendid |
+--------+----------+
| 1 | 2 |
| 1 | 3 |
| 2 | 5 |
+--------+----------+
3 rows in set (0.00 sec)
mysql>
Давайте посмотрим на все отношения
mysql> SELECT A.userid,A.name,B.friendid,C.name
-> FROM user A
-> INNER JOIN friends B ON A.userid=B.userid
-> INNER JOIN user C on B.friendid=C.userid
-> ;
+--------+---------+----------+-----------+
| userid | name | friendid | name |
+--------+---------+----------+-----------+
| 1 | rolando | 2 | pamela |
| 1 | rolando | 3 | dominique |
| 2 | pamela | 5 | diamond |
+--------+---------+----------+-----------+
3 rows in set (0.00 sec)
mysql>
Давайте рассмотрим все 5 идентификаторов пользователей и посмотрим, правильно ли показаны отношения
mysql> SET @givenuserid = 1;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT B.name "Friend's Name"
-> FROM
-> (
-> SELECT userid FROM friends WHERE friendid=@givenuserid
-> UNION
-> SELECT friendid FROM friends WHERE userid=@givenuserid
-> ) A INNER JOIN user B USING (userid);
+---------------+
| Friend's Name |
+---------------+
| pamela |
| dominique |
+---------------+
2 rows in set (0.00 sec)
mysql> SET @givenuserid = 2;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT B.name "Friend's Name"
-> FROM
-> (
-> SELECT userid FROM friends WHERE friendid=@givenuserid
-> UNION
-> SELECT friendid FROM friends WHERE userid=@givenuserid
-> ) A INNER JOIN user B USING (userid);
+---------------+
| Friend's Name |
+---------------+
| rolando |
| diamond |
+---------------+
2 rows in set (0.00 sec)
mysql> SET @givenuserid = 3;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT B.name "Friend's Name"
-> FROM
-> (
-> SELECT userid FROM friends WHERE friendid=@givenuserid
-> UNION
-> SELECT friendid FROM friends WHERE userid=@givenuserid
-> ) A INNER JOIN user B USING (userid);
+---------------+
| Friend's Name |
+---------------+
| rolando |
+---------------+
1 row in set (0.01 sec)
mysql> SET @givenuserid = 4;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT B.name "Friend's Name"
-> FROM
-> (
-> SELECT userid FROM friends WHERE friendid=@givenuserid
-> UNION
-> SELECT friendid FROM friends WHERE userid=@givenuserid
-> ) A INNER JOIN user B USING (userid);
Empty set (0.00 sec)
mysql> SET @givenuserid = 5;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT B.name "Friend's Name"
-> FROM
-> (
-> SELECT userid FROM friends WHERE friendid=@givenuserid
-> UNION
-> SELECT friendid FROM friends WHERE userid=@givenuserid
-> ) A INNER JOIN user B USING (userid);
+---------------+
| Friend's Name |
+---------------+
| pamela |
+---------------+
1 row in set (0.00 sec)
mysql>
Все они выглядят правильно для меня.
Теперь давайте воспользуемся вашим вторым запросом, чтобы увидеть, соответствует ли он ...
mysql> SET @givenuserid = 1;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT u.name, f.friendid
-> FROM friends f inner join user u ON ( u.userid = f.friendid )
-> WHERE f.userid = @givenuserid;
+-----------+----------+
| name | friendid |
+-----------+----------+
| pamela | 2 |
| dominique | 3 |
+-----------+----------+
2 rows in set (0.00 sec)
mysql> SET @givenuserid = 2;
Query OK, 0 rows affected (0.01 sec)
mysql> SELECT u.name, f.friendid
-> FROM friends f inner join user u ON ( u.userid = f.friendid )
-> WHERE f.userid = @givenuserid;
+---------+----------+
| name | friendid |
+---------+----------+
| diamond | 5 |
+---------+----------+
1 row in set (0.00 sec)
mysql> SET @givenuserid = 3;
Query OK, 0 rows affected (0.01 sec)
mysql> SELECT u.name, f.friendid
-> FROM friends f inner join user u ON ( u.userid = f.friendid )
-> WHERE f.userid = @givenuserid;
Empty set (0.00 sec)
mysql> SET @givenuserid = 4;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT u.name, f.friendid
-> FROM friends f inner join user u ON ( u.userid = f.friendid )
-> WHERE f.userid = @givenuserid;
Empty set (0.00 sec)
mysql> SET @givenuserid = 5;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT u.name, f.friendid
-> FROM friends f inner join user u ON ( u.userid = f.friendid )
-> WHERE f.userid = @givenuserid;
Empty set (0.00 sec)
mysql>
Почему не совпадают? Это потому, что я не загружал (B,A)
для каждого (A,B)
. Позвольте мне загрузить (B,A)
отношения и повторить ваш второй запрос.
mysql> insert into friends values (2,1),(5,2),(3,1);
Query OK, 3 rows affected (0.02 sec)
Records: 3 Duplicates: 0 Warnings: 0
mysql> SET @givenuserid = 1;
Query OK, 0 rows affected (0.01 sec)
mysql> SELECT u.name, f.friendid
-> FROM friends f inner join user u ON ( u.userid = f.friendid )
-> WHERE f.userid = @givenuserid;
+-----------+----------+
| name | friendid |
+-----------+----------+
| pamela | 2 |
| dominique | 3 |
+-----------+----------+
2 rows in set (0.00 sec)
mysql> SET @givenuserid = 2;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT u.name, f.friendid
-> FROM friends f inner join user u ON ( u.userid = f.friendid )
-> WHERE f.userid = @givenuserid;
+---------+----------+
| name | friendid |
+---------+----------+
| rolando | 1 |
| diamond | 5 |
+---------+----------+
2 rows in set (0.00 sec)
mysql> SET @givenuserid = 3;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT u.name, f.friendid
-> FROM friends f inner join user u ON ( u.userid = f.friendid )
-> WHERE f.userid = @givenuserid;
+---------+----------+
| name | friendid |
+---------+----------+
| rolando | 1 |
+---------+----------+
1 row in set (0.00 sec)
mysql> SET @givenuserid = 4;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT u.name, f.friendid
-> FROM friends f inner join user u ON ( u.userid = f.friendid )
-> WHERE f.userid = @givenuserid;
Empty set (0.00 sec)
mysql> SET @givenuserid = 5;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT u.name, f.friendid
-> FROM friends f inner join user u ON ( u.userid = f.friendid )
-> WHERE f.userid = @givenuserid;
+--------+----------+
| name | friendid |
+--------+----------+
| pamela | 2 |
+--------+----------+
1 row in set (0.00 sec)
mysql>
Они все еще не совпадают. Это потому, что ваш второй запрос проверяет только одну сторону.
Давайте проверим ваш первый запрос по каждому значению только с (A, B) и не с (B, A):
mysql> SET @givenuserid = 1;
SELECT u.name, f.friendid userid, IF(f.userid = @givenuserid, f.friendid, f.userid) friendid
FROM friends f
inner join user u ON ( u.userid = IF(f.userid = @givenuserid, f.friendid, f.userid))
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT u.name, f.friendid userid, IF(f.userid = @givenuserid, f.friendid, f.userid) friendid
-> FROM friends f
-> inner join user u ON ( u.userid = IF(f.userid = @givenuserid, f.friendid, f.userid))
-> WHERE ( f.userid = @givenuserid or f.friendid = @givenuserid );
+-----------+--------+----------+
| name | userid | friendid |
+-----------+--------+----------+
| pamela | 2 | 2 |
| dominique | 3 | 3 |
+-----------+--------+----------+
2 rows in set (0.00 sec)
mysql> SET @givenuserid = 2;
FROM friends f
inner join user u ON ( u.userid = IF(f.userid = @givenuserid, f.friendid, f.userid))
WHERE ( f.userid = @givenuserid or f.friendid = @givenuserid );
Query OK, 0 rows affected (0.01 sec)
mysql> SELECT u.name, f.friendid userid, IF(f.userid = @givenuserid, f.friendid, f.userid) friendid
-> FROM friends f
-> inner join user u ON ( u.userid = IF(f.userid = @givenuserid, f.friendid, f.userid))
-> WHERE ( f.userid = @givenuserid or f.friendid = @givenuserid );
+---------+--------+----------+
| name | userid | friendid |
+---------+--------+----------+
| rolando | 2 | 1 |
| diamond | 5 | 5 |
+---------+--------+----------+
2 rows in set (0.00 sec)
mysql> SET @givenuserid = 3;
SELECT u.name, f.friendid userid, IF(f.userid = @givenuserid, f.friendid, f.userid) friendid
FROM friends f
inner join user u ON ( u.userid = IF(f.userid = @givenuserid, f.friendid, f.userid))
WHERE ( f.userid = @givenuserid or f.friendid = @givenuserid );
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT u.name, f.friendid userid, IF(f.userid = @givenuserid, f.friendid, f.userid) friendid
-> FROM friends f
-> inner join user u ON ( u.userid = IF(f.userid = @givenuserid, f.friendid, f.userid))
-> WHERE ( f.userid = @givenuserid or f.friendid = @givenuserid );
+---------+--------+----------+
| name | userid | friendid |
+---------+--------+----------+
| rolando | 3 | 1 |
+---------+--------+----------+
1 row in set (0.00 sec)
mysql> SET @givenuserid = 4;
FROM friends f
inner join user u ON ( u.userid = IF(f.userid = @givenuserid, f.friendid, f.userid))
WHERE ( f.userid = @givenuserid or f.friendid = @givenuserid );
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT u.name, f.friendid userid, IF(f.userid = @givenuserid, f.friendid, f.userid) friendid
-> FROM friends f
-> inner join user u ON ( u.userid = IF(f.userid = @givenuserid, f.friendid, f.userid))
-> WHERE ( f.userid = @givenuserid or f.friendid = @givenuserid );
Empty set (0.01 sec)
mysql> SET @givenuserid = 5;
FROM friends f
Query OK, 0 rows affected (0.00 sec)
inner join user u ON ( u.userid = IF(f.userid = @givenuserid, f.friendid, f.userid))
mysql> SELECT u.name, f.friendid userid, IF(f.userid = @givenuserid, f.friendid, f.userid) friendid
-> FROM friends f
-> inner join user u ON ( u.userid = IF(f.userid = @givenuserid, f.friendid, f.userid))
-> WHERE ( f.userid = @givenuserid or f.friendid = @givenuserid );
+--------+--------+----------+
| name | userid | friendid |
+--------+--------+----------+
| pamela | 5 | 2 |
+--------+--------+----------+
1 row in set (0.00 sec)
mysql>
Ваш первый работает хорошо. Я уверен, что он выигрывает от уникального индекса, как я уже говорил ранее, но ИМХО, я думаю, UNION проще. С чем уникальным индексом, это будет шесть из полутора десятков других с точки зрения исполнения и вывода.
Вы должны сравнить свой первый запрос с моим предложением UNION и посмотреть.
Это был хороший вопрос, который вы задали сегодня. +1 за твой вопрос.