2013-04-09 4 views
0

Мне нужно отобразить список сообщений. Для каждого поста, я должен также показать:Как это сделать в одном запросе?

  1. Сколько людей «любят» сообщение.
  2. Три имени тех, кто «любит» сообщение (желательно, друзья просмотра пользователя).
  3. Если зритель «любит» сообщение, я бы хотел, чтобы он был одним из трех.

Я не знаю, как это сделать без запроса каждого элемента в цикле for, который оказывается очень медленным. Конечно, кэширование/денормализация помогут, но я хотел бы знать, можно ли это сделать иначе. Как это делает facebook?

Предполагая, что эта базовая структура db, любые предложения?

users 
----- 
id 
username 

posts 
--------- 
id 
user_id 
content 

friendships 
----------- 
user_id 
friend_id 
is_confirmed (bool) 

users_liked_posts 
----------------- 
user_id 
post_id 

В качестве примечания стороны, если кто-либо знает, как это сделать в SQLAlchemy, это очень понравилось бы.

EDIT: SQLFiddle http://sqlfiddle.com/#!2/9e703

ответ

2

Вы можете попробовать это в вашем sqlfiddle. Условие «WHERE user_id = 2» нуждается в замене 2 на ваш текущий идентификатор пользователя.

SELECT numbered.* 
FROM 
(SELECT ranked.*, 
     IF ([email protected]_post, 
      @n := @n + 1, 
      @n := 1 AND @prev_post := post_id) as position 
FROM 
    (SELECT users_liked_posts.post_id, 
      users_liked_posts.user_id, 
      visitor.user_id as u1, 
      friendships.user_id as u2, 
      IF (visitor.user_id is not null, 1, IF(friendships.user_id is not null, 2, 3)) as rank 
    FROM users_liked_posts 
    INNER JOIN posts 
    ON  posts.id = users_liked_posts.post_id 
    LEFT JOIN friendships 
    ON  users_liked_posts.user_id = friendships.user_id 
    AND friendships.friend_id = posts.user_id 
    LEFT JOIN (SELECT post_id, user_id FROM users_liked_posts WHERE user_id = 2) visitor 
    ON  users_liked_posts.post_id = visitor.post_id 
    AND users_liked_posts.user_id = visitor.user_id 
    ORDER BY users_liked_posts.post_id, rank) as ranked 
    JOIN 
    (SELECT @n := 0, @prev_post := 0) as setup) as numbered 
WHERE numbered.position < 4 

Вы можете легко присоединиться к подзапросу «с номерами» с помощью «пользователей» таблицы, чтобы получить дополнительную информацию о пользователе. Есть дополнительные поля u2, u3, чтобы увидеть, что происходит. Вы можете удалить их.

Общее представление о запросе:

1) левый присоединиться users_liked_posts с собой два раза. В первый раз он ограничен текущим посетителем, создавая посетителей подзапроса. Второй раз ограничивается друзьями.

2) ранг столбца, IF (visitor.user_id не равно null, 1, IF (friendships.user_id не равно null, 2, 3)), присваивает ранг каждому пользователю в users_liked_posts. Этот запрос сортируется по почте и по рангу.

3) использовать предыдущие в качестве подзапроса для создания тех же данных, но с текущей позиции для пользователей за сообщение.

4) использовать предыдущий как подзапрос, чтобы извлечь верхние 3 позиции за сообщение.

Нет, эти шаги не могут быть объединены, в частности потому, что MySQL не позволяет использовать вычисляемый столбец псевдонимом в условии WHERE.

+0

Спасибо за ответ, но я думаю, что вы неправильно поняли - возможно, моя вина. В настоящее время я запускаю один запрос, чтобы получить все сообщения. В цикле for я запускаю отдельный запрос для каждого сообщения, чтобы получить всю другую информацию. Так что, если есть 50 сообщений, это в общей сложности 51 запрос. Мне было интересно, есть ли способ сконденсировать все в один запрос (получить все сообщения, а также все связанные данные для каждого сообщения). Кажется, что ваш ответ по-прежнему запрашивает цикл for. – BDuelz

+0

Я вижу, это тоже выполнимо, но немного сложнее. Не могли бы вы создать sqlfiddle с образцами данных для тестирования? – koriander

