Я пытаюсь реализовать функцию, которая собирает непрочитанные сообщения из коллекции статей. В каждой статье в сборнике есть запись «обсуждение» с субдокументами комментариев к обсуждению. Примером такого поддокументе является:Mongo Aggregate: как сравнить с полем из другой коллекции?
{
"id": NumberLong(7534),
"user": DBRef("users", ObjectId("...")),
"dt_create": ISODate("2015-01-26T00:10:44Z"),
"content": "The discussion comment content"
}
Родительский документ имеет следующую (частичную) структуру:
{
model: {
id: 17676,
title: "Article title",
author: DBRef("users", ObjectId(...)),
// a bunch of other fields here
},
statistics: {
// Statistics will be stored here (pageviews, etc)
},
discussions: [
// Array of discussion subdocuments, like the one above
]
}
Каждый пользователь также имеет last_viewed
запись, которая является документом, примером может служить следующее :
{
"17676" : "2015-01-10T00:00:00.000Z",
"18038" : "2015-01-10T00:00:00.000Z",
"18242" : "2015-01-20T00:00:00.000Z",
"18325" : "2015-01-20T00:00:00.000Z"
}
Это означает, что пользователь смотрел на обсуждение комментариев в последний раз 10 января 2015 года для статей с идентификаторами 17676 и 18038, а 20 января го 2015 года для статей с идентификаторами 18242 и 18325.
Поэтому я хочу собрать записи обсуждения из документов статьи, а для статьи с ID 17676 я хочу собрать записи обсуждения, созданные после 2015-01-10, и для статьи с ID 18242 я хочу показать записи обсуждения, созданные после 2015-01-20.
ОБНОВЛЕНО
Основываясь на Neil Lunn's reply, функция я создал до сих пор:
function getUnreadDiscussions(userid) {
user = db.users.findOne({ 'model.id': userid });
last_viewed = [];
for(var i in user.last_viewed) {
last_viewed.push({
'id': parseInt(i),
'dt': user.last_viewed[i]
});
}
result = db.articles.aggregate([
// For now, collect just articles the user has written
{ $match: { 'model.author': DBRef('users', user._id) } },
{ $unwind: '$discussions' },
{ $project: {
'model': '$model',
'discussions': '$discussions',
'last_viewed': {
'$let': {
'vars': { 'last_viewed': last_viewed },
'in': {
'$setDifference': [
{ '$map': {
'input': '$$last_viewed',
'as': 'last_viewed',
'in': {
'$cond': [
{ '$eq': [ '$$last_viewed.id', '$model.id' ] },
'$$last_viewed.dt',
false
]
}
} },
[ false ]
]
}
}
}
}
},
// To get a scalar instead of a 1-element array:
{ $unwind: '$last_viewed' },
// Match only those that were created after last_viewed
{ $match: { 'discussions.dt_create': { $gt: '$last_viewed' } } },
{ $project: {
'model.id': 1,
'model.title': 1,
'discussions': 1,
'last_viewed': 1
} }
]);
return result.toArray();
}
Вся $let
вещь, и $unwind
после того, преобразует данные в следующую частичную проекцию (с последними $match
не заполнено):
{
"_id" : ObjectId("54d9af1dca71d8054c8d0ee3"),
"model" : {
"id" : NumberLong(18325),
"title" : "Article title"
},
"discussions" : {
"id" : NumberLong(7543),
"user" : DBRef("users", ObjectId("54d9ae24ca71d8054c8b4567")),
"dt_create" : ISODate("2015-01-26T00:10:44Z"),
"content" : "Some comment here"
},
"last_viewed" : ISODate("2015-01-20T00:00:00Z")
},
{
"_id" : ObjectId("54d9af1dca71d8054c8d0ee3"),
"model" : {
"id" : NumberLong(18325),
"title" : "Article title"
},
"discussions" : {
"id" : NumberLong(7554),
"user" : DBRef("users", ObjectId("54d9ae24ca71d8054c8b4567")),
"dt_create" : ISODate("2015-01-26T02:03:22Z"),
"content" : "Another comment here"
},
"last_viewed" : ISODate("2015-01-20T00:00:00Z")
}
Пока все хорошо. Но проблема в том, что $match
для выбора только обсуждений, созданных после даты last_viewed
, не работает. Я получаю пустой ответ массива. Однако, если я скорректирую дату и поставлю $match: { 'discussions.dt_create': { $gt: ISODate("2015-01-20 00:00:00") } }
, она работает. Но я хочу, чтобы он взял его с last_viewed
.
Поток выглядит следующим образом: сначала выполняется сценарий java в коде, затем код агрегации передается на сервер. Таким образом, оценка 'last_viewed ['$ model.id']' происходит до того, как выполняется код агрегации. Переменная 'last_viewed' не имеет свойства' $ model.id', что приводит к неопределенному значению. Следовательно, это неверный путь. – BatScream
Спасибо @BatScream. Да, я нашел это: http://stackoverflow.com/questions/24335983/how-to-use-a-javascript-object-inside-mongodb-aggregation-pipeline, и он упоминает, что внешняя переменная действительно не будет доступна в пределах процесс агрегации.Я смотрю на ответ Нейла там, задаваясь вопросом, могу ли я переформатировать свой объект last_viewed таким образом и посмотреть, могу ли я заставить его работать, используя '$ let'. Но я не уверен, как работает блок '$ let'. –
Вы не сможете динамически ссылаться на значение поля, которое вы пытаетесь сделать. Было бы неплохо, если бы вы могли опубликовать полный пример родительского документа записей обсуждения, потому что тогда я мог бы более конкретно рассказать о том, как следует структурировать вещи, но я думаю, что лучший путь вперед - это иметь записи обсуждения отдельные документы. Вероятно, вам нужно будет использовать один запрос для каждой статьи, чтобы получить непрочитанные обсуждения. – wdberkeley