MongoDB lookup match two values - mongodb

I have two collections, tennis matches with two players and players.
matches looks like this:
{
"_id" : ObjectId("5ce51febc6dd820a820f20a5"),
"players" : [
ObjectId("5ce51c1af3cd6009a171a5b3"),
ObjectId("5ce51c1af3cd6009a171a350")
],
"result" : "4:6 6:3 7:6(7) 7:6(8)"
},
{
"_id" : ObjectId("5ce51febc6dd820a820f20a6"),
"players" : [
ObjectId("5ce51c1af3cd6009a171a005"),
ObjectId("5ce51c1af3cd6009a171a16c")
],
"result" : "6:2 4:6 6:3"
},
[...]
and players like this:
{
"_id" : ObjectId("5ce51c1af3cd6009a171a5b3"),
"name" : "Serena Williams",
"country" : "USA"
},
{
"_id" : ObjectId("5ce51c1af3cd6009a171a350"),
"name" : "Garbiñe Muguruza",
"country" : "Spain"
},
[...]
I need all matches where players[0] is equal to a name and players[1] to another name.
I've tried this without success:
db.matches.aggregate([
{
$unwind: "$players"
},
{
$lookup: {
from: "players",
localField: "players",
foreignField: "_id",
as: "tmp_join"
}
},
{
$match: {
"tmp_join.name": ["Serena Williams","Garbiñe Muguruza"]
}
}
])

You have to first $unwind the tmp_join array and then you can use $in to find the documents contain name.
db.matches.aggregate([
{ "$lookup": {
"from": "players",
"localField": "players",
"foreignField": "_id",
"as": "tmp_join"
}},
{ "$unwind": "$tmp_join" },
{ "$match": {
"tmp_join.name": {
"$in": ["Serena Williams","Garbiñe Muguruza"]
}
}}
])
Use below aggregation if you are using mongodb 3.6 and above
db.matches.aggregate([
{ "$lookup": {
"from": "players",
"let": { "players": "$players" },
"pipeline": [
{ "$match": {
"$expr": { "$in": ["$_id", "$$players"] },
"name": { "$in": ["Serena Williams", "Garbiñe Muguruza"] }
}}
],
"as": "tmp_join"
}},
{ "$match": { "$expr": { "$gt": [{ "$size": "$tmp_join" }, 1] }}}
])

Related

How to join multiple tables in mongodb

