How to use lookup with custom condition inside subelement in MongoDB aggregation? - mongodb

Helo everyone!
I have a products collection like this:
db.products.insertMany( [
{ "_id" : 1, "name" : "Apple", "variants" : [ { "_id" : 1, "name" : "Red Apple" }, { "_id" : 2, "name" : "Green Apple" }] },
{ "_id" : 2, "name" : "Banana", "variants" : [ { "_id" : 3, "name" : "Yellow Banana" }, { "_id" : 4, "name" : "Green Banana" }] },
] )
and a orders collection
db.orders.insertMany( [
{ "_id" : 1, "price" : 123, "itemId": 2},
] )
How to join products collection to orders collection by itemId (itemId == variants._id) with aggregate?
I try with this way but it's not working
db.orders.aggregate([
{
$lookup: {
from: 'products',
as: 'product',
let: { variantId: '$_id' },
pipeline: [
{
$match: {
$expr: { $eq: ['$$variantId', '$variants._id'] },
},
}
],
},
},
])
maybe issues from $expr { $eq: ['$$variantId', '$variants._id'] } but i cannot resolve it. anybody can help?
Thanks for help!

Related

Adding separate conditions for each item in Mongodb

I have 3 collections.
The user_movie collection keeps the relationship of the user and the movies he added to his list. The membership_date field is when the user is subscribed.
The user wants to see the reviews of the movies he added to his list.
But when showing these reviews, I want to show the comments after the subscription date.
With the query I tried, the user sees all the reviews.
Collection structure
db={
"user_movie": [
{
"_id" : 1,
"movie_id" : 1,
"user_id" : 1,
"status" : true,
"membership_date" : ISODate("2021-01-01")
},
{
"_id" : 2,
"movie_id" : 2,
"user_id" : 1,
"status" : true,
"membership_date" : ISODate("2021-01-01")
},
{
"_id" : 3,
"movie_id" : 3,
"user_id" : 1,
"status" : true,
"membership_date" : ISODate("2022-01-02")
}
],
"movie": [
{
"_id" : 1,
"movie_name" : "fugiat nulla",
},
{
"_id" : 2,
"movie_name" : "sint occaecat",
},
{
"_id" : 3,
"movie_name" : "cupidatat non",
}
],
"movie_reviews": [
{
"_id" : 1,
"movie_id" : 1,
"review": "Lorem ipsum dolor"
"review_date" : ISODate("2021-01-02"),
},
{
"_id" : 2,
"movie_id" : 2,
"review": "Consectetur adipiscing elit"
"review_date" : ISODate("2021-01-02"),
},
{
"_id" : 3,
"movie_id" : 3,
"review": "Do eiusmod tempor"
"review_date" : ISODate("2021-01-02"),
},
{
"_id" : 4,
"movie_id" : 3,
"review": "Abore et dolore magna"
"review_date" : ISODate("2022-01-01"),
}
]
}
The query I tried gives this output.
[
{
"_id" : 1,
"movie_id" : 1,
"date" : ISODate("2021-01-02"),
},
{
"_id" : 2,
"movie_id" : 2,
"date" : ISODate("2021-01-02"),
},
{
"_id" : 3,
"movie_id" : 3,
"date" : ISODate("2021-01-02"),
},
{
"_id" : 4,
"movie_id" : 3,
"date" : ISODate("2022-01-01"),
}
]
But this is not the output I was expecting. The movie reviews with id 3,4 should not be seen by the user. Because it was written before the membership_date date, so the user should not see these reviews.
How can I get all movie reviews of the user with id 1 as mentioned?
Stage 1: $lookup with pipeline
Join conditions:
By movie_id.
movie_review's review_date must not be earlier ($gte) than membership_date (from user_movie).
Stage 2: $unwind
Deconstruct movie_reviews array field to multiple document.
Stage 3: $replaceWith
Decorate the output document to show movie_review document.
db.user_movie.aggregate([
{
"$lookup": {
"from": "movie_reviews",
let: {
movie_id: "$movie_id",
membership_date: "$membership_date"
},
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: [
"$movie_id",
"$$movie_id"
]
},
{
$gte: [
"$review_date",
"$$membership_date"
]
}
]
}
}
}
],
as: "movie_reviews"
}
},
{
$unwind: "$movie_reviews"
},
{
"$replaceWith": "$movie_reviews"
}
])
Sample Mongo Playground
Updated: Lookup join movie_reviews with user_movie
Since you are just to get movie_reviews, I think it is better to join from movie_reviews with user_movie instead of joining from user_movie with movie_reviews to get rid of the use of $unwind.
db.movie_reviews.aggregate([
{
"$lookup": {
"from": "user_movie",
let: {
movie_id: "$movie_id",
review_date: "$review_date"
},
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: [
"$movie_id",
"$$movie_id"
]
},
{
$gte: [
"$$review_date",
"$membership_date"
]
}
]
}
}
}
],
as: "user_movie"
}
},
{
$match: {
"user_movie": {
$ne: []
}
}
},
{
$project: {
user_movie: 0
}
}
])
Sample Mongo Playground (Get movie_reviews)

