Я работал над запросом MongoDB, который становится все более и более сложным. Я уже открыл пару тем, но я чувствую, что было бы лучше, чтобы объяснить весь мой случай и настроить запрос сразу, чтобы удовлетворить все мои потребности, потому что я чувствую, что мне нужно изменить все, как я хочу вводить все новые и новые условия.
Итак, для моего случая я хочу создать приложение для чата. Пользователи могут вести беседы один на один, а также групповые чаты с участием до 25 человек. Идея моей базы данных состоит в том, чтобы сохранить документ conversations
и документ messages
, которые связаны друг с другом с помощью _id из беседы. Это мой conversations
документ:
{
"_id" : ObjectId("5e35f2c840713a43aeeeb3d9"),
"n" : "Example Group Chat",
"members" : [
{
"uID" : "1",
"j" : 1580580922,
"i" : "1",
"r" : "admin",
"a" : 1
},
{
"uID" : "4",
"j" : 1580580922,
"l" : 1581863346,
"i" : "1",
"r" : "member",
"a" : 0
},
{
"uID" : "5",
"j" : 1580581922,
"i" : "1",
"r" : "member",
"a" : 1
},
{
"uID" : "9",
"j" : 1580593922,
"i" : "1",
"r" : "member",
"a" : 1
},
{
"uID" : "3",
"j" : 1580594920,
"i" : "1",
"r" : "member",
"a" : 1
},
{
"uID" : "8",
"j" : 1580594999,
"i" : "1",
"r" : "member",
"a" : 1
}
]
}
{
"_id" : ObjectId("5e39d5d740713a43aeef5b26"),
"members" : [
{
"uID" : "1",
"j" : 1580580922,
"i" : "1",
"r" : "member",
"a" : 1
},
{
"uID" : "2",
"j" : 1580580922,
"i" : "1",
"r" : "member",
"a" : 1
}
]
}
. Вы можете видеть групповой чат (первый) и чат один на один (второй). Групповые чаты могут иметь имена (n), индивидуальные чаты - нет. Каждый диалог имеет массив участников, в котором хранятся идентификатор пользователя (uID), дата присоединения (j), оставленная дата (l), поле приглашенного userID (i), поле роли (r) и активное поле ( а). Мне, вероятно, не понадобится поле "active", так как у меня есть отметка времени соединения / оставления, но все же. Я, вероятно, удалю его позже, так что это поле, вероятно, не будет включено.
Далее у меня есть messages
документ следующим образом:
{
"_id" : ObjectId("5e4917bca59ce44ef2770086"),
"c_ID" : ObjectId("5e35f2c840713a43aeeeb3d9"),
"msg" : "Whats good?",
"fromID" : "1",
"__v" : 0,
"t" : 1582369525,
"d" : {
"4" : 1582369525
},
"r" : {
"4" : 1582369525
}
}
Это содержит само сообщение (msg), пользователь, который его отправил (fromID), отметка времени в UNIX Epoch (t) и вложенные коллекции доставок (d) и reads (r) и, конечно, объект ObjectID разговора (c_ID).
В настоящее время у меня есть следующий запрос, после большой помощи здесь, в StackOverflow:
db.conversations.aggregate([
{
$lookup: {
from: "messages",
foreignField: "c_ID",
localField: "_id",
as: "messages"
}
},
{
"$unwind": "$messages"
},
{
"$sort": {
"messages.t": -1
}
},
{
"$group": {
"_id": "$_id",
"lastMessage": {
"$first": "$messages"
},
"allFields": {
"$first": "$$ROOT"
}
}
},
{
"$replaceRoot": {
"newRoot": {
"$mergeObjects": [
"$allFields",
{
"lastMessage": "$lastMessage"
}
]
}
}
},
{
$project: {
messages: 0
}
},
{
$match: {
"members.uID": "1"
}
},
{
$sort: {
"lastMessage.t": 1
}
},
{
$limit: 10
},
{
$project: {
members: {
$filter: {
input: "$members",
as: "member",
cond: {
$and: [
{
$ne: [
"$$member.uID",
"1"
]
},
{
$or: [
{
$eq: [
"$$member.l",
undefined
]
},
{
$lt: [
"$$member.l",
"$$member.j"
]
}
]
}
]
}
}
},
n: 1,
lastMessage: 1
}
}
])
Я столкнулся с парой проблем с выше:
- Мне нужно убедиться, что Участнику по-прежнему разрешено видеть разговор. Если они уходят, и, таким образом, поле
l
устанавливается для члена с отметкой времени, превышающей дату присоединения, пользователь все еще может видеть этот диалог, но последнее сообщение должно быть перед левой отметкой времени. Например: участник с userID 4 покинул групповой чат, но чат все еще виден в его обзоре с сообщениями ДО его оставленной даты (1581863346
) - Я хочу убедиться, что lastMessage всегда Последнее сообщение, прежде чем кто-то оставил. Опять же, в приведенном выше примере пользователю с идентификатором пользователя 4 разрешено просматривать только сообщения с временными метками, меньшими, чем его левая дата (поэтому в данном конкретном случае c до
1581863346
) - РЕДАКТИРОВАТЬ: Это более приятно иметь необязательные для основной функциональности, но было бы здорово включить поле
memberCount
, которое является просто количество пользователей, основываясь на тех же критериях: нет оставленной даты (не определено) или дата присоединения больше, чем оставленная дата, поэтому получите «фактическое» количество членов, находящихся в настоящее время в группе.
Есть идеи о том, как улучшить мой запрос, чтобы включить два вышеуказанных условия? Или также есть способ оптимизировать мой запрос по производительности? Приветствия!
Кстати: это может быть важно, но я думаю, что это имеет смысл, если вы прочитали все до конца: этот запрос будет использоваться в обзоре всех ваших активных бесед, отсортированных по метке времени. Что вы увидите в первую очередь, если откроете Facebook Messenger или WhatsApp, скажем.
РЕДАКТИРОВАТЬ: Основываясь на ответе @Tunmee, я нашел еще два условия, которые необходимо было применить: - Последнее сообщение должно быть опубликовано не только до левой даты (l
), но также и после даты присоединения (j
). - Я хочу возвращать только те разговоры, в которых извлекается последнее сообщение, поэтому только при наличии сообщения чата, которое нужно показать пользователю.
Я задал следующий запрос. Это выглядит хорошо? Есть какие-то проблемы?
db.conversations.aggregate([
{
$match: {
"members.uID": "4"
}
},
{
$addFields: {
user: {
$arrayElemAt: [
{
$filter: {
input: "$members",
as: "member",
cond: {
$eq: [
"$$member.uID",
"4"
]
}
}
},
0
]
}
}
},
{
$lookup: {
from: "messages",
let: {
user: "$user",
conversatoinId: "$_id"
},
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: [
"$c_ID",
"$$conversatoinId"
]
},
{
$cond: {
if: {
$lt: [
{
$ifNull: [
"$$user.l",
0
]
},
"$$user.j"
]
},
then: true,
else: {
$lt: [
"$t",
"$$user.l"
]
}
}
},
{
$gt: [
"$t",
"$$user.j"
]
}
]
}
},
},
{
$sort: {
"t": -1
},
}
],
as: "messages"
}
},
{
$project: {
lastMessage: {
$arrayElemAt: [
"$messages",
0
]
},
n: 1,
members: 1
}
},
{
$sort: {
"lastMessage.t": 1
}
},
{
$project: {
members: {
$filter: {
input: "$members",
as: "member",
cond: {
$and: [
{
$ne: [
"$$member.uID",
"4"
]
},
{
$or: [
{
$eq: [
"$$member.l",
undefined
]
},
{
$lt: [
"$$member.l",
"$$member.j"
]
}
]
}
]
}
}
},
memberCount: {
$size: {
$filter: {
input: "$members",
as: "member",
cond: {
$and: [
{
$ne: [
"$$member.uID",
"4"
]
},
{
$or: [
{
$eq: [
"$$member.l",
undefined
]
},
{
$lt: [
"$$member.l",
"$$member.j"
]
}
]
}
]
}
}
}
},
n: 1,
lastMessage: 1
}
},
{
$match: {
lastMessage: {
$exists: true
}
}
},
{
$limit: 10
}
])
РЕДАКТИРОВАТЬ: Насколько я могу судить, все это выглядит хорошо в данный момент. Единственное, чего мне не хватает сейчас, так это того, что можно уловить следующую ситуацию: представьте, что есть групповой чат с 20 участниками. Название чата "Веселые пятницы". userID 4 присоединяется к этому групповому чату (я установил для поля j
(объединение) значение 1582475543
(отметка времени), участвует в течение двух недель, а затем уходит (я установил для поля l
(слева) значение 1583685143
(отметка времени)) Это все будет работать нормально. Однако, как я могу снова добавить userID 4 в тот же групповой чат через 1 неделю (отметка времени 1584289943
) и убедиться, что userID 4 может видеть lastMessage, ЕСЛИ это либо между первым подключением / оставил ИЛИ, если он будет опубликован после того, как он присоединился снова?
Я хотел бы иметь возможность добавлять одного и того же пользователя более одного раза в массив members, но с разными j
(и l
) полей, а затем запросите lastMessage, чтобы быть между одним из них, что позволит для того, что я хочу сделать, как описано выше.