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.
Related
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.
collection A document 1
{
"_id" : ObjectId("5c2ee03224acf45a663d8f09"),
"_class" : "document.domain.DDD",
"generated" : false,
"linkIds" : [],
"types" : [],
"metadata" : {
"templateId" : "ABDC",
"userId" : "Master"
}
"versions" : [
{
"revision" : "fb4fb8ec-edfe-4a3e-a1a9-c8c4b2bce678",
"fileId" : "5c2ee03224acf45a663d8f08"
}
]
}
collection B document 1
{
"_id" : ObjectId("5c2ee03224acf45a663d8f08"),
"_class" : "document.domain.RDF",
"extension" : ".pdf",
"rootPath" : "D"
"size" : 152754
}
the field id in collection A , document 1 appears as String in objectid of collection B doc 1.
how to lookup for the string in collection B which appears as objecid?
you have to use mongodb aggregation there
you have view this more on
https://docs.mongodb.com/manual/core/aggregation-pipeline/
and you have to use $lookup operator more you learn this on
https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/
you can achived your task doing this
collectionA.aggregate.([
{
$match:{},// To match and get all doc
},
{
$lookup:
{
from: collection B//<collection to join>,
localField: versions.fileId//<field from the input documents>,
foreignField: _id//<field from the documents of the "from" collection>,
as: versions.file//<output array field>
}
}])
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"
}
}
]);
So I have collections projects which has field contacts.envCon.$id:
{
"contacts" : {
...
"envCon" : {
"$ref" : "contacts",
"$id" : ObjectId("5807966090c01f4174cb1714") <---- NOTICE!!!
}
...
}
}
On contacts collection Object with id Example12345 looks like this:
{
"_id" : ObjectId("5807966090c01f4174cb1714"),
"name" : "Terracon"
}
So I tried the following $lookup from aggregation framework:
db.getCollection('projects').aggregate([
{
$lookup:{
from: "contacts",
localField: "contacts.envCon.id",
foreignField: "id",
as: "example"
}
}
]);
But it is not doing the JOIN what am I missing? how to do lookups between 2 collections using contacts.envCon.id from projects and _id from contacts.
I'm using meteor just in case.
I'm not sure what is going on with your ObjectId values; I'm not even able to insert a field value of ObjectId("Example12345"). The difference in ObjectID values between the two collections is likely what is causing the $lookup to fail.
The aggregation query you provided works in my environment when I let MongoDB generate the _id ObjectID value in "contacts" and store the same value in the "projects" collection.
$ db.projects.find().pretty()
{
"_id" : ObjectId("580832011b3c40dba2ae6e32"),
"contacts" : {
"envCon" : DBRef("contacts", ObjectId("580831d61b3c40dba2ae6e31"))
}
}
$ db.contacts.find().pretty()
{ "_id" : ObjectId("580831d61b3c40dba2ae6e31"), "name" : "Terracon" }
$ db.getCollection("projects").aggregate([ { "$lookup" : { "from" : "contacts", "localField" : "contacts.envCon.id", "foreignField" : "id", "as" : "example" } } ]).pretty()
{
"_id" : ObjectId("580832011b3c40dba2ae6e32"),
"contacts" : {
"envCon" : DBRef("contacts", ObjectId("580831d61b3c40dba2ae6e31"))
},
"example" : [
{
"_id" : ObjectId("580831d61b3c40dba2ae6e31"),
"name" : "Terracon"
}
]
}
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.