Mongodb Aggregation get Data per user

Report table sample data
{
"_id" : ObjectId("614415f4a6566a001623b622"),
"record" : [
{
"dateTime" : ISODate("2021-09-17T04:13:39.465Z"),
"status" : "time-in",
"month" : 9,
"day" : 17,
"year" : 2021,
"time" : 1631852019465.0,
"date" : ISODate("2021-09-17T00:00:00.000Z"),
},
{
"dateTime" : ISODate("2021-09-17T04:14:01.182Z"),
"status" : "time-out",
"month" : 9,
"day" : 17,
"year" : 2021,
"time" : 1631852041182.0,
"date" : ISODate("2021-09-17T00:00:00.000Z"),
}
],
"uid" : ObjectId("614415b0a6566a001623b80b"),
"date" : ISODate("2021-09-17T00:00:00.000Z"),
"status" : "time-out",
"createdAt" : ISODate("2021-09-17T04:13:40.102Z"),
"updatedAt" : ISODate("2021-09-17T04:14:01.831Z"),
"__v" : 0
}
Users table sample data
{
"_id" : ObjectId("615c0f6db30aff375cd05ac1"),
"displayName" : "test test",
"firstName" : "test",
"lastName" : "test",
"email" : "test#gmail.com",
"brand" : "Jollibee",
"phone" : "+632312312312312",
"role" : 1,
"isVerified" : true,
"isArchived" : false,
"createdAt" : ISODate("2021-10-05T08:40:13.208Z"),
"updatedAt" : ISODate("2021-10-05T08:40:13.208Z"),
"__v" : 0
}
I have a data like this
db.getCollection('users').aggregate([
{
"$match": { brand: "Jollibee" }
},
{
$lookup: {
from: "orders",
let: { id: 'id' },
pipeline: [
{
$match: {
date: { $gte: ISODate("2020-11-01"), $lt: ISODate("2021-11-31") },
}
}
],
as: "orders",
},
},
{
$project: {
"_id": 1,
"name": 1,
"orders": 1
}
}
])
when I'm using this aggregation I'm getting all the data inserted per user.
What I want to happen is that. I will only get the data that belong to the user and not all the data of all users.
Added the sample documents for each collection
You are not comparing the userIds of both collections. You should add that on your $match. Playground
db.users.aggregate([
{
"$match": {
brand: "Jollibee"
}
},
{
$lookup: {
from: "orders",
let: {
id: "$_id"
},
pipeline: [
{
$match: {
date: {
$gte: ISODate("2020-11-01"),
$lt: ISODate("2021-11-30")
},
$expr: {
$eq: [
"$uid",
"$$id"
]
}
}
}
],
as: "orders",
},
},
{
$project: {
"_id": 1,
"name": 1,
"orders": 1
}
}
])

MongoDB aggregation project the specific fields from lookup

This example is following https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/#use-lookup-with-mergeobjects
db.orders.insert([
{ "_id" : 1, "item" : "almonds", "price" : 12, "quantity" : 2 },
{ "_id" : 2, "item" : "pecans", "price" : 20, "quantity" : 1 }
])
db.items.insert([
{ "_id" : 1, "item" : "almonds", description: "almond clusters", "instock" : 120 },
{ "_id" : 2, "item" : "bread", description: "raisin and nut bread", "instock" : 80 },
{ "_id" : 3, "item" : "pecans", description: "candied pecans", "instock" : 60 }
])
Aggregation:
db.orders.aggregate([
{
$lookup: {
from: "items",
localField: "item", // field in the orders collection
foreignField: "item", // field in the items collection
as: "fromItems"
}
},
{
$replaceRoot: { newRoot: { $mergeObjects: [ { $arrayElemAt: [ "$fromItems", 0 ] }, "$$ROOT" ] } }
},
{ $project: { fromItems: 0 } }
])
Result:
{ "_id" : 1, "item" : "almonds", "description" : "almond clusters", "instock" : 120, "price" : 12, "quantity" : 2 }
{ "_id" : 2, "item" : "pecans", "description" : "candied pecans", "instock" : 60, "price" : 20, "quantity" : 1 }
Question: How to modify the aggregation to project the specific fields? e.g. project "_id", "item" and "description" only:
{ "_id" : 1, "item" : "almonds", "description" : "almond clusters" }
{ "_id" : 2, "item" : "pecans", "description" : "candied pecans" }
You're getting an empty array, because the $lookup catching anything.
match the types
$addFields to convert
PLAYGROUND
This should be the first stage:
{
$addFields: {
itemId: {
$convert: {
input: "$itemId",
to: "int"
}
}
}
},
If you prefer, there is no need to add a stage
You could also remove addFields and use $lookup+let.
Modify the lookup this way:
{
$lookup: {
from: "items",
let: {
itemId: {
$convert: {
input: "$itemId",
to: "int"
}
}
},
pipeline: [
{
$match: {
$expr: {
$eq: [
"$_id",
"$$itemId"
]
}
}
}
],
/** field in the items collection*/
as: "fromItems"
}
}
PLAYGROUND2

