compare string and objectId in lookup - mongodb

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"
}
}
]);

Related

Not able to receive expected result in mongodb between collections

I'm trying to get a product list with logged user wishlist in the product list itself. I'm trying $lookup but not getting the expected result.
Product Document:
[
{
"_id" : ObjectId("6044351794bee8b6e0fce48f"),
"sku" : "P003474",
"name" : "Kitchen Wash"
},
{
"_id" : ObjectId("6085584c42ad3c58c5dbc6b7"),
"sku" : "TS0012",
"name" : "T-shirt"
},
{
"_id" : ObjectId("608d20a90e629fcc0d3a93e1"),
"sku" : "Apple1101",
"name" : "Green Apple"
}
]
Wishlist Document:
wishlist:
[
{
"_id" : ObjectId("608d8af12b7556c445f5cd87"),
"product" : ObjectId("6044351794bee8b6e0fce48f"),
"user" : ObjectId("601fb31a60e0a024143e6ea3"),
"isLiked" : false
},
{
"_id" : ObjectId("608d8bad2b7556c445f5cd88"),
"product" : ObjectId("6085584c42ad3c58c5dbc6b7"),
"user" : ObjectId("601fb31a60e0a024143e6ea3"),
"isLiked" : true
}
]
Expected Result:
Expected output:
[
{
"_id" : ObjectId("6044351794bee8b6e0fce48f"),
"sku" : "P003474",
"name" : "Kitchen Wash",
"isLiked": false
},
{
"_id" : ObjectId("6085584c42ad3c58c5dbc6b7"),
"sku" : "TS0012",
"name" : "T-shirt",
"isLiked": true
},
{
"_id" : ObjectId("608d20a90e629fcc0d3a93e1"),
"sku" : "Apple1101",
"name" : "Green Apple"
}
]
The query I'm trying:
db.getCollection('products').aggregate([
{
$lookup: {
from: "wishlists",
localField: "_id",
foreignField: "product",
as: "product"
}
},
{
$unwind:"$product"
}
])
Not able to get the above-declared result
$unwind causing the issues, first problem is it will remove document when product lookup result empty/[] if you don't specify preserveNullAndEmptyArrays : true, see working behaviour in playground
use $lookup with pipeline and match product id and user id condition
i have changed as name to isLiked in $lookup
$addFields to add isLiked parameter it its present, $arrayElemAt to get isLiked element from first element
db.getCollection('products').aggregate([
{
$lookup: {
from: "wishlists",
let: { product: "$_id" },
pipeline: [
{
$match: {
$and: [
{ $expr: { $eq: ["$$product", "$product"] } },
{ user: ObjectId("601fb31a60e0a024143e6ea3") }
]
}
}
],
as: "isLiked"
}
},
{
$addFields: {
isLiked: { $arrayElemAt: ["$isLiked.isLiked", 0] }
}
}
])
Playground

Why "as" in $lookup is replacing the complete set?