+0

Есть у него - http://sqlfiddle.com/#!2/9e703 – BDuelz

1

@koriander дал ответ SQL, но что касается того, как Facebook это делает, вы уже частично ответили на это; они используют высоко денормализованные данные и кэширование. Кроме того, они реализуют атомные счетчики, кратные списки в памяти для выполнения обходов графика, и они, безусловно, не используют понятия реляционных баз данных (например, JOIN), поскольку они не масштабируются. Даже кластеры MySQL, которые они запускают, по существу являются просто парами ключ/значение, которые доступны только при промахе в уровне кэша.

Вместо RDBS, я мог бы предложить базу данных графа для ваших целей, как neo4j

Удачи.

EDIT:

Вы действительно придется играть с Neo4j, если вы заинтересованы в его использовании. Вы можете найти или не найти его проще, исходя из фона SQL, но он, безусловно, обеспечит более мощный и, скорее всего, быстрее, запросы для выполнения обходов графика.

Вот несколько примеров запросов Cypher, которые могут быть вам полезны.

Граф, как много людей, как пост:

START post=node({postId}) 
MATCH post<-[:like]-user 
RETURN count(*) 

(на самом деле вы должны использовать атомный счетчик, вместо этого, если это то, что вы собираетесь быть запрашивая много)

Получить три человека, которые любили постсо следующими ограничениями:

  1. Первый likingUser всегда будет настоящим пользователем, если ему понравится сообщение.
  2. Если друзьям нынешнего пользователя понравился пост, они появятся перед любыми друзьями.
 
START post=node({postId}), user=node({currentUserId}) 
MATCH path = post<-[:like]-likingUser-[r?:friend*0..1]-user 
RETURN likingUser, count(r) as rc, length(path) as len 
ORDER BY rc desc, len asc 
LIMIT 3 

Я попытаюсь объяснить выше запрос ... если я могу.

  1. Start, захватывая два узла, в post и текущий user
  2. Совпадение по всем пользователям, которые как пост (likingUser)
  3. Кроме того, тест есть ли путь длины 0 или 1, который соединяет likingUser через Дружба отношение к текущему user (путь длины 0 указывает, что likingUser==user).
  4. Теперь, закажите его первым, независимо от того, имеет отношение r (он будет существовать, если likingUser является другом с user или если likingUser==user). Таким образом, count(r) будет либо 0, либо 1 для каждого результата. Поскольку мы предпочитаем результаты, где count(r)==1, мы отсортируем его по убыванию.
  5. Затем выполните второстепенную сортировку, которая заставляет текущий user в начало списка, если он был частью набора результатов. Мы делаем это, проверяя длину path. Когда user==likingUser, длина пути будет короче, чем когда user является другом likingUser, поэтому мы можем использовать length(path), чтобы заставить user вверх, сортируя в порядке возрастания.
  6. Наконец, мы ограничиваем результаты только тремя результатами.

Надеюсь, это имеет смысл. В качестве дополнительной заметки вы можете получить лучшую производительность, отделив свои запросы.Например, один запрос, чтобы узнать, нравится ли пользователю сообщение, а затем другой, чтобы получить до трех друзей, которым понравился пост, и, наконец, еще один, чтобы получить до трех не-друзей, которым нравится сообщение. Я говорю, что это может быть быстрее, потому что каждый запрос может замыкаться после получения трех результатов, тогда как большой один запрос, который я написал, должен учитывать все возможности, а затем сортировать их. Поэтому просто имейте в виду, что только потому, что вы можете объединить несколько вопросов в один запрос, он может фактически выполнять хуже, чем несколько запросов.

+1

Ваш ответ заставил меня задуматься, и я заглянул в neo4j. Можете ли вы привести пример того, как это может работать с помощью neo4j (предпочтительно в коде)? – BDuelz

+0

См. Мое редактирование. Я думаю, что если вы дадите ему некоторое время, вы обнаружите, что граф является естественным представлением социальной сети, и что база данных графов будет выполнять реляционную базу данных довольно радикально для операций, связанных с графикой. –

+0

Есть ли способ получить все сообщения при одновременном выполнении этого запроса для каждого сообщения, или я ограничусь выполнением этого запроса в цикле. – BDuelz

Смежные вопросы