MongoDB — запрос, чтобы получить предметы, а также узнать, проголосовал ли я за них

Этот вопрос связан с этим: простая система голосования с MongoDB

У меня есть коллекция элементов, за которые пользователи могут проголосовать за или против. Интересно, как я могу получить некоторые предметы и в то же время узнать, проголосовал ли я за них или против.

Коллекция элементов содержит элементы, более или менее похожие на следующие:

{
  _id:0,
  name:'Item 0',
  upVoters: [ 1, 2 ],
  downVoters: [ 3, 4 ]
}
{
  _id:1,
  name:'Item 1',
  upVoters: [ 1, 3 ],
  downVoters: [ 4 ]
}
{
  _id:2,
  name:'Item 2',
  upVoters: [ ],
  downVoters: [ 2 ]
}

Это означает, что, например, за элемент 0 проголосовали пользователи с идентификаторами 1 и 2, а против — пользователи 3 и 4.

Если я являюсь пользователем 2 и получаю эти элементы, я хочу знать, что я проголосовал за элемент 0, не проголосовал за элемент 1 и проголосовал против элемента 2.

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

Ожидаемый результат, если я запрошу элементы с точки зрения пользователя 2:

{
  _id:0,
  name:'Item 0',
  user_vote: 1
}
{
  _id:1,
  name:'Item 1',
  user_vote: 0
}
{
  _id:2,
  name:'Item 2',
  user_vote: -1
}

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

Альтернативная модель:

{
  _id:0,
  name:'Item 0',
  voters: [
    { id:1, vote:+1}, { id:2, vote:+1},
    { id:3, vote:-1}, { id:4, vote:-1}
  ]
}
{
  _id:1,
  name:'Item 1',
  voters: [
    { id:1, vote:+1}, { id:3, vote:+1},
    { id:4, vote:-1}
  ]
}
{
  _id:2,
  name:'Item 2',
  voters: [
    { id:2, vote:-1}
  ]
}

Спасибо большое

-- Ферран

РЕДАКТИРОВАТЬ:

Как я уже говорил в своих комментариях, решение, которое я нашел до сих пор, состоит в том, чтобы отправить 3 запроса: один, чтобы получить элементы, за которые проголосовали, другой, чтобы получить проголосовавшие против, и еще один, чтобы получить не проголосовавшие:

Временное решение для получения элементов и голосов за пользователя

db.items.find({ upVoters:USER_ID })
db.items.find({ downVoters:USER_ID })
db.items.find({ upVoters:{$ne:USER_ID}, downVoters:{$ne:USER_ID} })

person Ferran Maylinch    schedule 12.09.2013    source источник
comment
Одно решение, которое я нашел до сих пор, - запросить 3 раза. Один, чтобы получить элементы, за которые проголосовали, другой, чтобы получить проголосовавшие против, и третий, чтобы получить не проголосовавшие. Я комментирую это в ответе @Tamas.   -  person Ferran Maylinch    schedule 13.09.2013
comment
Также было бы здорово, если бы я мог уменьшить массив до логического значения, как это предлагается здесь: stackoverflow.com/questions/16240382/. Таким образом, я мог получить логическое значение для upVote и другое для downVote.   -  person Ferran Maylinch    schedule 13.09.2013
comment
Я думаю, что не могу использовать $cond, как они делают здесь потому что кажется, что $cond нельзя использовать для проверки того, находится ли значение внутри массива.   -  person Ferran Maylinch    schedule 13.09.2013


Ответы (2)


Если вы используете свой второй пример в качестве схемы, вы можете выполнить агрегацию следующим образом:

db.coll2.aggregate([
   // Split items by vote
   {$unwind:"$voters"},
   // Set vote to "0" for votes the user didn't cast
   {$project:{"vote":{$cond:[{$eq:["$voters.id",2]},"$voters.vote",0]}}},
   // Sum the votes for each item for this user (will get rid of 0-vote if user actually voted)
   {$group:{"_id":"$_id", "vote":{$sum:"$vote"}}}])

для достижения желаемого результата. Одно предостережение заключается в том, что если за элемент нет голосов, то шаг $unwind не позволит этому документу пройти через остальную часть конвейера, поэтому вы не узнаете, что данный пользователь не проголосовал за него. Это было бы проблемой также при попытке выполнить запрос агрегации для вашей первой предложенной схемы, поскольку интуитивно нам пришлось бы $unwind как для upVoters, так и для downVoters, и если бы один из этих массивов был пуст, то документ не прошел бы через трубопровод.

Если вы пытаетесь получить доступ к тем голосам, которые часто отдавал пользователь, то, возможно, стоит хранить голоса каким-то образом, который позволит вам построить для них индекс на user_id и/или item_id (приведенный выше запрос агрегации не может использовать преимущество индекс). Вы можете хранить голоса в собственной коллекции, например:

{
    item_id:0,
    user_id:1,
    vote:1
}
{
    item_id:0,
    user_id:4,
    vote:-1
}
{
    item_id:2,
    user_id:2,
    vote:-1
}

Это позволяет вам извлекать нужные данные без агрегирования и может быть гораздо более быстрым вариантом с индексами user_id и item_id.

person llovett    schedule 19.09.2013

На мой взгляд, вам, вероятно, придется $раскрутить массивы upVoters и downVoters, а затем также выполнить сопоставление, что-то вроде:

db.votes.aggregate([
  {$unwind: "$upVoters"},
  {$group: {"_id": "upVoters"}},
  {$match:{"_id.upVoters": {"$in": [2]}}}
])

(Я примерно на 99% уверен, что вам нужно немного переделать это :))

person Tamas    schedule 12.09.2013
comment
Спасибо, но я думаю, что этот запрос получает элементы, за которые проголосовал пользователь. Затем вам нужно будет отправить еще один запрос, чтобы получить элементы, за которые проголосовали, и еще один, чтобы получить элементы, за которые не проголосовали. На самом деле вы можете сделать это без агрегации: db.items.find({upVoters:2}). Пока что это лучшее решение, которое я нашел, хотя оно мне не очень нравится. - person Ferran Maylinch; 13.09.2013