Let me first introduce you to the 2 collections I am using :
Collection 1 : users
> db.users.find().pretty()
{
"_id" : ObjectId("5ee4e727d04e4b4ac1ef115b"),
"name" : "Ashutosh Tiwari",
"age" : 21,
"email" : "ashutosh#gmail.com"
}
{
"_id" : ObjectId("5ee4e727d04e4b4ac1ef115c"),
"name" : "Maximilian",
"age" : 32,
"email" : "max#yahoo.com"
}
Collection 2 : posts
> db.posts.find().pretty()
{
"_id" : ObjectId("5ee51b7ed9f661cad505fcc6"),
"title" : "First One",
"text" : "Hey this is the first Author",
"author" : ObjectId("5ee4e727d04e4b4ac1ef115c"),
"comments" : [
{
"user" : ObjectId("5ee4e727d04e4b4ac1ef115b"),
"comment" : "This is my comment"
}
]
}
{
"_id" : ObjectId("5ee5353cd9f661cad505fcc8"),
"title" : "First One",
"author" : ObjectId("5ee4e727d04e4b4ac1ef115c"),
"comments" : [
{
"user" : ObjectId("5ee4e727d04e4b4ac1ef115b"),
"comment" : "This is my comment"
}
]
}
I want to have the user inside comments array in 2nd Collection(posts) to be replaced by the user who has written that comment.
I have tried the query below but it is replacing the comments section !
> db.posts.aggregate([
{ $lookup:
{from: "users",
localField:"comments.user",
foreignField:"_id",
as:"comments.user"
}
} ]).pretty()
{
"_id" : ObjectId("5ee51b7ed9f661cad505fcc6"),
"title" : "First One",
"text" : "Hey this is the first Author",
"author" : ObjectId("5ee4e727d04e4b4ac1ef115c"),
"comments" : {
"user" : [
{
"_id" : ObjectId("5ee4e727d04e4b4ac1ef115b"),
"name" : "Ashutosh Tiwari",
"age" : 21,
"email" : "ashutosh#gmail.com"
}
]
}
}
{
"_id" : ObjectId("5ee5353cd9f661cad505fcc8"),
"title" : "First One",
"author" : ObjectId("5ee4e727d04e4b4ac1ef115c"),
"comments" : {
"user" : [
{
"_id" : ObjectId("5ee4e727d04e4b4ac1ef115b"),
"name" : "Ashutosh Tiwari",
"age" : 21,
"email" : "ashutosh#gmail.com"
}
]
}
}
So, here, whole comments section is now replaced whereas I wanted to have the details in comments.user section so I could see the comment and the user who has posted that comment.
you need to unwind the comments array first
your query may look something like this
db.posts.aggregate([
{
$unwind: "$comments" // unwind the comments array to get a stream of documents, each document has only one comment
},
{
$lookup: {
from: "users",
localField: "comments.user",
foreignField: "_id",
as: "comments.user"
}
},
{
$unwind: "$comments.user" // we know there is only one user inside a single comment, so we can unwind this user array to be an object too (as the lookup returns an array)
},
{
$group: { // then do a group by the document _id to get unique documents with comments array instead of the same document duplicated with different comments
_id: "$_id",
author: {
$first: "$author"
},
text: {
$first: "$text"
},
title: {
$first: "$title"
},
comments: {
$push: "$comments"
}
}
}
])
you can test it here
hope it helps
You can handle it in the projection.
db.posts.aggregate([
{ $lookup:
{from: "users",
localField:"comments.user",
foreignField:"_id",
as:"cu"
}
},
{$unwind:{path:"$cu"}},
{
$project:{
"title":1,
"text":1,
"author":1,
"comments":{
user: "$cu",
comment: { $arrayElemAt: [ "$comments.comment", 0 ] },
}
}
}
])

How to use join in finding data in MongoDB database?

how to join schema in mongodb?
Example :
a collection
{
ObjectId : ObjectId("ABCDE")
userName : "Jason",
level : 30,
money : 200
}
b collection
{
Id : ObjectId("AAACC"),
userId : ObjectId("ABCDE"),
item : "sword"
}
b.aggregate....
i want result is
id : ObjectId("AAACC"), userName : "Jason", item : "sword"
You should use the aggregation pipeline to join two collections and select the required data. I assume here that you have proper identity fields named _id instead of ObjectId and Id as in your sample:
db.items.aggregate([
{
$lookup:
{
from: "users",
localField: "userId",
foreignField: "_id", // ObjectId in your sample
as: "user"
}
},
{ $unwind: "$user" },
{
$project:
{
"item": 1,
"userName": "$user.userName"
// Id: 1 if you will use your names, because only _id is selected by default
}
}
])
The first step is lookup which joins items and users collections on userId field equals _id field in users collection.
Then you should unwind results, because lookup puts all matched users into user field as an array of user documents.
And last step - project result documents to the desired format.
Now sample. If you have following documents in items collection:
{
"_id" : ObjectId("5c18df3e5d85eb27052a599c"),
"item" : "sword",
"userId" : ObjectId("5c18ded45d85eb27052a5988")
},
{
"_id" : ObjectId("5c18df4f5d85eb27052a599e"),
"item" : "helmet",
"userId" : ObjectId("5c18ded45d85eb27052a5988")
},
{
"_id" : ObjectId("5c18e2da5d85eb27052a59ee"),
"item" : "helmet"
}
And you have two users:
{
"_id" : ObjectId("5c18ded45d85eb27052a5988"),
"userName" : "Jason",
"level" : 30,
"money" : 200
},
{
"_id" : ObjectId("5c18dee35d85eb27052a598a"),
"userName" : "Bob",
"level" : 70,
"money" : 500
}
Then the query above will produce
{
"_id" : ObjectId("5c18df3e5d85eb27052a599c"),
"item" : "sword",
"userName" : "Jason"
},
{
"_id" : ObjectId("5c18df4f5d85eb27052a599e"),
"item" : "helmet",
"userName" : "Jason"
},
{
"_id" : ObjectId("5c18e2da5d85eb27052a59ee"),
"item" : "helmet"
}
NOTE: Usually user names should be unique. Consider to use them as identity for users collection. That will also give you desired result in items collection without any joins.
You can use the lookup aggregation operator to join both collections, and then project only the field from collection a that you are interested in:
db.b.aggregate([
{
$lookup: {
from: "a",
localField: "userId",
foreignField: "ObjectId",
as: "user"
}
},
{
$unwind: "$user"
},
{
$project: {
Id: 1
userName: "$user.userName",
item: 1
}
}
]);
I assume a.ObjectId should in fact be called a._id and b.Id should be b._id? Either way the same principle applies.
EDIT: had forgotten the unwind stage. You need this since your lookup will return the new joined field as an array (albeit with one element), so you need this to get rid of the square brackets.