I currently have 5 tables that need to joined due to their coupling.
Using $lookup I can join the Order table with the Plan table and get what I need, but how do I go about the other tables?
Here is each table, and the Id/table it needs to connect with
Plan - _id, unassignedOrderIds(array)
DriverPlan - _id, planId, orderIds(array), driverId, vehicleId
Driver - _id, vehicleId
Vehicle - _id
Orders - _id
In the end I'm looking for mongoDb to return a Plan object that has UnassignedOrders loaded and DriverPlans loaded. Followed by DriverPlans having its Orders,Driver, and Vehicle loaded.
Here is what I have so far:
db.Plan.aggregate([
// Unwind the source
{ "$unwind": "$UnassignedOrderIds" },
// Do the lookup matching
{ "$lookup": {
"from": "Order",
"localField": "UnassignedOrderIds",
"foreignField": "_id",
"as": "UnassignedOrders"
}},
// Unwind the result arrays ( likely one or none )
{ "$unwind": "$UnassignedOrders" },
// Group back to arrays
{ "$group": {
"_id": "$_id",
"Order": { "$push": "$Order" },
"UnassignedOrders": { "$push": "$UnassignedOrders" }
}}
])
Sample Document:
//Plan
{
"_id" : ObjectId("5c1d244de707b20cece645f1"),
"UnassignedOrderIds" : [
ObjectId("5c1d247fe707b20cece6462e"),
ObjectId("5c1d035de707b20cece63104")
]
}
//DriverPlan
[{
"_id" : ObjectId("123d247fe707b20cece6462e"),
"PlanId" : ObjectId("5c1d244de707b20cece645f1"),
"DriverId" : ObjectId("1c1d247fe707b20cece64622"),
"VehicleId" : ObjectId("3c1d247fe707b20cece64633"),
"OrderIds": [
ObjectId("5c1d247fe707b20cece64621"),
ObjectId("5c1d247fe707b20cece64624")
]
},{
"_id" : ObjectId("123d247fe707b20cece64655"),
"PlanId" : ObjectId("5c1d244de707b20cece645f1"),
"DriverId" : ObjectId("2c1d035de707b20cece63104"),
"VehicleId" : null,
"OrderIds": [
ObjectId("5c1d247fe707b20cece64625")
]
}]
//Orders
[{
"_id" : ObjectId("5c1d247fe707b20cece6462e"),
"name" "Order1"
},{
"_id" : ObjectId("5c1d035de707b20cece63104"),
"name" "Order2"
},{
"_id" : ObjectId("5c1d247fe707b20cece64621"),
"name" "Order3"
},{
"_id" : ObjectId("5c1d247fe707b20cece64624"),
"name" "Order4"
},{
"_id" : ObjectId("5c1d247fe707b20cece64625"),
"name" "Order5"
}]
//Driver
[{
"_id" : ObjectId("1c1d247fe707b20cece64622"),
"vehicleId" : ObjectId("3c1d247fe707b20cece6462e"),
"name" "Driver1"
},{
"_id" : ObjectId("2c1d035de707b20cece63104"),
"vehicleId" : null,
"name" "Driver2"
},{
"_id" : ObjectId("3c1d247fe707b20cece64621"),
"vehicleId" : ObjectId("3c1d035de707b20cece63104"),
"name" "Driver3"
}]
//Vehicle
[{
"_id" : ObjectId("3c1d247fe707b20cece6462e"),
"name" "Vehicle1"
},{
"_id" : ObjectId("3c1d035de707b20cece63104"),
"name" "Vehicle2"
},{
"_id" : ObjectId("3c1d247fe707b20cece64633"),
"name" "Vehicle3"
}]
The Expected output is json object as follows
//Plan with children loaded
{
"_id" : ObjectId("5c1d244de707b20cece645f1"),
"UnassignedOrderIds" : [
ObjectId("5c1d247fe707b20cece6462e"),
ObjectId("5c1d035de707b20cece63104")
],
"UnassignedOrders": [{
"_id" : ObjectId("5c1d247fe707b20cece6462e"),
"name" "Order1"
},{
"_id" : ObjectId("5c1d035de707b20cece63104"),
"name" "Order2"
}],
"DriverPlans" :
[{
"_id" : ObjectId("123d247fe707b20cece6462e"),
"PlanId" : ObjectId("5c1d244de707b20cece645f1"),
"DriverId" : ObjectId("1c1d247fe707b20cece64622"),
"Driver": {
"_id" : ObjectId("1c1d247fe707b20cece64622"),
"vehicleId" : ObjectId("3c1d247fe707b20cece6462e"),
"name" "Driver1"
},
"VehicleId" : ObjectId("3c1d247fe707b20cece64633"),
"Vehicle" : {
"_id" : ObjectId("3c1d247fe707b20cece64633"),
"name" "Vehicle3"
},
"OrderIds": [
ObjectId("5c1d247fe707b20cece64621"),
ObjectId("5c1d247fe707b20cece64624")
],
"Orders" : [
{
"_id" : ObjectId("5c1d247fe707b20cece64621"),
"name" "Order3"
},{
"_id" : ObjectId("5c1d247fe707b20cece64624"),
"name" "Order4"
}]
},{
"_id" : ObjectId("123d247fe707b20cece64655"),
"PlanId" : ObjectId("5c1d244de707b20cece645f1"),
"DriverId" : ObjectId("2c1d035de707b20cece63104"),
"Driver" : {
"_id" : ObjectId("2c1d035de707b20cece63104"),
"vehicleId" : null,
"name" "Driver2"
}
"VehicleId" : null,
"Vehicle" : null,
"OrderIds": [
ObjectId("5c1d247fe707b20cece64625")
],
"Orders": [{
"_id" : ObjectId("5c1d247fe707b20cece64625"),
"name" "Order5"
}
]
}]
}
You can use below aggregation
db.Plan.aggregate([
{ "$lookup": {
"from": Order.collection.name,
"let": { "unassignedOrderIds": "$UnassignedOrderIds" },
"pipeline": [
{ "$match": { "$expr": { "$in": ["$_id", "$$unassignedOrderIds"] } } }
],
"as": "UnassignedOrderIds"
}},
{ "$lookup": {
"from": DriverPlan.collection.name,
"let": { "planId": "$_id" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$PlanId", "$$planId"] } } },
{ "$lookup": {
"from": Driver.collection.name,
"let": { "driveId": "$DriverId" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$_id", "$$driveId"] } } }
],
"as": "Driver"
}},
{ "$lookup": {
"from": Vehicle.collection.name,
"let": { "vehicleId": "$VehicleId" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$_id", "$$vehicleId"] } } }
],
"as": "Vehicle"
}},
{ "$lookup": {
"from": Order.collection.name,
"let": { "orderIds": "$OrderIds" },
"pipeline": [
{ "$match": { "$expr": { "$in": ["$_id", "$$orderIds"] } } }
],
"as": "Orders"
}}
],
"as": "DriverPlans"
}}
])
With mongodb 3.4 and below $lookup syntax
db.Plan.aggregate([
{ "$lookup": {
"from": Order.collection.name,
"localField": "UnassignedOrderIds",
"foreignField": "_id",
"as": "UnassignedOrderIds"
}},
{ "$lookup": {
"from": DriverPlan.collection.name,
"localField": "_id",
"foreignField": "PlanId",
"as": "DriverPlans"
}},
{ "$unwind": "$DriverPlans" },
{ "$lookup": {
"from": Driver.collection.name,
"localField": "DriverPlans.DriverId",
"foreignField": "_id",
"as": "DriverPlans.Driver"
}},
{ "$unwind": "$DriverPlans.Driver" },
{ "$lookup": {
"from": Vehicle.collection.name,
"localField": "DriverPlans.VehicleId",
"foreignField": "_id",
"as": "DriverPlans.Vehicle"
}},
{ "$unwind": "$DriverPlans.Vehicle" },
{ "$lookup": {
"from": Order.collection.name,
"localField": "DriverPlans.OrderIds",
"foreignField": "_id",
"as": "DriverPlans.Orders"
}},
{ "$group": {
"_id": "$_id",
"DriverPlans": { "$push": "$DriverPlans" }
}}
])

