mongodb, get the related documents from an array - mongodb

I have an Event collection that could contain the following:
{ "_id" : ObjectId("5a12fa490eeff735737e7711"),
"title" : "dgdfgfgfd",
"startDate" : ISODate("2017-11-20T15:52:33.060Z"),
"endDate" : ISODate("2017-11-20T16:52:33.060Z"),
"registrations" : [ ObjectId("5a0c0c5ea8c2405f092fc83d") ]
}
{
"_id" : ObjectId("5a12ffbed7a6043de1ba7d72"),
"title" : "kjkj",
"startDate" : ISODate("2017-11-20T16:15:54.204Z"),
"endDate" : ISODate("2017-11-20T16:15:54.204Z"),
"registrations" : [ ObjectId("5a0c0c5ea8c2405f092fc83d"), ObjectId("7a0c0c5ea8dfd05f092fc84d") ]
}
The registration field contains a set of user ids.
How to get a list of all users referenced in registrations for a given event?
For instance, for event with _id 5a12fa490eeff735737e7711 I would like to end up with a list of users that looks like this:
[
{
"_id" : ObjectId("5a0c0c5ea8c2405f092fc83d"),
"name" : "test user"
"email" : "m#m.fr"
}
]
In this case, there would be only one element in the resulting list since the registrations field for the given event only contains a single reference.
Thanks in advance

The aggregation is not so trivial so maybe you should consider embedding these two fields into your array (also because of potential performance issues). However, what you need to do is to:
unwind registrations
use $lookup to join data from second collection
use $project to decompose your document, so that registration id and data about users will be one subdocument
use $group to obtain array of registrations
db.Event.aggregate([
{
"$unwind": "$registrations"
},
{
"$lookup": {
"from": "Users",
"localField": "registrations",
"foreignField": "_id",
"as" : "user"
}
},
{
"$project": {
"_id": 1,
"title" : 1,
"startDate" : 1,
"endDate": 1,
"registrations._id": "registrations",
"registrations.name": "user.name",
"registrations.email": "user.email"
}
},
{
"$group": {
"_id": "$_id",
"title" : { "$first": "$title"},
"startDate" : { "$first": "$startDate"},
"endDate" : { "$first": "$endDate"},
"registrations": { "$push": "$registrations"}
}
}
])

Related

$eq inside filter does not work on array fields - Mongodb