mongodb, get the related documents from an array

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"}
}
}
])

Mongodb:Right way to collect data from two collections?

I have two collections: one is items and the second one is user_item_history. I want to fetch items with their status. Status of each item is stored in user_item_history, and other details of the item are in the items collection. we have to filter data for particular user and category of item. so user_id and category is in user_item_history collection.
user_item_history:
{
"_id" : NumberLong(25424),
"_class" : "com.samepinch.domain.registration.UserItemHistory",
"user_id" : NumberLong(25416),
"item_id" : NumberLong(26220),
"catagoryPreference" : "BOTH",
"preference" : 0.6546536707079772,
"catagory" : "FOOD",
"status" : 1,
"createdDate" : ISODate("2015-09-02T07:50:36.760Z"),
"updatedDate" : ISODate("2015-09-02T07:55:24.105Z")
}
items:
{
"_id" : NumberLong(26220),
"_class" : "com.samepinch.domain.item.Item",
"itemName" : "Shoes",
"categoryName" : "SHOPPING",
"attributes" : [
"WESTERN",
"CASUAL",
"ELEGANT",
"LATEST"
],
"isAccessed" : false,
"imageUrl" : "0bd2838e-9349-432a-a200-6e6b659e853eitemcompressed.jpg",
"catagoryPreference" : "FEMALE",
"startDate" : ISODate("2015-11-26T18:30:00Z"),
"endDate" : ISODate("2015-11-27T18:30:00Z"),
"location" : {
"coordinates" : [
77.24149558372778,
28.56973445677584
],
"type" : "Point",
"radius" : 2000
},
"createdDate" : ISODate("2015-11-16T10:49:11.858Z"),
"updatedDate" : ISODate("2015-11-16T10:49:11.858Z")
}
As the final result, I would like to have documents of this format:
{
item_id:26220,
status:1,
imageUrl: "0bd2838e-9349-432a-a200-6e6b659e853eitemcompressed.jpg"
}
Update to MongoDB 3.2 and you'll be able to use the $lookup aggregation stage, which works similarly to SQL joins.
One-to-many relationship
If there are many corresponding user_item_history documents for each items document, you can get a list of item statuses as an array.
Query
db.items.aggregate([
{
$lookup:
{
from: "user_item_history",
localField: "_id",
foreignField: "item_id",
as: "item_history"
}
},
{
$project:
{
item_id: 1,
status: "$item_history.status",
imageUrl: 1
}
}])
Example Output
{
"_id" : NumberLong(26220),
"imageUrl" : "0bd2838e-9349-432a-a200-6e6b659e853eitemcompressed.jpg",
"status" : [ 1 ]
},
{
"_id" : NumberLong(26233),
"imageUrl" : "0bd2838e-9349-432a-a200-6e6b659e853eitemcompressed.jpg",
"status" : [ 1, 2 ]
}
One-to-one relationship
If there's only one corresponding history document for every item, you can use the following approach to get the exact format you requested:
Query
db.items.aggregate([
{
$lookup:
{
from: "user_item_history",
localField: "_id",
foreignField: "item_id",
as: "item_history"
}
},
{
$unwind: "$item_history"
},
{
$project:
{
item_id: 1,
status: "$item_history.status",
imageUrl: 1
}
}])
Example Output
{
"_id" : NumberLong(26220),
"imageUrl" : "0bd2838e-9349-432a-a200-6e6b659e853eitemcompressed.jpg",
"status" : 1
}
Please bear in mind that with every additional aggregation pipeline stage you add, the performance deteriorates. So you may prefer the one-to-many query even if you have a one-to-one relationship.
Applying filtering
In your edit, you added this:
we have to filter data for particular user and category of item. so user_id and category is in user_item_history collection
To filter your results, you should add a $match step to your query:
db.items.aggregate([
{
$lookup:
{
from: "user_item_history",
localField: "_id",
foreignField: "item_id",
as: "item_history"
}
},
{
$unwind: "$item_history"
},
{
$match:
{
"item_history.user_id": NumberLong(25416),
"item_history.catagory": "FOOD"
}
},
{
$project:
{
item_id: 1,
status: "$item_history.status",
imageUrl: 1
}
}])
Please note that "category" is misspelled as "catagory" in your example data, so I also had to misspell it in the query above.