lookup with condition in mongoose - mongodb

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

Related

Mongodb can't group properly after a chain of lookup/unwined stages

I have a complex query requiring a chain of nested unwinds and grouping them in order.
here are relations between models [policy, asset, assetType, field, fieldType]
policy has many asset
asset has one assetType
asset has many fields
field has one fieldType
example object would be something like, where
{
policy: {
..., // policy fields
assets: [
{
..., // asset fields
assetType: {},
fields: [
{
..., // field fields
fieldType: {},
},
],
},
],
},
}
Now I'm trying to do a pipeline to get the nested date with the same structure above
this is the far I get to
mongoose.model('policy').aggregate([
{
$lookup: {
from: 'assets',
localField: 'assets',
foreignField: '_id',
as: 'assets',
},
},
{
$lookup: {
from: 'assettypes',
let: {
id: '$assets._id',
fields: '$assets.fields',
name: '$assets.displayName',
atId: '$assets.assetType',
},
pipeline: [
{
$match: {
$expr: {
$eq: ['$_id', '$$atId'],
},
},
},
{
$project: {
_id: '$$id',
assetId: '$$id',
assetDisplayName: '$$name',
assetFields: '$$fields',
type: 1,
name: 1,
},
},
],
as: 'assets',
},
},
{
$unwind: {
path: '$assets',
},
},
{
$unwind: {
path: '$assets.fields',
},
},
{
$lookup: {
from: 'fieldtypes',
let: {
ftId: '$assets.fields.fieldType',
value: '$assets.fields.value',
ref: '$assets._id',
},
pipeline: [
{
$match: {
$expr: {
$eq: ['$_id', '$$ftId'],
},
},
},
{
$addFields: {
value: '$$value',
assetId: '$$ref',
},
},
],
as: 'assets.fields',
},
},
])
and now I'm stuck with grouping the results to get the optimal object I described above.
Can you help, please?
UPDATE: here is Sample data
If I understand you correctly, you want something like this:
Get all the relevant assets from the policies and unwind them (I guess you only want it for few selected policies, otherwise, if you want to use all assets, you may as well start from their collection and in the end group them by policy)
Get all the wanted data from other collections. Create a fieldtypes array in each document
In order to match each item in fields with its fieldtype use $map with $mergeObjects (this is the more complicated part).
Group by policy
db.policies.aggregate([
{$lookup: {
from: "assets",
localField: "assets",
foreignField: "_id",
as: "assets"
}},
{$unwind: "$assets"},
{$lookup: {
from: "fields",
localField: "assets.fields",
foreignField: "_id",
as: "assets.fields"
}},
{$lookup: {
from: "assettypes",
localField: "assets.assetType",
foreignField: "_id",
as: "assets.assetType"
}},
{$lookup: {
from: "fieldtypes",
localField: "assets.fields.fieldType",
foreignField: "_id",
as: "assets.fieldtypes"
}},
{$set: {
"assets.assetType": {$first: "$assets.assetType"},
"assets.fields": {
$map: {
input: "$assets.fields",
in: {
$mergeObjects: [
"$$this",
{fieldType: {
$getField: {
input: {
$arrayElemAt: [
"$assets.fieldtypes",
{$indexOfArray: ["$assets.fieldtypes._id", "$$this.fieldType"]}
]
},
field: "key"
}
}
}
]
}
}
},
"assets.fieldtypes": "$$REMOVE"
}
},
{$group: {_id: "$_id", assets: {$push: "$assets"}}}
])
See how it works on the playground example

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

Mongo multiple $lookup in any collections