I have an aggregate query below. I would have to filter out the result of aggregation on Product collection because for some customers, there are huge number of products and fetching all the customer's products without filter (in a single aggregate query) would result in Bson too large exception. The problem is one of the fields by which I want to perform the filter is array (p.metadata.category) and Mongo $eg seems not working on array fields, it seems it works only on simple values and objects fields.
db.getCollection('customer').aggregate([
{
$lookup: {
from: 'Product',
localField: '_id',
foreignField: 'partiesList._id',
as: 'products',
}
},
{
$match: {
"_id": {$in: [
"C123",
"C456"
]
}
}
},
{
"$project": {
"products": {
"$filter": {
"input": "$products",
"as": "p",
"cond": {
$and:[
{
"$eq": ["$$p.metadata.category.name","somevalue"]
},
{
"$eq": ["$$p.isMain",true]
}
]
}
}
}
}
}
])
So result of above query would be list of customers with empty products array (although products actually exist) but if I remove metadata.category.name condition from $and array in above query it works like charm and the p.isMain filter works fine and filters out the products as expected and shows only products with isMain set to true.
Here is my sample data :
Customer :
{
"_id" : "C123",
"name" : "coooo"
}
Product (Customer's product) :
{
"_id" : "PR123",
"isMain" : true,
"name" : "My Product",
"metadata" : {
"category" : [
{
"name" : "somevalue",
"version" : "1",
"referredType" : "Category",
"type" : "Category"
},
{
"name" : "someOtherValue",
"version" : "1",
"referredType" : "Category",
"type" : "Category"
}
]
},
"partiesList" : [
{
"_id" : "C123",
"role" : "Customer"
"referredType" : "Customer"
}
]
}
Any ideas or alternatives ??
Since Product.metadata.category is an array,"$$p.metadata.category.name" is an array of all of the name values in each of those elements.
That $eq is then testing ["somevalue", "someOtherValue"] == "somevalue" which is always going to be false.
To check if a value is contained in an array, use $in, like
{$in: ["somevalue", "$$p.metadata.category.name"]}
Unrelated performance note:
Since the match statement is considering the _id of the document from the input collection, placing the $match before the $lookup in the pipeline will result in fewer documents retrieved during the lookup, and therefore faster performance.

find all with aggregate in mongodb with another key in that object array

I have collection like this,
company has many people
person has many companies
the company document like this :
{
"_id" : ObjectId("5eeecf60a3a0d434693efe9a"),
"name" : "ONE",
"members" : []
}
{
"_id" : ObjectId("5eeec400d655e7af27b07d8a"),
"name" : "Two TWO",
"members" : [
{"_id": ObjectId("5eeec400d65ddddd5555"), "status": false}
]
}
This is what I tried using aggregation
db.companies.aggregate(
{
"$lookup": {
"from": "people",
"localField": "members._id",
"foreignField": "_id",
"as": "members"}
}).pretty()
After aggregation, the status is missing from the resulting objects.
Here's the aggregation result.
{
"_id" : ObjectId("5eeec400d655e7af27b07d8a"),
"name" : "Two TWO",
"members" : [
{
"_id": ObjectId("5eeec400d65ddddd5555"),
"name": "John DO"
"gender": "male"
}
]
}
{
"_id" : ObjectId("5eeecf60a3a0d434693efe9a"),
"name" : "ONE",
"members" : []
}
Is it possible to include status key as a part of aggregation output
Expected Output
members : [
{
company: {
"_id": ObjectId("5eeec400d65ddddd5555"),
"name": "John DO"
"gender": "male"
}
status: false
}
]
In lookup, you are storing it as matching document result in members.
This is replacing your existing members object from your $$ROOT
To maintain the original $members from companies as well as $members from people.
You would need to merge both objects in $project phase.
Here's a breakdown of pipeline:
Phase 1: $unwind on $members to to pull members array at $ROOT level.
Phase2: $lookup document from people collection and store it under fromPeople attribute.
Phase 3: Assuming you have only one matching document fromPeople, merge object element at array index 0, with $ROOT.members, store the merged object in members attribute.
db.companies.aggregate([
{
$unwind:{path: "$members"}
},
{
"$lookup": {
"from": "people",
"localField": "members._id",
"foreignField": "_id",
"as": "fromPeople"
}
},
{
$project: {
members: { $mergeObjects: [ { $arrayElemAt: [ "$fromPeople", 0 ] }, "$$ROOT.members" ] },
}
}
])
Output
{
"_id" : "5eeec400d655e7af27b07d8a",
"members" : {
"_id" : "5eeec400d65ddddd5555",
"name" : "John DO",
"gender" : "male",
"status" : false
}
}

compare string and objectId in lookup

I foundout that there is no option to compare these two .If there is any alternate please tell me.
As you see i have two collection event and eventuser i want list of user from eventuser on the basis
of event name in event collection.
But i have userId in string format then how to compare userId column
in event with _id column in eventuser collection.
Event collection
{
"_id" : ObjectId("5b5867500be60f139e67c908"),
"userId" : "5b58674e0be60f139e67cfea",
"name" : "Add to Cart",
},
{
"_id" : ObjectId("5b5867500be60f139e67c090"),
"userId" : "5b58674e0be60f139e67cfea",
"name" : "Searched",
},
{
"_id" : ObjectId("5b5867500be60f139e67c098"),
"userId" : "5b58674e0be60f139e67cacd",
"name" : "Add to Cart",
}
EventUser Collection
{
"_id":ObjectId("5b58674e0be60f139e67cfea"),
"name":"jogendra"
},
{
"_id":ObjectId("5b58674e0be60f139e67cfcv"),`
"name":"jogendra singh"
}
mmy query- it return users array as empty list
db.getCollection("event").aggregate([
{$match:{"name":"Add to Cart"}},
{$lookup:{
from:"eventuser",
localField:"userId",
foreignFiled:"_id",
as:"users"
}}
]);
In $lookup $lookup you can't match with string --> _id or _id --> string
Possible case is _id --> _id or string --> string
So you need change your data like this
{
"_id" : ObjectId("5b5867500be60f139e67c908"),
"userId" : ObjectId("5b58674e0be60f139e67cfea"),
"name" : "Add to Cart",
},
{
"_id" : ObjectId("5b5867500be60f139e67c090"),
"userId" : ObjectId("5b58674e0be60f139e67cfea"),
"name" : "Searched",
},
{
"_id" : ObjectId("5b5867500be60f139e67c098"),
"userId" : ObjectId("5b58674e0be60f139e67cacd"),
"name" : "Add to Cart",
}
otherwise, you need to upgrade your MongoDB version 4 and you can use $toObjectId $toObjectId
db.collection.aggregate([
{ $match: { "name": "Add to Cart" } },
{
$addFields: {
convertedId: { $toObjectId: "$userId" }
}
},
{
"$lookup": {
"from": "from_collection",
"localField": "convertedId",
"foreignField": "_id",
"as": "data"
}
}
]);

How to return just the nested documents of an array from all documents

I have a question about querying nested documents. I tried to search but nothing answered my question or I am maybe overlooking it. I have structure like this:
{
"_id" : ObjectId("592aa441e0f8de09b0912fe9"),
"name" : "Patrick Rothfuss",
"books" : [
{
"title" : "Name of the wind",
"pages" : 400,
"_id" : ObjectId("592aa441e0f8de09b0912fea")
},
{
"title" : "Wise Man's Fear",
"pages" : 500,
"_id" : ObjectId("592aa441e0f8de09b0912feb")
},
},
{
"_id" : ObjectId("592aa441e0f8de09b0912fe9"),
"name" : "Rober Jordan",
"books" : [
{
"title" : "The Eye of the World",
"pages" : 400,
"_id" : ObjectId("592aa441e0f8de09b0912fea")
},
{
"title" : "The Great Hunt",
"pages" : 500,
"_id" : ObjectId("592aa441e0f8de09b0912feb")
}
},
And I would like to query for the list of all books in entire colletion of Authors - something like:
"books" : [
{
"title" : "The Eye of the World",
"pages" : 400,
"_id" : ObjectId("592aa441e0f8de09b0912fea")
},
{
"title" : "The Great Hunt",
"pages" : 500,
"_id" : ObjectId("592aa441e0f8de09b0912feb")
},
{
"title" : "Name of the wind",
"pages" : 400,
"_id" : ObjectId("592aa441e0f8de09b0912fea")
},
{
"title" : "Wise Man's Fear",
"pages" : 500,
"_id" : ObjectId("592aa441e0f8de09b0912fea")
}]
You can do this using .aggregate() and predominantly the $unwind pipeline operator:
In modern MongoDB 3.4 and above you can use in tandem with $replaceRoot
Model.aggregate([
{ "$unwind": "$books" },
{ "$replaceRoot": { "newRoot": "$books" } }
],function(err,results) {
})
In earlier versions you specify all fields with $project:
Model.aggregate([
{ "$unwind": "$books" },
{ "$project": {
"_id": "$books._id",
"pages": "$books.pages",
"title": "$books.title"
}}
],function(err,results) {
})
So $unwind is what you use to deconstruct or "denormalise" the array entries for processing. Effectively this creates a copy of the whole document for each member of the array.
The rest of the task is about returning "only" those fields present in the array.
It's not a very wise thing to do though. If your intent is to only return content embedded within an array of a document, then you would be better off putting that content into a separate collection instead.
It's far better for performance, pulling apart a all documents from a collection with the aggregation framework, just to list those documents from the array only.
According to above mentioned description please try executing following query in MongoDB shell.
db.collection.aggregate(
// Pipeline
[
// Stage 1
{
$unwind: "$books"
},
// Stage 2
{
$group: {
_id:null,
books:{$addToSet:'$books'}
}
},
// Stage 3
{
$project: {
books:1,
_id:0
}
},
]
);

MongoDB query only the inner document

My mongodb collection looks like this:
{
"_id" : ObjectId("5333bf6b2988dc2230c9c924"),
"name" : "Mongo2",
"notes" : [
{
"title" : "mongodb1",
"content" : "mongo content1"
},
{
"title" : "replicaset1",
"content" : "replca content1"
}
]
}
{
"_id" : ObjectId("5333fd402988dc2230c9c925"),
"name" : "Mongo2",
"notes" : [
{
"title" : "mongodb2",
"content" : "mongo content2"
},
{
"title" : "replicaset1",
"content" : "replca content1"
},
{
"title" : "mongodb2",
"content" : "mongo content3"
}
]
}
I want to query only notes that have the title "mongodb2" but do not want the complete document.
I am using the following query:
> db.test.find({ 'notes.title': 'mongodb2' }, {'notes.$': 1}).pretty()
{
"_id" : ObjectId("5333fd402988dc2230c9c925"),
"notes" : [
{
"title" : "mongodb2",
"content" : "mongo bakwas2"
}
]
}
I was expecting it to return both notes that have title "mongodb2".
Does mongo return only the first document when we query for a document within a document ?
The positional $ operator can only return the first match index that it finds.
Using aggregate:
db.test.aggregate([
// Match only the valid documents to narrow down
{ "$match": { "notes.title": "mongodb2" } },
// Unwind the array
{ "$unwind": "$notes" },
// Filter just the array
{ "$match": { "notes.title": "mongodb2" } },
// Reform via group
{ "$group": {
"_id": "$_id",
"name": { "$first": "$name" },
"notes": { "$push": "$notes" }
}}
])
So you can use this to "filter" specific documents from the array.
$ always refers to the first match, as does the $elemMatch projection operator.
I think you have three options:
separate the notes so each is a document of its own
accept sending more data over the network and filter client-side
use the aggregation pipeline ($match and $project)
I'd probably choose option 1, but you probably have a reason for your data model.