Parent child reversal in the result

I am using MongoDB 3.4.
Have 2 collection as follows.
Collection 1:- type
{
"_id": {
"$numberLong": "1234"
},
"name" : "board1"
"type" : "electronic"
},
{
"_id": {
"$numberLong": "1235"
},
"name" : "board2",
"type" : "electronic"
}
Collection 2:- products
{
"_id": {
"$numberLong": "9876"
},
"types" : [
"1234",
"1235",
"1238"
]
},
{
"_id": {
"$numberLong": "9875"
},
"types" : [
"1234",
"1238"
]
}
Type collection will have multiple types and each product in products collection will have multiple types.
There can be multiple document with different ids for the same type in type collection. And, product collection might have types array with different Ids of same type or different type.
I would like to get all the ids of type electronic and find the products which has id in the types array for each product.
I want result like the below one.
{
"_id": {
"$numberLong": "1234"
},
"name" : "board1",
"products" : [
"9876",
"9875"
]
},
{
"_id": {
"$numberLong": "1235"
},
"name" : "board2"
"products" : [
"9876",
"9875"
]
}
Currently, I am making so many calls, like for each type id, get all products.
Is there any other simple way with single query using $lookup or any other mechanism?
You can try below aggregation in mongodb 3.6 and above
db.types.aggregate([
{ "$match": { "type" : "electronic" }},
{ "$lookup": {
"from": "testCollection2",
"let": { "typeId": "$_id" },
"pipeline": [
{ "$match": { "$expr": { "$in": ["$$typeId", "$types"] }}}
],
"as": "products"
}},
{ "$addFields": {
"products": "$products._id"
}}
])
You can try bbelow aggregation in mongodb 3.4
db.types.aggregate([
{ "$match": { "type" : "electronic" }},
{ "$lookup": {
"from": "testCollection2",
"localField": "_id",
"foreignField": "types",
"as": "products"
}},
{ "$addFields": {
"products": "$products._id"
}}
])
In MongoDB 3.4 you can use $lookup and then $addFields to get _id from products:
db.types.aggregate([
{
"$match": { "type" : "electronic" }
},
{
$lookup: {
from: "products",
localField: "_id",
"foreignField": "types",
"as": "products"
}
},
{
$project: {
field1: 1,
field2: 1,
products: {
$map: {
input: "$products",
as: "p",
in: "$$p._id"
}
}
}
}
])

