Первое, что , вы не должны реализовывать мой первый ответ . Вместо этого вам следует изменить или ограничить вашу схему . Ниже приведены мои предложения.
Как я понимаю вашу схему, это:
create table Friends (
user_Id1 int,
user_Id2 int,
status int);
Если в любое время существуют отношения с другом, один из идентификаторов находится в позиции 1, а 1 - в позиции 2.
Теперь, предполагая, что мой идентификатор 1212, мой список идентификаторов друга:
select user_Id1 as my_friends_userId
from Friends f
where f.user_Id2 = '1212'
and status = 1
union
select user_Id2 as my_friends_userId
from Friends f
where f.user_Id1 = '1212'
and status = 1;
Список идентификаторов друзей моих друзей:
select f1.user_id1 as friends_of_friends
from Friends f1
where f1.user_Id2 in (select user_Id1 as my_friends_userId
from Friends f
where f.user_Id2 = '1212'
and status = 1
union
select user_Id2 as my_friends_userId
from Friends f
where f.user_Id1 = '1212'
and status = 1)
union
select user_id2 as friends_of_friends
from Friends f1
where f1.user_Id1 in (
select user_Id1 as my_friends_userId
from Friends f
where f.user_Id2 = '1212'
and status = 1
union
select user_Id1 as my_friends_userId
from Friends f
where f.user_Id1 = '1212'
and status = 1);
А затем, чтобы добавить исключения для моих друзей и друзей, которых я заблокировал, это становится:
select f1.user_id1 as friends_of_friends
from Friends f1
where f1.user_Id2 in (select user_Id1 as my_friends_userId /* sub-query for friends of friends */
from Friends f
where f.user_Id2 = '1212'
and status = 1
union
select user_Id2 as my_friends_userId
from Friends f
where f.user_Id1 = '1212'
and status = 1)
and f1.user_id1 not in /* exclusion of my own friends */
(select user_Id1 as my_friends_userId
from Friends f
where f.user_Id2 = '1212'
and status = 1
union
select user_Id2 as my_friends_userId
from Friends f
where f.user_Id1 = '1212'
and status = 1
)
and f1.user_id1 != '1212' /* exclusion of myself. */
and f1.user_id1 not in (select user_Id1 as my_friends_userId /* exlusion of people I've blocked. */
from Friends f
where f.user_Id2 = '1212'
and status = 3
union
select user_Id2 as my_friends_userId
from Friends f
where f.user_Id1 = '1212'
and status = 3
)
union /* Now do it all over again for user_id2 */
select f2.user_id2 as friends_of_friends
from Friends f2
where f2.user_Id1 in (select user_Id1 as my_friends_userId
from Friends f
where f.user_Id2 = '1212'
and status = 1
union
select user_Id2 as my_friends_userId
from Friends f
where f.user_Id1 = '1212'
and status = 1)
and f2.user_id2 not in
(select user_Id1 as my_friends_userId
from Friends f
where f.user_Id2 = '1212'
and status = 1
union
select user_Id2 as my_friends_userId
from Friends f
where f.user_Id1 = '1212'
and status = 1
)
and f2.user_id2 != '1212'
and f2.user_id2 not in (select user_Id1 as my_friends_userId
from Friends f
where f.user_Id2 = '1212'
and status = 3
union
select user_Id2 as my_friends_userId
from Friends f
where f.user_Id1 = '1212'
and status = 3
)
, где я отметил первый раз для каждого из этих условий. Теперь вы можете видеть беспорядок union
, который я должен был сделать для этого. (Что, вероятно, должно быть union distinct
)
Вы не должны создавать предложение с group-concat. Несмотря на длину здесь, это быстрее.
Вы можете спросить, что делают отдельные части. Но опять же, мой совет: НЕ ДЕЛАЙТЕ ЭТОГО . Вот почему хороший дизайн стола делает вещи намного проще.
SQL Fiddle для справки и отображения результатов: http://sqlfiddle.com/#!2/e6376/13
РЕДАКТИРОВАТЬ: Просто чтобы добавить, как я бы изменить эту схему.
Неясно, если в вашем приложении отношения между друзьями - это отношения Google (разрешены асимметричные отношения) или Facebook (разрешены только симметричные отношения).
В обоих случаях я бы изменил схему на:
create table Friends (
individual_userId int,
friend_userId int,
status int);
В гугле, дело, все готово. В случае с Facebook я бы использовал эту структуру, но потребовал бы, чтобы для каждого отношения в таблицу помещались две строки. Таким образом, если «1212» - это друзья на Facebook с «0415», то есть (индивидуальный_пользователь, идентификатор_пользователя) строк («1212», «0415») & («0415», «1212»). Чтобы это работало и поддерживалось, потребуются хранимые процедуры для вставок / удалений, чтобы убедиться, что обе строки добавлены и удалены. (Обновления нет - это уникальные идентификаторы.)
Если мы уверены, что эти отношения поддерживаются и что друг, инициирующий отношения, всегда присутствует в индивидуальном_пользователе, тогда мой последний запрос будет:
select f1.friend_userId as friends_of_friends
from Friends f1
where f1.individual_userId in ( /* retrieve my friend list */
select friend_userId as my_friends_userId
from Friends f
where f.individual_userId = '1212'
and status = 1)
and f1.friend_userId not in ( /* exclusion of my own friends */
select friend_userId as my_friends_userId
from Friends f
where f.individual_userId = '1212'
and status = 1
)
and f1.friend_userId not in ( /* exlusion of people I have blocked. */
select friend_userId as my_friends_userId
from Friends f
where f.individual_userId = '1212'
and status = 3
)
and f1.friend_userId != '1212' /* exclusion of myself. */
, с которым гораздо проще иметь дело. Вы также можете переписать это как серию объединений, но я подозреваю, что в качестве первого шага, с помощью таких фраз in
и not in
легче читать.
Пересмотрено sqlfiddle: http://sqlfiddle.com/#!2/92ff2/1
(Я должен был бы протестировать его с большим набором данных, но моя интуиция говорит, что объединения будут быстрее - но для такого кода я подозреваю, что изучение / получение правильного ответа важнее, чем первоначальная оптимизация для скорость.)