mongo join two collections and use aggregation - mongodb

im trying to get only those products for whom no order exists, so each product has an order id, these audit tables were linked to orders, but those orders are now deleted, i need to locate those products with no orders.
I know when doing aggregates if the joining collection has no records its not returning anything as "docs", but how can i get it to return me docs == empty or null only..
db.products.aggregate([
{
$match: {
$and: [
{ "docs": { $exists: false } }
]
}
},
{
$lookup: {
from: "orders",
localField: "orderId",
foreignField: "orderId",
as: "docs"
}
},
{
$unwind:
{
path: "$docs",
preserveNullAndEmptyArrays: true
}
},
{ $limit: 10 }
]).pretty()

db.products.aggregate([
{
$lookup: {
from: "orders",
localField: "orderId",
foreignField: "orderId",
as: "docs"
}
},
{ $match: { docs: [] },
{ $limit: 10 }
]).pretty()

Related

MongoDB Query to find the list of customers who has SELL and PURCHASE Both

I am using mongoDB as NoSql Database. I have two collections One is CUSTOMER(CID,CNAME) and Other one is SHOP(Bill_No, Type, Amount,CID)
In SHOP Collection, CID is referenced with Customer(CID).Also Type can be either "SELL" or "PURCHASE".
db.CUSTOMER.insertOne({"CID":1,"CNAME":"Mark"});
db.CUSTOMER.insertOne({"CID":2,"CNAME":"Chris"});
db.CUSTOMER.insertOne({"CID":3,"CNAME":"James"});
db.SHOP.insertOne({"Bill_No":1,"TYPE":"SELL","Amount":1000,"CID":1});
db.SHOP.insertOne({"Bill_No":2,"TYPE":"SELL","Amount":350,"CID":2});
db.SHOP.insertOne({"Bill_No":3,"TYPE":"PURCHASE","Amount":450,"CID":1});
db.SHOP.insertOne({"Bill_No":4,"TYPE":"PURCHASE","Amount":360,"CID":3});
db.SHOP.insertOne({"Bill_No":5,"TYPE":"SELL","Amount":800,"CID":3});
What should be query to find the list of customers who has SELL and PURCHASE Both.
So according to given data the output should be like
"CID":1, "NAME":"MARK"
"CID":3, "NAME":"JAMES"
Thanks in advance..
Demo - https://mongoplayground.net/p/ZXylHb2g7Dm
Get all details linked to CUSTOMER from SHOP using $
lookup
Use $group combine all types into 1 document
To match both types "PURCHASE", "SELL" use $all
db.CUSTOMER.aggregate([
{
$lookup: {
from: "SHOP",
let: { c_id: "$CID" },
pipeline: [
{ $match: { $expr: {$eq: [ "$CID", "$$c_id" ] } } },
{ $group: { _id: null, types: { "$addToSet": "$TYPE" } } },
{ $match: { types: { $all: [ "PURCHASE", "SELL" ] } } }
],
as: "trades"
}
},
{ $match: { "trades.types.0": { $exists: true } } },
{ $project: { _id: 0, CID: 1, CNAME: 1 } }
])
You can use $lookup for aggregation, click here for reference.
Syntax
{
$lookup:
{
from: <collection to join>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
as: <output array field>
}
}
In your case the query would be
db.CUSTOMER.aggregate([
{
$lookup:
{
from: "SHOP",
localField: "CID",
foreignField: "CID",
as: "SELL_PURCHASE"
}
}
])

Join multiple collections in mongodb and keep all fields

I am new to NoSQL databases, and I got a little confused with collection aggregation. Here is what I am trying to do: I have three collections: productCollection, detailsCollection and brandCollection.
productCollection has the following fields: _id, name, details
detailsCollection has _id and brand
brandCollection has _id and name
These collections also have other fields, but these are the most interesting for me. As you may guess, details in productCollection is a reference to _id in detailsCollection, while brand in detailsCollection is a reference to _id in brandCollection. What I need is to get the collection with products and their brands. So, basically, I need to join these three collections and extract name from productCollection and name from brandCollection
So far, I managed to write this script:
db.productCollection.aggregate([
{
$lookup: {
from: "detailsCollection",
localField: "details",
foreignField: "_id",
as: "det"
}
},
{
$replaceRoot: { newRoot: { $mergeObjects: [ { $arrayElemAt: [ "$det", 0 ] }, "$$ROOT" ] } }
},
{ $project: { "det": 0 } },
{
$lookup: {
from: "brandCollection",
localField: "brand",
foreignField: "_id",
as: "br"
}
},
{
$replaceRoot: { newRoot: { $mergeObjects: [ { $arrayElemAt: [ "$br", 0 ] }, "$$ROOT" ] } }
},
{ $project: { "br": 0 } }
])
It shows me all the fields in all three collections, but it does not show me the brand's name. I think it might be because the field name appears in both productCollection and brandCollection. All other fields are fine.
Hence, my question is: how do I make name from brandCollection appear in the result too? Maybe I can rename it in the process to be shown under another name? And is there an easier way to join these three collections? Or is the script above fine?
Thank you for any help!
$lookup with detailsCollection collection
$lookup with brandCollection and pass localField as brand id
$arrayElemAt to get first element from brand result
remove details field its no longer needed
db.productCollection.aggregate([
{
$lookup: {
from: "detailsCollection",
localField: "details",
foreignField: "_id",
as: "brand"
}
},
{
$lookup: {
from: "brandCollection",
localField: "brand.brand",
foreignField: "_id",
as: "brand"
}
},
{
$addFields: {
brand: {
$arrayElemAt: ["$brand.name", 0]
},
details: "$$REMOVE"
}
}
])
Playground

Mongoose lookup across 3 collections using foreign key

I have found a few questions that relate to this (here and here) but I have been unable to interpret the answers in a way that I can understand how to do what I need.
I have 3 collections: Organisations, Users, and Projects. Every project belongs to one user, and every user belongs to one organisation. From the user's id, I need to return all the projects that belong to the organisation that the logged-in user belongs to.
Returning the projects from the collection that belong to the user is easy, with this query:
const projects = await Project.find({ user: req.user.id }).sort({ createdAt: -1 })
Each user has an organisation id as a foreign key, and I think I need to do something with $lookup and perhaps $unwind mongo commands, but unlike with SQL queries I really struggle to understand what's going on so I can construct queries correctly.
EDIT: Using this query
const orgProjects = User.aggregate(
[
{
$match: { _id: req.user.id }
},
{
$project: { _id: 0, org_id: 1 }
},
{
$lookup: {
from: "users",
localField: "organisation",
foreignField: Organisation._id,
as: "users_of_org"
}
},
{
$lookup: {
from: "projects",
localField: "users_of_org._id",
foreignField: "user",
as: "projects"
}
},
{
$unset: ["organisation", "users_of_org"]
},
{
$unwind: "$projects"
},
{
$replaceWith: "$projects"
}
])
Seems to almost work, returning the following:
Aggregate {
_pipeline: [
{ '$match': [Object] },
{ '$project': [Object] },
{ '$lookup': [Object] },
{ '$lookup': [Object] },
{ '$unset': [Array] },
{ '$unwind': '$projects' },
{ '$replaceWith': '$projects' }
],
_model: Model { User },
options: {}
}
assuming your documents have a schema like this, you could do an aggregation pipeline like below with 2 $lookup stages.
db.users.aggregate(
[
{
$match: { _id: "user1" }
},
{
$project: { _id: 0, org_id: 1 }
},
{
$lookup: {
from: "users",
localField: "org_id",
foreignField: "org_id",
as: "users_of_org"
}
},
{
$lookup: {
from: "projects",
localField: "users_of_org._id",
foreignField: "user_id",
as: "projects"
}
},
{
$unset: ["org_id", "users_of_org"]
},
{
$unwind: "$projects"
},
{
$replaceWith: "$projects"
}
])

lookup with condition in mongoose

I have two collections. articles and bookmarks.
articles
{
_id: "5faa889ade5e0a6326a873d3",
name: "article 1"
},
{
_id: "5faa889ade5e0a6326a873d",
name: "article 2"
}
bookmarks
{
_id: "5faa889ade5e0a6326a873d1",
user_id: "5fc7b50da483a66a86aa7e9e",
model_id: "5faa889ade5e0a6326a873d3"
}
I want to join article with bookmark. if user bookmarked a article.
what i have tried
const aggregate = await Articles.aggregate([{
$lookup: {
from: "categories",
localField: "category_id",
foreignField: "_id",
as: "category_id"
}
},
{
$lookup: {
from: "bookmarks",
localField: "_id",
foreignField: "model_id",
as: "bookmarks"
}
}
]);
but it will gives all bookmark for the article not only logged in user bookmark. so how can I add a condition.
{ "user_id": objectId(req.user._id) } // logged user id
You can use $lookup with pipeline starting from MongoDB v3.6,
let to pass localField _id as model_id variable, you can use the field inside lookup pipeline using $$ reference,
pipeline to put $match stage and match your required conditions and user_id condition
{
$lookup: {
from: "bookmarks",
let: { model_id: "$_id" },
pipeline: [
{
$match: {
$expr: { $eq: ["$$model_id", "$model_id"] },
user_id: objectId(req.user._id)
}
}
],
as: "bookmarks"
}
}
Other option for MongoDB v3.4,
$filter to iterate loop of bookmarks and get filtered bookmarks on the base of condition
{
$lookup: {
from: "bookmarks",
localField: "_id",
foreignField: "model_id",
as: "bookmarks"
}
},
{
$addFields: {
bookmarks: {
$filter: {
input: "$bookmarks",
cond: { $eq: ["$$this.user_id", objectId(req.user._id)] }
}
}
}
}
You can have nested pipeline inside $lookup,
db.articles.aggregate([
{
$lookup: {
from: "bookmarks",
let: {
article_id: "$_id"
},
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: [
"$model_id",
"$$article_id"
]
},
{
$eq: [
"$user_id",
"5fc7b50da483a66a86aa7e9a"
]
}
]
}
}
}
],
as: "bookmarks"
}
}
])
Here's a working playground

Meteor Mongo Aggregate $lookup specify output field

I have two collections: Products and Stocks.
The relation between these two collections is one to one.
Products structure:
{
_id:
sku:
....
}
Stocks structure :
{
_id:
sku:
availability: []
....
}
My query:
Products.aggregate([
{
$match: cAux
}, {
$lookup: {
from: "Stocks",
localField: "sku",
foreignField: "sku",
as: "availability"
}
}, {
$sort: PRODUCT_SORT
}
]);
The result from this "join" is
{
_id:
sku:
availability: {_id:, sku:, **availabity**: []}
...
}
The join is okay, but I would like to only have the availability array field being joined and not the whole Stock document. Whats the best way to accomplish this? Any help would be appreciated.
Solution
Products.aggregate([
{
$match: cAux
}, {
$lookup: {
from: "Stocks",
localField: "sku",
foreignField: "sku",
as: "availability"
}
}, {
$project: {
...PRODUCT_FIELDS,
availability: {
$arrayElemAt: ['$availability.availability', 0]
}
}
}, {
$sort: PRODUCT_SORT
}
]);