random document aggregate by two or more fields - mongodb

I have questions list and they have structure like this:
{
"_id": {
"$oid": "5a077c418fdf294df73bf7ea"
},
"author": "PinkyaRabbit",
"question": "Что здесь литерал: var x = {};",
"category": "Javascript шаблоны",
"answers": [
"var x",
"{}",
"var x = {};",
"Нету тут его"
],
"real": "{}",
"description": "Литерал, это нейкое чистое значение, которое встречается в коде. Цифра, слово, объект - что угодно",
"users": [
{
"status": "good",
"wasToday": true,
"user": 471317129
}
],
"blockedBy": [],
"date": "2017-11-12T01:40:01+03:00"
}
I need to pick one random, question for user. Each user have personal categories list. blockedBy its an array if user dont want to take. I pick personal categories list like array user.categories and try to make aggregate request.
TGquestions.aggregate([
{ $match: {
users:{
"status": "clear",
"wasToday": false,
"user": chatid
},
category: { $in: user.categories },
blockedBy: { $ne: chatid}
}},
{ $sample: { size: 1 } }
], (err, qq) => {
The problem is that looks like my random not working atall. For example, first picked question from category with name "Javascript", next will be same from "Javascript", and white question not ends all next will be from this category. When question in this category ends, with next category same problem. The worst in this is that queue of categories each time same. So my random aggregate totaly failed. How to fix this?
upd. example
I have a user
{
"_id": {
"$oid": "5a15aa31457bca063c01248b"
},
"chatid": 213229659,
"username": "iamRB01",
"baned": false,
"name": "Rina",
"categories": ["Javascript", "Java"],
}
And some questions from some categories. I changed _id's to make all more readable.
{
"_id": "1",
"author": "PinkyaRabbit",
"question": "Что здесь литерал: var x = {};",
"category": "Javascript",
"answers": [
"var x",
"{}",
"var x = {};",
"Нету тут его"
],
"real": "{}",
"description": "Литерал, это нейкое чистое значение, которое встречается в коде. Цифра, слово, объект - что угодно",
"users": [
{
"status": "clear",
"wasToday": false,
"user": 213229659
}
],
"blockedBy": [],
"date": "2017-11-12T01:40:01+03:00"
},
{
"_id": "2",
"author": "PinkyaRabbit",
"question": "Первым признаком конструктора является?",
"category": "Javascript",
"answers": [
"слово new",
"метод construct",
"имя пишется с большой буквы",
"возможно обращение через this"
],
"real": "имя пишется с большой буквы",
"description": "Если имя чего-то пишется с большой буквы в Javascript, то это конструктор (если код писал не криворукий рак)",
"users": [
{
"status": "clear",
"wasToday": false,
"user": 213229659
}
],
"blockedBy": [],
"date": "2017-11-13T16:20:50+03:00"
},
{
"_id": "3",
"author": "Xiroho",
"question": "Для записи пятеричной системы счисления используются цифры -",
"category": "Java",
"answers": [
"01234",
"0123456789",
"01",
"10"
],
"real": "01234",
"description": "",
"users": [
{
"status": "clear",
"wasToday": false,
"user": 213229659
}
],
"date": "2017-11-20T23:31:03+03:00",
"blockedBy": []
},
{
"_id": "4",
"author": "PinkyaRabbit",
"question": "Как проверить, есть ли элемент в массиве",
"category": "Javascript",
"answers": [
"сделать цикл с проверкой if(haystack[i]===needle){return true;}",
"использовать проверку через filter",
"использовать функцию indexOf",
"использовать функцию includes"
],
"real": "использовать функцию includes",
"users": [
{
"status": "clear",
"wasToday": false,
"user": 213229659
}
],
"description": "Хотя indexOf работает верно\nif(array.indexOf(\"test\") > -1){ result++; }\nона проигрывает includes по быстродействию\narray.includes(\"test\")\nпоэтому стоит использовать includes. Тем не менее, не все браузеры поддерживают includes, которая появилась только в 2016 году, поэтому indexOf знать тоже надо",
"blockedBy": [213229659],
"date": "2017-12-01T01:21:09+03:00"
}
I want to pick one random question of this collection. For example first id=2, next with id 3, next with id 1 but not with id 4 cuz user turned off this question. But in my script looks like aggregate random not working... =(

The answer was that in nested arrays for picking real results needed to use $elemMatch.
TGquestions.aggregate([
{ $match: {
category: { $in: user.categories },
users: {
$elemMatch: {
"status": "clear",
"wasToday": false,
"user": chatid
}},
blockedBy: { $ne: chatid}
}},
{ $sample: { size: 1 } }
], (err, qq) => {

Related

Enrich array of objects with another document based on a condition

Given two collections users and activities, I want to enrich users.friends.user with all relevant activities, such that users.friends.user matches doc2.invitations.userId.
Below describes exactly what I would like:
user f24b189f-9e00-4d2b-b7f2-148a265fae7c is not a part of any activity, so their activities array is [].
user 223f2e04-6f0c-40a3-a88a-69d0127f2e92 is a part of an activity, so their activities array is populated with the activity object.
Raw:
db={
"users": [
{
"_id": "9c69a57f-e90a-43be-b559-449e973c6cba",
"__v": {
"$numberInt": "0"
},
"friends": [
{
"status": "ACCEPTED",
"user": "f24b189f-9e00-4d2b-b7f2-148a265fae7c"
},
{
"status": "ACCEPTED",
"user": "223f2e04-6f0c-40a3-a88a-69d0127f2e92"
}
]
}
],
"activities": [
{
"_id": {
"$oid": "63a92531bc6dea668f93bb06"
},
"invitations": [
{
"_id": {
"$oid": "63a92531bc6dea668f93bb07"
},
"userId": "9c69a57f-e90a-43be-b559-449e973c6cba",
"status": "ACCEPTED"
},
{
"_id": {
"$oid": "63a92544bc6dea668f93bb09"
},
"userId": "223f2e04-6f0c-40a3-a88a-69d0127f2e92",
"status": "ACCEPTED"
},
],
"__v": {
"$numberInt": "0"
}
}
]
}
Expected result:
"expectedResult": [
{
"_id": "9c69a57f-e90a-43be-b559-449e973c6cba",
"__v": {
"$numberInt": "0"
},
"friends": [
{
"status": "ACCEPTED",
"activities": [{
"_id": {
"$oid": "63a92531bc6dea668f93bb06"
},
"invitations": [
{
"_id": {
"$oid": "63a92531bc6dea668f93bb07"
},
"userId": "9c69a57f-e90a-43be-b559-449e973c6cba",
"status": "ACCEPTED"
},
{
"_id": {
"$oid": "63a92544bc6dea668f93bb09"
},
"userId": "223f2e04-6f0c-40a3-a88a-69d0127f2e92",
"status": "ACCEPTED"
},
],
"__v": {
"$numberInt": "0"
}
}]
},
{
"status": "ACCEPTED",
"activities": []
}
]
}
]
And here's a link to the playground.
I played around with aggregate and populate, but this use case is unlike a regular "join", which is throwing me off. Any pointers will be appreciated.

MongoDB lookup with multiple nested levels

In my application, I have a section of comments and replies under some documents.
Here's how my database schema looks like
db.updates.insertOne({
"_id": "62347813d28412ffd82b551d",
"documentID": "17987e64-f848-40f3-817e-98adfd9f4ecd",
"stream": [
{
"id": "623478134c449b218b68f636",
"type": "comment",
"text": "Hey #john, we got a problem",
"authorID": "843df3dbbdfc62ba2d902326",
"taggedUsers": [
"623209d2ab26cfdbbd3fd348"
],
"replies": [
{
"id": "623478284c449b218b68f637",
"type": "reply",
"text": "Not sure, let's involve #jim here",
"authorID": "623209d2ab26cfdbbd3fd348",
"taggedUsers": [
"26cfdbbd3fd349623209d2ab"
]
}
]
}
]
})
db.users.insertMany([
{
"_id": "843df3dbbdfc62ba2d902326",
"name": "Manager"
},
{
"_id": "623209d2ab26cfdbbd3fd348",
"name": "John"
},
{
"_id": "26cfdbbd3fd349623209d2ab",
"name": "Jim"
},
])
I want to join those two collections, and replace user ids with complete user information on all levels. So the final JSON should look like this
{
"_id": "62347813d28412ffd82b551d",
"documentID": "17987e64-f848-40f3-817e-98adfd9f4ecd",
"stream": [
{
"id": "623478134c449b218b68f636",
"type": "comment",
"text": "Hey #john, we got a problem",
"author": {
"_id": "843df3dbbdfc62ba2d902326",
"name": "Manager"
},
"taggedUsers": [
{
"_id": "623209d2ab26cfdbbd3fd348",
"name": "John"
}
],
"replies": [
{
"id": "623478284c449b218b68f637",
"type": "reply",
"text": "Not sure, let's involve #jim here",
"author": {
"_id": "623209d2ab26cfdbbd3fd348",
"name": "John"
},
"taggedUsers": [
{
"_id": "26cfdbbd3fd349623209d2ab",
"name": "Jim"
}
]
}
]
}
]
}
I know how to do the $lookup on the top-level fields, including pipelines, but how can I do with the nested ones?

Is it possible to filter a MongoDB on the value of a field whose value is a list of dictionaries?

I want to know if it is possible to get all the documents from a MongoDB database by querying on the elements of a field whose value is a list. For example here q2.Results has a list of comments and I would like to get all the documents where the UserNickname of the person who wrote the comment is Jackobear :
{
"_id": {
"$oid": "5fd5e617260828c7646000aa"
},
"q2": {
"Id": "q2",
"Locale": "fr_FR",
"Results": [
{
"Id": "163069310",
"CID": "eb523c23-980d-5661-b9d0-c6dc1028fa42",
"SourceClient": "sephora-fr",
"Badges": {
"loyaltyYes--Im-a-VIB-Rouge": {
"ContentType": "REVIEW",
"Id": "loyaltyYes--Im-a-VIB-Rouge",
"BadgeType": "Custom"
}
},
"BadgesOrder": [
"loyaltyYes--Im-a-VIB-Rouge"
],
"LastModeratedTime": "2020-09-22T12:00:18.000+00:00",
"LastModificationTime": "2020-12-10T14:36:16.000+00:00",
"ProductId": "P3742199",
"CampaignId": "BV_PIE_ONLINE",
"ContextDataValuesOrder": [
"Gender",
"Eyes",
"Skin"
],
"UserLocation": "Paris",
"AuthorId": "78948942",
"ContentLocale": "fr_FR",
"IsFeatured": false,
"TotalInappropriateFeedbackCount": {
"$numberInt": "0"
},
"TotalClientResponseCount": {
"$numberInt": "0"
},
"TotalCommentCount": {
"$numberInt": "0"
},
"Rating": {
"$numberInt": "5"
},
"IsRatingsOnly": false,
"IsRecommended": true,
"Helpfulness": {
"$numberDouble": "1.0"
},
"TotalFeedbackCount": {
"$numberInt": "2"
},
"TotalNegativeFeedbackCount": {
"$numberInt": "0"
},
"TotalPositiveFeedbackCount": {
"$numberInt": "2"
},
"ModerationStatus": "APPROVED",
"SubmissionId": "r23232-fr__16007743UA3Zr3O9L5",
"SubmissionTime": "2020-09-22T11:32:57.000+00:00",
"ReviewText": "J’adore, il sent le bord de mer une ressemblance avec le parfum Kenzo",
"Title": "Très bon parfum",
"UserNickname": "Jackobear",
"ContextDataValues": {
"Gender": {
"Value": "Male",
"Id": "Gender"
},
"Skin": {
"Value": "Normale",
"Id": "Skin"
},
"Eyes": {
"Value": "Marrons",
"Id": "Eyes"
}
},
"Videos": [],
"Pros": null,
"InappropriateFeedbackList": [],
"SecondaryRatings": {},
"ClientResponses": [],
"Photos": [],
"Cons": null,
"IsSyndicated": false,
"SecondaryRatingsOrder": [],
"AdditionalFields": {},
"RatingRange": {
"$numberInt": "5"
},
"TagDimensions": {},
"AdditionalFieldsOrder": [],
"ProductRecommendationIds": [],
"CommentIds": [],
"TagDimensionsOrder": []
},
{
"Id": "129228166",
"CID": "66aec0c2-04b0-57d2-aa9f-ac3b7ecb91df",
"SourceClient": "sephora-fr",
"Badges": {
"loyaltyYes--Im-a-beauty-insider": {
"ContentType": "REVIEW",
"Id": "loyaltyYes--Im-a-beauty-insider",
"BadgeType": "Custom"
}
},
"BadgesOrder": [
"loyaltyYes--Im-a-beauty-insider"
],
"LastModeratedTime": "2019-06-12T00:15:07.000+00:00",
"LastModificationTime": "2020-02-07T10:11:38.000+00:00",
"ProductId": "P3742199",
"CampaignId": "BV_REVIEW_DISPLAY",
"ContextDataValuesOrder": [
"Gender",
"Age",
"Eyes",
"Skin"
],
"UserLocation": "Bayeux",
"AuthorId": "78051240",
"ContentLocale": "fr_FR",
"IsFeatured": false,
"InappropriateFeedbackList": [
{
"AuthorId": "ztn5x2jvnf5u8llq2jagt7j4mp",
"SubmissionTime": "2019-06-11T23:12:44.000+00:00"
},
{
"AuthorId": "zpebdw7hh48w5zpr3778nx2hgg",
"SubmissionTime": "2019-06-06T23:20:57.000+00:00"
},
{
"AuthorId": "zkvy1qeaaz8s2znk6kesfg3dnb",
"SubmissionTime": "2019-06-03T20:01:34.000+00:00"
}
],
"TotalInappropriateFeedbackCount": {
"$numberInt": "3"
},
"TotalClientResponseCount": {
"$numberInt": "0"
},
"TotalCommentCount": {
"$numberInt": "0"
},
"Rating": {
"$numberInt": "5"
},
"IsRatingsOnly": false,
"IsRecommended": true,
"Helpfulness": {
"$numberDouble": "0.8999999761581421"
},
"TotalFeedbackCount": {
"$numberInt": "40"
},
"TotalNegativeFeedbackCount": {
"$numberInt": "4"
},
"TotalPositiveFeedbackCount": {
"$numberInt": "36"
},
"ModerationStatus": "APPROVED",
"SubmissionId": "halilss25hcanccb0yj6nxvpk",
"SubmissionTime": "2019-06-03T11:43:04.000+00:00",
"ReviewText": "Un parfum enivrant mais pas entêtant, la note de tête est exquise et très fraîche, parfaite pour l'été. Vous devriez vendre ce parfum dans plus de sephora et avec plus de stock!!",
"Title": "Parfait pour l'été",
"UserNickname": "Abraxas",
"ContextDataValues": {
"Age": {
"Value": "18to24",
"Id": "Age"
},
"Gender": {
"Value": "Female",
"Id": "Gender"
},
"Skin": {
"Value": "Grasse",
"Id": "Skin"
},
"Eyes": {
"Value": "Bleus",
"Id": "Eyes"
}
},
"Videos": [],
"Pros": null,
"SecondaryRatings": {},
"ClientResponses": [],
"Photos": [],
"Cons": null,
"IsSyndicated": false,
"SecondaryRatingsOrder": [],
"AdditionalFields": {},
"RatingRange": {
"$numberInt": "5"
},
"TagDimensions": {},
"AdditionalFieldsOrder": [],
"ProductRecommendationIds": [],
"CommentIds": [],
"TagDimensionsOrder": []
}
],
"HasErrors": false,
"Errors": []
},
"name": "L'Atelier des Subtils Eau d'Océan - Eau de toilette",
}
I was thinking of a cause like doing a find_all with:
users = ['Jackobear']
collection.findall({'q2.Results.x.UserNickname' : {'$in': users}})
If it is not possible, I suppose I have to make a new database with the perfumes appreciated by each person? How can I make the script that could retrieve this information from the database by searching these documents for each user?
$match to filter the records 1st so that $unwind will have fewer records to deal with.
$unwind to break into individual documents.
Now perform match on individual documents.
You can use $group to group documents.
db.collection.aggregate([
{ $match: { "q2.Results.UserNickname": { $in: [ "Jackobear", "ABC" ] } } },
{ $unwind: "$q2.Results" },
{ $match: { "q2.Results.UserNickname": { $in: [ "Jackobear", "ABC" ] } } }
])
Demo - https://mongoplayground.net/p/g04J8f4-TjY

How to get ids of parents based on deeply nested sub document?

This question is in continuation with older question: Parent.save() not working when sub document / deeply nested document is modified
Say I have a document as below :
{
"apiCallCount": 1,
"_id": "5e0da052b4b3fe5188602e11",
"email": "abc#def.net",
"password": "123123",
"userName": "username",
"companyName": "companyName",
"apiKey": "apiKey",
"solutionType": "solutionType",
"parentCompany": "parentCompany",
"buildings": [
{
"gateways": [
{
"devices": [
{
"_id": "5e0da052b4b3fe5188602e15",
"serialNumber": "serialNumber 1",
"area": "area",
"connectionStatus": 0,
"gatewayKey": "gatewayKey",
"applicationNumber": 11,
"firmwareVersion": "firmwareVersion",
"needsAttention": true,
"verificationCode": "123456",
"patientRiskStatus": "patientRiskStatus",
"patientFirstName": "UPDATED!!!",
"patientLastName": "patientLastName",
"createdAt": "2020-01-02T07:48:34.287Z",
"updatedAt": "2020-01-02T07:48:34.287Z"
},
{
"_id": "5e0da052b4b3fe5188602e14",
"serialNumber": "serialNumber 2",
"area": "area",
"connectionStatus": 0,
"gatewayKey": "gatewayKey",
"applicationNumber": 22,
"firmwareVersion": "firmwareVersion",
"needsAttention": true,
"verificationCode": "987654",
"patientRiskStatus": "patientRiskStatus",
"patientFirstName": "patientFirstName",
"patientLastName": "patientLastName",
"createdAt": "2020-01-02T07:48:34.288Z",
"updatedAt": "2020-01-02T07:48:34.288Z"
}
],
"_id": "5e0da052b4b3fe5188602e13",
"gatewayName": "gatewayName 1",
"gatewayKey": "gatewayKey",
"suite": "suite",
"createdAt": "2020-01-02T07:48:34.288Z",
"updatedAt": "2020-01-02T07:48:34.288Z"
}
],
"_id": "5e0da052b4b3fe5188602e12",
"buildingName": "buildingName 1",
"address": "address",
"suite": "suite",
"floor": "floor",
"timeZone": "String",
"createdAt": "2020-01-02T07:48:34.288Z",
"updatedAt": "2020-01-02T07:48:34.288Z"
}
],
"createdAt": "2020-01-02T07:48:34.289Z",
"updatedAt": "2020-01-02T09:10:25.200Z",
"__v": 0
}
I am able to dig through document and able to get device sub document with "verificationCode": "123456"
Now I want to get gatewayID(one level up) and buildingID(two level up) for this device.
Currently I have a call like this :
I am trying to update parent doc based on deeply nested sub-document.
I get sub document by accountId and verification code like below.
and then need to update the parent.
In my sample below I Put hard-coded ids which i need to get run time.
if (newlySavedUser) {
try {
let result = await Account.findByIdAndUpdate(
accountId,
{
$set: {
"buildings.$[building].gateways.$[gateway].devices.$[device].patientFirstName": "userName",
"buildings.$[building].gateways.$[gateway].devices.$[device].patientLastName": "userName1"
}
},
{
arrayFilters: [
{ "building._id": ObjectId("5d254bb179584ebcbb68b712") }, /// <---- I want to get this buildingId
{ "gateway._id": ObjectId("5d254b64ba574040d9632ada") }, /// <---- I want to get this gatewayId
{ "device.verificationCode": "4144" } /// <-- based on this verificationCode
],
new: true
}
);
if (!result) return res.status(404);
console.log(result)
//res.send(result);
} catch (err) {
console.log(err);
res.status(500).send("Something went wrong");
}
}
Trying "tom slabbaert" solution for aggregate.
Account.aggregate([
{
$unwind: "$buildings"
},
{
$unwind: "$gateways"
},
{
$match: {
"buildings.gateways.devices.verificationCode": "4144"
}
},
{
$project: {
buildingID: "$buildings._id",
gatewayID: "$gateways._id",
}
}
]).exec((err, result)=>{
console.log("result", result)
if(err) throw err;
});
Your help is appreciated.
You can use this 3 level $unwind aggregation, and then match the document you want:
db.collection.aggregate([
{
$unwind: "$buildings"
},
{
$unwind: "$buildings.gateways"
},
{
$unwind: "$buildings.gateways.devices"
},
{
$match: {
"buildings._id": "5e0da052b4b3fe5188602e12",
"buildings.gateways._id": "5e0da052b4b3fe5188602e13",
"buildings.gateways.devices.verificationCode": "123456"
}
}
])
This will give you the following result:
[
{
"__v": 0,
"_id": "5e0da052b4b3fe5188602e11",
"apiCallCount": 1,
"apiKey": "apiKey",
"buildings": {
"_id": "5e0da052b4b3fe5188602e12",
"address": "address",
"buildingName": "buildingName 1",
"createdAt": "2020-01-02T07:48:34.288Z",
"floor": "floor",
"gateways": {
"_id": "5e0da052b4b3fe5188602e13",
"createdAt": "2020-01-02T07:48:34.288Z",
"devices": {
"_id": "5e0da052b4b3fe5188602e15",
"applicationNumber": 11,
"area": "area",
"connectionStatus": 0,
"createdAt": "2020-01-02T07:48:34.287Z",
"firmwareVersion": "firmwareVersion",
"gatewayKey": "gatewayKey",
"needsAttention": true,
"patientFirstName": "UPDATED!!!",
"patientLastName": "patientLastName",
"patientRiskStatus": "patientRiskStatus",
"serialNumber": "serialNumber 1",
"updatedAt": "2020-01-02T07:48:34.287Z",
"verificationCode": "123456"
},
"gatewayKey": "gatewayKey",
"gatewayName": "gatewayName 1",
"suite": "suite",
"updatedAt": "2020-01-02T07:48:34.288Z"
},
"suite": "suite",
"timeZone": "String",
"updatedAt": "2020-01-02T07:48:34.288Z"
},
"companyName": "companyName",
"createdAt": "2020-01-02T07:48:34.289Z",
"email": "abc#def.net",
"parentCompany": "parentCompany",
"password": "123123",
"solutionType": "solutionType",
"updatedAt": "2020-01-02T09:10:25.200Z",
"userName": "username"
}
]
Mongoplayground
A simple aggregation would suffice:
db.collection.aggregate([
{
$unwind: "$buildings"
},
{
$unwind: "$buildings.gateways"
},
{
$match: {
"buildings.
gateways.devices.verificationCode": "123456"
}
},
{
$project: {
buildingID: "$buildings._id",
gatewayID: "$gateways._id",
}
}
])
If its possible to get duplicates you can $group on buildingID to avoid that.

check if a field of type array contains an array

Im using mongoose, I have the following data of user collection:
[{
"_id": "1",
"notes": [
{
"value": "A90",
"text": "math"
},
{
"value": "A80",
"text": "english"
},
{
"value": "A70",
"text": "art"
}
]
},
{
"_id": "2",
"notes": [
{
"value": "A90",
"text": "math"
},
{
"value": "A80",
"text": "english"
}
]
},
{
"_id": "3",
"notes": [
{
"value": "A80",
"text": "art"
}
]
}]
and I have as a parameters the following array: [ "A90", "A80" ]
so I want to make a query to use this array to return only the records that have all the array items in the notes (value) table.
So for the example above it will return:
[{
"_id": "1",
"notes": [
{
"value": "A90",
"text": "math"
},
{
"value": "A80",
"text": "english"
},
{
"value": "A70",
"text": "art"
}
]
},
{
"_id": "2",
"notes": [
{
"value": "A90",
"text": "math"
},
{
"value": "A80",
"text": "english"
}
]
}]
I tried the following find query:
{ "notes": { $elemMatch: { value: { $in: valuesArray } } }}
but it returns a record even if just one element in valuesArray exist.
it turned out to be quite easy:
find({ "notes.value": { $all: arrayValues } })