I'm trying to set up a query on the mongo that searches in 3 different collections.
Document of client:
{
"_id": ObjectId("1a")
"razaosocial:"32423424",
"prepository": [
{
"$ref": "prepository",
"$id": ObjectId("2a")
}
]
}
Document of prepository:
{
"_id": ObjectId("2a")
"name:"Jonh",
"prepository": {
"$ref": "representative",
"$id": ObjectId("3a")
}
}
Document of representative:
{
"_id": ObjectId("3a")
"name:"Josh"
}
I'm doing it this way, but it doesn't return anything:
db.clients.aggregate(
[
{
$lookup: {
from: 'prepository',
localField: 'prepository',
foreignField: 'id',
as: 'prepository'
}
},
{ $unwind: "$prepository" },
{ $lookup: {
from: 'representative',
localField: 'id',
foreignField: 'prepository._id',
as: 'prepository.repre'
}
},
{ $group: {
_id: "$_id",
client: { $first: "$razaosocial" },
users: {
$push: "$prepository"
}
} }
])
I'm trying to return
{
"_id": "1a"
"razaosocial:"32423424",
"prepository": [
{
"_id": "2a"
"name:"Jonh",
"representative": {
"_id": "3a"
"name:"Josh"
}
}
]
}
I am grateful for any help
You can use nested lookup,
$lookup with prepository collection
$match with prepository's id
$lookup with representative collection
$unwind deconstruct representative array
db.client.aggregate([
{
$lookup: {
from: "prepository",
as: "prepository",
let: { prepository: "$prepository" },
pipeline: [
{ $match: { $expr: { $in: ["$_id", "$$prepository"] } } },
{
$lookup: {
from: "representative",
localField: "representative",
foreignField: "_id",
as: "representative"
}
},
{ $unwind: "$representative" }
]
}
}
])
Playground

Mongdb look let uses the fields in the previous lookup result

i want use $c.cid
How does this js work?
this customer_url as c and lookup customer use customer_url.cid
{
$lookup: {
from: "customer_url",
localField: "url_id",
foreignField: "url_id",
as: "c"
}
},
{
$unwind: "$c"
},
{
$lookup: {
as: "customer",
from: "customer_dim",
let: {
cid: "$c.cid"
},
pipeline: [
{
$match: {
$expr: {
$eq: ["$_id", "$$cid"]
}
}
}
]
}
}

Remove field from embedded projection document after lookup

I have the following mongo aggregate query:
return db.collection('projects').aggregate([
{
$match: {
agents: ObjectId(agent)
}
},
{
$lookup: {
from: "agents",
localField: "agents",
foreignField: "_id",
as: "agents"
}
},
{
$lookup: {
from: "roles",
localField: "roles",
foreignField: "_id",
as: "roles"
}
},
{
$lookup: {
from: "agencies",
localField: "agency",
foreignField: "_id",
as: "agency"
}
},
{
$lookup: {
from: "resources",
localField: "roles.applicants",
foreignField: "_id",
as: "roles.applicants"
}
}
])
It works as it should, embedding the proper documents. However, the "password_hash" field is showing for each applicant. I want to remove that field. If I try to project and set roles.applicants.password_hash: 0 I actually end up getting the entire applicant without the password hash, but the rest of the roles fields are no longer there. So I get something that looks like:
roles: {
applicants: {
name: "Josh"
}
}
That should be
roles: {
title: "Super Hero",
applicants: {
name: "Josh"
}
}
I figured it out. Here is how I did it. First, the projection wasn't the issue with why the roles document was missing fields. It was the lookup.
What I did was changed the roles lookup to use the pipeline method on the nested documents and then did another pipeline on applicants, which first matched the applicants from the resource collection and then handled the projection. It looks like this.
{
$lookup: {
from: "roles",
let: { "roles": "$roles" },
pipeline: [
{ $match: { $expr: { $in: [ "$_id", "$$roles" ] } } },
{
$lookup: {
from: "resources",
let: { "applicants": "$applicants" },
pipeline: [
{ $match: { $expr: { $in: [ "$_id", "$$applicants" ] } } },
{
$project: {
first_name: 1
}
}
],
as: "applicants"
}
}
],
as: "roles"
}
}
The entire aggregate looks like this
return db.collection('projects').aggregate([
{
$match: {
agents: ObjectId(agent)
}
},
{
$lookup: {
from: "agents",
localField: "agents",
foreignField: "_id",
as: "agents"
}
},
{
$lookup: {
from: "agencies",
localField: "agency",
foreignField: "_id",
as: "agency"
}
},
{
$lookup: {
from: "roles",
let: { "roles": "$roles" },
pipeline: [
{ $match: { $expr: { $in: [ "$_id", "$$roles" ] } } },
{
$lookup: {
from: "resources",
let: { "applicants": "$applicants" },
pipeline: [
{ $match: { $expr: { $in: [ "$_id", "$$applicants" ] } } },
{
$project: {
first_name: 1
}
}
],
as: "applicants"
}
}
],
as: "roles"
}
}
])