$lookup with nested data in mongodb

How do I combine 2 array objects using mongoDB NoSQL? Because I have tried to find some of the same problems here that I got, but I have not found the answers and problems that match what I got.
If someone here wants to help me, here are the problems I want to solve.
Example: I tried using noSQL in mongoDB like this:
db.tables.aggregate([
{ $lookup: { from: 'reservations', localField: '_id', foreignField: 'tableId', as: 'reservation' }},
{ $unwind: { path: '$reservation', 'preserveNullAndEmptyArrays': true }},
{ $lookup: { from: 'orders', localField: 'reservation._id', foreignField: 'reservationId', as: 'orders' }},
{ $lookup: { from: 'products', localField: 'orders.productId', foreignField: '_id', as: 'products' }},
{
$project: {
'_id': 1,
'initial': 1,
'description': 1,
'reservation._id': 1,
'reservation.guest': 1,
'orders._id': 1,
'orders.status': 1,
'orders.quantity': 1,
'orders.productId': 1,
'products._id': 1,
'products.name': 1
}
},
]);
After running noSQL mongoDB above, I got the results below:
{
"_id" : ObjectId("5b63e519514cf01c2864749a"),
"description" : "Kursi VIP 01",
"reservation" : {
"_id" : ObjectId("5b63f104514cf01c286474b6"),
"guest" : "Jhon Doe"
},
"orders" : [
{
"_id" : ObjectId("5b63f239514cf01c286474bb"),
"productId" : ObjectId("5b63e72d514cf01c286474a3"),
"status" : "3",
"quantity" : "2"
},
{
"_id" : ObjectId("5b63f252514cf01c286474bc"),
"productId" : ObjectId("5b63e7de514cf01c286474a6"),
"status" : "2",
"quantity" : "3"
},
{
"_id" : ObjectId("5b63f267514cf01c286474bd"),
"productId" : ObjectId("5b63e937514cf01c286474ac"),
"status" : "0",
"quantity" : "2"
}
],
"products" : [
{
"_id" : ObjectId("5b63e72d514cf01c286474a3"),
"name" : "AQUA 600ML"
},
{
"_id" : ObjectId("5b63e7de514cf01c286474a6"),
"name" : "Nasi Goreng Kecap Asin"
},
{
"_id" : ObjectId("5b63e937514cf01c286474ac"),
"name" : "Daging Ayam Goreng"
}
]
}
Now, my Question is. How to merge/combine 2 Object Array ("orders and products"), So I can get results like this:
{
"_id" : ObjectId("5b63e519514cf01c2864749a"),
"description" : "Kursi VIP 01",
"reservation" : {
"_id" : ObjectId("5b63f104514cf01c286474b6"),
"guest" : "Jhon Doe"
},
"orders" : [
{
"_id" : ObjectId("5b63f239514cf01c286474bb"),
"productId" : ObjectId("5b63e72d514cf01c286474a3"),
"name" : "AQUA 600ML",
"status" : "3",
"quantity" : "2"
},
{
"_id" : ObjectId("5b63f252514cf01c286474bc"),
"productId" : ObjectId("5b63e7de514cf01c286474a6"),
"name" : "Nasi Goreng Kecap Asin",
"status" : "2",
"quantity" : "3"
},
{
"_id" : ObjectId("5b63f267514cf01c286474bd"),
"productId" : ObjectId("5b63e937514cf01c286474ac"),
"name" : "Daging Ayam Goreng"
"status" : "0",
"quantity" : "2"
}
]
}
I hope, someone can help me.
Thanks in advance.
You can try below aggregation with mongodb 3.4
You need to $unwind the orders array to add the field($addFields) name inside orders and then $group to rollback orders again to the make an array field
db.tables.aggregate([
{ "$lookup": {
"from": "reservations",
"localField": "_id",
"foreignField": "tableId",
"as": "reservation"
}},
{ "$unwind": { "path": '$reservation', 'preserveNullAndEmptyArrays': true }},
{ "$lookup": {
"from": "orders",
"localField": "reservation._id",
"foreignField": "reservationId",
"as": "orders",
}},
{ "$unwind": { "path": '$orders', 'preserveNullAndEmptyArrays': true }},
{ "$lookup": {
"from": "products",
"localField": "orders.productId",
"foreignField": "_id",
"as": "orders.products"
}},
{ "$unwind": { "path": '$orders.products', 'preserveNullAndEmptyArrays': true }},
{ "$addFields": {
"orders.name": "$orders.products.name"
}},
{ "$group": {
"_id": "$_id",
"description": { "$first": "$description" },
"reservation": { "$first": "$reservation" },
"orders": { "$push": "$orders" }
}},
{ "$project": { "orders.products": 0 }}
])
Which is far simple with mongodb 3.6 nested $lookup version
db.tables.aggregate([
{ "$lookup": {
"from": "reservations",
"let": { "reservationId": "$_id" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$tableId", "$$reservationId" ] } } }
],
"as": "reservations"
}},
{ "$lookup": {
"from": "orders",
"let": { "reservationId": "$reservation._id" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$reservationId", "$$reservationId" ] } } },
{ "$lookup": {
"from": "products",
"let": { "productId": "$productId" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$_id", "$$productId" ] } } },
{ "$project": { "_id": false }}
],
"as": "products"
}},
{ "$unwind": "$products" },
{ "$addFields": { "name": "$products.name" } },
{ "$project": { "products": 0 }}
],
"as": "orders"
}}
])