MongoDB aggregation - show values from different arrays in one array

I have this type of data
{
"_id" : 6444,
"name" : [
{
"name" : "John",
"sourcesID" : [
1,
2
]
},
{
"name" : "Jack",
"sourcesID" : [
3,
4
]
}
],
"address" : [
{
"city" : "Chicago",
"sourcesID" : [
3,
4
]
},
{
"city" : "Boston",
"sourcesID" : [
5,
6
]
}
]
}
I want to aggregate the data so that I will be able to match a certain sourcesID and find all the information types that came from this source.
This is what I am looking to achieve
{"type" : "name", "sourceID" : 1}
{"type" : "name", "sourceID" : 2}
{"type" : "name", "sourceID" : 3}
{"type" : "name", "sourceID" : 4}
{"type" : "address", "sourceID" : 3}
{"type" : "address", "sourceID" : 4}
{"type" : "address", "sourceID" : 5}
{"type" : "address", "sourceID" : 6}
Thanks for your help.
Assuming that there's always sourceID field you can run $objectToArray to transform be able to read object keys dynamically and then run $unwind three times to get single document per sourceID:
db.collection.aggregate([
{
$project: {
data: {
$filter: {
input: { $objectToArray: "$$ROOT" },
cond: {
$ne: [ "$$this.k", "_id" ]
}
}
}
}
},
{ $unwind: "$data" },
{ $unwind: "$data.v" },
{ $unwind: "$data.v.sourcesID" },
{
$project: {
_id: 0,
type: "$data.k",
sourceID: "$data.v.sourcesID"
}
}
])
Mongo Playground

How to convert existing array of string to custom object array in mongodb?

I have some existing data and I want to update it but I can't able to build a query for that.
I want to convert and update Options field which is string array to object array.
Is it possible or not?
I tried arrayToObject but it doesn't work
Below is my existing record:
{
"_id" : ObjectId("5b6455d9c006ae9d142b0da8"),
"PartnerId" : "585938e3d4e9dac9bb2b09c6",
"BusinessID" : NumberLong(98),
"Responses" : [
{
"QID" : 1,
"Order" : 1,
"Question" : "Contact Information 1",
"Options" : [
"First Name",
"Address",
"Email",
"Phone"
],
"Answers" : [
"First Name",
"111, Dublin, California, 94568",
"forms1#vagaro.com",
"111"
]
},
{
"QID" : 8,
"Order" : 6,
"Question" : "Contact Information 2",
"Options" : [
"Address",
"Email"
],
"Answers" : [
"5000 Estate Enighed, Independence, Kansas, 67301"
]
}
]
}
Expected result:
{
"_id" : ObjectId("5b6455d9c006ae9d142b0da8"),
"PartnerId" : "585938e3d4e9dac9bb2b09c6",
"BusinessID" : NumberLong(98),
"Responses" : [
{
"QID" : 1,
"Order" : 1,
"Question" : "Contact Information 1",
"Options" : [
{Option:"First Name", Order:1},
{Option:"Address", Order:2},
{Option:"Email", Order:3},
{Option:"Phone", Order:4}
],
"Answers" : [
"First Name",
"111, Dublin, California, 94568",
"forms1#vagaro.com",
"111"
]
},
{
"QID" : 8,
"Order" : 6,
"Question" : "Contact Information 2",
"Options" : [
{Option:"Address", Order:1},
{Option:"Email" , Order:2}
],
"Answers" : [
"5000 Estate Enighed, Independence, Kansas, 67301"
]
}
]
}
Please help me.
Thanks
You can use Aggregation like follows. I used mongo shell with the same document you described.
db.test.aggregate([
{ $match: {} },
{
$project: {
_id: "$_id",
PartnerId: "$PartnerId",
BusinessID: "$BusinessID",
Responses: {
$map: {
input: "$Responses",
as: 'response',
in: {
"QID" : "$$response.QID",
"Order" : "$$response.Order",
"Question" : "$$response.Question",
"Options" : {
$map: {
input: "$$response.Options",
as: 'option',
in: {
Option: "$$option",
Order: {
$sum: [
{
$indexOfArray: [
"$$response.Options",
"$$option"
]
},
1
]
}
}
}
},
"Answers" : "$$response.Answers"
}
}
}
}
},
{ $out: 'output' }
])
Will output the desired documents to the collection output. You can check it and rename it later or just specify another collection name in the $out stage if you want to override/create another collection.