MongoDB aggregation with multiple inter-related collections

I have the following collections;
users, user_roles, market_managers, program_markets, programs
I can export the data from mongoDB in CSV format and construct relational tables and run the following SQL statement to retrieve the data I need;
select u.name, ur.type, pm.code, p.program_name, p.shortnam, p.enddate, p.description
from users u, user_roles ur, market_managers mm, program_markets pm, programs p
where u.roles_0 = ur.id
and ur.type = 'CountryManager'
and u.id = mm.userid
and pm.programid = mm.programid
and pm.id = mm.marketid
and p.id = pm.programid;
How can I achieve the same results by writing native mongo syntax?
Here's a list of collections in scope:
users collection
{
"_id" : ObjectId("5b3f59c96e1c20d84e2b5ce5"),
"name" : "some_country_manager",
"roles" : [
"5b2a8df52b3a6f945d4e85fe"
]
}
user_roles collection
{
"_id" : ObjectId("5b430f9981f6a7382a24995b"),
"type" : "countryManager",
"name" : "Country Manager"
}
market_managers collection
{
"_id" : ObjectId("5894bcf60418700b70745fc9"),
"programId" : "5862c1d43b1a1b113a8a841f",
"marketId" : "5862c1d43b1a1b113a8a84a9",
"userId" : "5b3f59c96e1c20d84e2b5ce5"
}
program_markets collection
{
"_id" : ObjectId("5b43588689c117241c171e8c"),
"code" : "de",
"startDate" : "2018-07-09",
"endDate" : "2019-07-09"
}
programs collection
{
"_id" : ObjectId("5862c1d43b1a1b113a8a841f"),
"name" : "Test Program",
"shortname" : "TestP",
"status" : "planned",
"startDate" : "2018-07-09",
"endDate" : "2019-07-09",
"description" : "Test Program"
}
You can try below aggregation if you have mongodb 3.6 and above
Users.aggregate([
{ "$match": { "_id": mongoose.Types.ObjectId(id.id) } },
{ "$lookup": {
"from": UserRoles.collection.name,
"let": { "roles_0": "$roles_0" },
"pipeline": [
{ "$match": {
"$expr": { "$eq": [ "$_id", "$$roles_0" ] },
"type": "CountryManager"
}}
],
"as": "role"
}},
{ "$lookup": {
"from": MarketManagers.collection.name,
"let": { "user_id": "$_id" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$_id", "$$user_id" ] }}},
{ "$lookup": {
"from": Programs.collection.name,
"let": { "programid": "$programid" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$_id", "$$programid" ] }}}
],
"as": "programs"
}},
{ "$lookup": {
"from": ProgramManagers.collection.name,
"let": { "marketId": "$marketId" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$_id", "$$marketId" ] }}},
{ "$lookup": {
"from": Programs.collection.name,
"let": { "id": "$id" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$_id", "$$id" ] }}}
],
"as": "programs"
}}
],
"as": "programManagers"
}}
],
"as": "marketManagers"
}}
])

How to make lookup between two collections when an item in an array exists in the other collection?

In Lookup with a pipeline, I would like to get the linked records from an array in the parent document.
// Orders
[{
"_id" : ObjectId("5b5b91a25c68de2538620689"),
"Name" : "Test",
"Products" : [
ObjectId("5b5b919a5c68de2538620688"),
ObjectId("5b5b925a5c68de2538621a15")
]
}]
// Products
[
{
"_id": ObjectId("5b5b919a5c68de2538620688"),
"ProductName": "P1"
},
{
"_id": ObjectId("5b5b925a5c68de2538621a15"),
"ProductName": "P2"
}
,
{
"_id": ObjectId("5b5b925a5c68de2538621a55"),
"ProductName": "P3"
}
]
How to make a lookup between Orders and Products when Products field is an array!
I tried this query
db.getCollection("Orders").
aggregate(
[
{
$lookup:
{
from: "Products",
let: { localId: "$_id" , prods: "$Products" },
pipeline: [
{
"$match":
{
"_id" : { $in: "$$prods" }
}
},
{
$project:
{
"_id": "$_id",
"name": "$prods" ,
}
}
],
as: "linkedData"
}
},
{
"$skip": 0
},
{
"$limit": 1
},
]
)
This is not working because $in is expecting an array, and even though $$prods is an array, it is not accepting it.
Is my whole approach correct? How to make this magic join ?
You were going in the right direction the only thing you missed here is to use expr with in aggregation operator which matches the same fields of the document
db.getCollection("Orders").aggregate([
{ "$lookup": {
"from": "Products",
"let": { "localId": "$_id" , "prods": "$Products" },
"pipeline": [
{ "$match": { "$expr": { "$in": [ "$_id", "$$prods" ] } } },
{ "$project": { "_id": 1, "name": "$ProductName" } }
],
"as": "linkedData"
}},
{ "$skip": 0 },
{ "$limit": 1 }
])
See the docs here
You just need regular $lookup, the documentation states that:
If your localField is an array, you may want to add an $unwind stage to your pipeline. Otherwise, the equality condition between the localField and foreignField is foreignField: { $in: [ localField.elem1, localField.elem2, ... ] }.
So for below aggregation:
db.Orders.aggregate([
{
$lookup: {
from :"Products",
localField: "Products",
foreignField: "_id",
as: "Products"
}
}
])
you'll get following result for your sample data:
{
"_id" : ObjectId("5b5b91a25c68de2538620689"),
"Name" : "Test",
"Products" : [
{
"_id" : ObjectId("5b5b919a5c68de2538620688"),
"ProductName" : "P1"
},
{
"_id" : ObjectId("5b5b925a5c68de2538621a15"),
"ProductName" : "P2"
}
]
}
have you try unwind before the lookup. use unwind to brak the array annd then make lookup.