grouping result from multiple queries and aggregating result mongo - mongodb

I'm new to mongo and I have a document that has an array with the ids of all it's related documents. I need to fetch the document with all it's relateds in a single query. For the moment I fetch the document and I query separatly each of it's related document with there ids.
all my documents are on the same collection documents_nodes and look like so:
{
"id": "document_1",
"name": "flask",
"relateds": [
"document_2",
"document_3",
"document_4"
],
"parents": [
"document1_1"
]
}
The first query is to get the initial document
db.documents_nodes.find({id: document_1})
And then I query it's relateds with a second query
db.documents_nodes.aggregate([{
$match: {
$and: [{
id: {
$in: ["document_2", "document_3", "document_2"]
}
}]
}
}])
is there a way to combine the two queries, I tried this but it doesn't work
db.documents_nodes.aggregate([
{
$match: {
uri: "https://opus.adeo.com/LMFR_prod/3206"
}
},
{
$addFields: {
newRelateds:
{
$match: {
id: {
$in: [ "$relateds" ]
}
}
}
}
}
])
"errmsg" : "Unrecognized expression '$match'",
"code" : 168,
"codeName" : "InvalidPipelineOperator"

I have found a way to do it, in case someone has the same need.
I used the $unwind to flatten the array of documents and then used the $lookup to fetch the documents by their ids and finally I group the result on a new key.
[{
$match: {
id: "document_1"
}
}, {
$unwind: {
path: '$relateds',
}
}, {
$lookup: {
from: 'documents_nodes',
localField: 'relateds',
foreignField: 'id',
as: 'newRelateds'
}
}, {
$group: {
_id: '$id',
relateds: {
'$push': '$newRelateds'
}
}
}]

Related

MongoDB - combining query across mulitple collections

I'm trying to figure out how to essentially do a join in MongoDB. I've read about doing aggregates: https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/, but that doesn't seem to be what I'm looking for.
I'm also very new to NoSQL, so I'm not exactly sure what I should be using here.
I have two collections in MongoDB, structured as follows:
db collection - employees:
{
_id: 1,
name: 'John Doe',
filesAccess: {
web: true
},
fileIds: [
'fileId1',
'fileId2'
]
},
{
_id: 2,
name: 'Bob Jones',
filesAccess: {
web: false
},
fileIds: [
'fileId3',
'fileId4'
]
}
db collection - files:
{
_id: fileId1,
fileMetaData: {
location: 'NE'
}
},
{
_id: fileId2,
fileMetaData: {
location: 'NE'
}
},
{
_id: fileId3,
fileMetaData: {
location: 'SW'
}
},
{
_id: fileId4,
fileMetaData: {
location: 'SW'
}
}
I want to be able to query for all employees who have fileAccess.web = true and get their employee ID, name, and fileMetaData.location. The location for all of an employee's files will be the same. So the query only needs to use the first fileId from an employee to get the location from the files collection
So I'd like my result should look like:
{
_id: 1,
name: 'John Doe',
location: 'NE'
}
How would you structure a query to accomplish this in MongoDB? I'm using Studio3T as my interface to the db. Any help is greatly appreciated
You can use this aggregation query:
First $match to get only documents where filesAccess.web is true.
The join based on values on fileIds and _id. This give an array calling result.
Then get the first position
And $project to output the fields you want.
db.employess.aggregate([
{
"$match": {
"filesAccess.web": true
}
},
{
"$lookup": {
"from": "files",
"localField": "fileIds",
"foreignField": "_id",
"as": "result"
}
},
{
"$set": {
"result": {
"$arrayElemAt": [
"$result",
0
]
}
}
},
{
"$project": {
"_id": 1,
"name": 1,
"location": "$result.fileMetaData.location"
}
}
])
Example here

MongoDB Exclude docs from aggregate if field/reference exists in other collection

const sellerSchema = Schema(
{
name: String,
url:String
}
const productSchema = Schema(
{
title: String,
sellerUrl:String
}
Below query will return unique sellerUrl from all products:
context.Product.aggregate([
{
$group: {
_id: "$sellerUrl",
}
}
]);
But I also want to exclude from aggregation, sellers that I already saved. So if url == sellerUrl aggregation must exclude that seller.
Please help me
You can try below query :
db.product.aggregate([
{
$group: {
_id: "", /** group on no condition & push all unique `sellerUrl` to sellerUrls array */
sellerUrls: { $addToSet: "$sellerUrl" }
}
},
{
$lookup: {
from: "seller",
let: { sellerUrls: "$sellerUrls" }, // creating local variable
pipeline: [
{ $group: { _id: "", urls: { $addToSet: "$url" } } }, /** group on no condition & push all unique `url` to urls array */
{ $project: { _id: 0, uniqueAndNotInSellerColl: { $setDifference: [ "$$sellerUrls", "$urls" ] } } } // get difference between two arrays
],
as: "data" // As we're grouping will always be one doc/element in an array
}
},
/** Create a new root doc from getting first element(though it will have only one) from `data` array */
{
$replaceRoot: { newRoot: { $arrayElemAt: [ "$data", 0 ] } }
}
])
Test : mongoplayground
Update :
As you need few other fields from product collection but not just the sellerUrl field then try below query :
db.product.aggregate([
{
$group: {
_id: "$sellerUrl",
docs: { $push: { title: "$title" } } // We're only retrieving `title` field from `product` docs, if every field is needed use `$$ROOT`
}
},
/** We've used basic `lookup` stage, use this if you've only few matching docs from `seller` collection
* If you've a lot of matching docs for each `_id` (sellerUrl),
* then instead of getting entire `seller` doc (which is not needed) use `lookup` with aggregation pipeline &
* just get `_id`'s of seller docs for better performace refer previous query
*/
{
$lookup: {
from: "seller",
localField: "_id",
foreignField: "url",
as: "sellerDocs"
}
},
/** match will retain only docs which doesn't have a matching doc in seller collection */
{
$match: { sellerDocs: [] }
},
{
$project: { sellerDocs: 0 }
}
])
Test : mongoplayground

MongoDB aggregate query return document based on match query and priority values

In a mongodb collection , i have following documents :
{"id":"1234","name":"John","stateCode":"CA"}
{"id":"1234","name":"Smith","stateCode":"CA"}
{"id":"1234","name":"Tony","stateCode":"GA"}
{"id":"3323", "name":"Neo","stateCode":"OH"}
{"id":"3323", "name":"Sam","stateCode":"US"}
{"id":"4343","name":"Bruce","stateCode":"NV"}
I am trying to write a mongo aggregate query which do following things:
match based on id field
Give more priority to document having values other than "NV" or "GA" in stateCode field.
If all the document have values either "NV" or "GA" then ignore the priority.
If any of the document have stateCode other than "NV" or "GA" , then return those document.
Example 1:
id = "1234"
then return
{"id":"1234","name":"John","stateCode":"CA"}
{"id":"1234","name":"Smith","stateCode":"CA"}
Example 2:
id = "4343"
then return
{"id":"4343","name":"Bruce","stateCode":"NV"}
Could you please help with a query to achieve this.
I tried with a query , but i am stuck with error:
Failed to execute script.
Error: command failed: {
"ok" : 0,
"errmsg" : "input to $filter must be an array not string",
"code" : 28651,
"codeName" : "Location28651"
} : aggregate failed
Query :
db.getCollection('emp').aggregate([{$match:{
'id': "1234"
}
},
{
$project: {
"data": {
$filter: {
input: "$stateCode",
as: "data",
cond: { $ne: [ "$data", "GA" ],$ne: [ "$data", "NV" ] }
}
}
}
}
])
I actually recommend you split this into 2 queries, first try to find documents with a different status code and if that fails then retrieve the rest.
With that said here is a working pipeline that does it in one go, Due to the fact we cant know in advance whether the condition is true or not we need to iterate all the documents who match the id, this fact makes it VERY inefficient in the case the id is shared by many documents, if this is not possible then using this pipeline is fine.
db.getCollection('emp').aggregate([
{
$match: {
'id': "1234"
}
},
{ //we have to group so we can check
$group: {
_id: null,
docs: {$push: "$$ROOT"}
}
},
{
$addFields: {
highPriorityDocs: {
$filter: {
input: "$docs",
as: "doc",
cond: {$and: [{$ne: ["$$doc.stateCode", "NV"]}, {$ne: ["$$doc.stateCode", "GA"]}]}
}
}
}
},
{
$project: {
finalDocs: {
$cond: [ // if size of high priority docs gt 0 return them.
{$gt: [{$ize: "$highPriorityDocs"}, 0]},
"$highPriorityDocs",
"$docs"
]
}
}
},
{
$unwind: "$finalDocs"
},
{
$replaceRoot: {newRoot: "$finalDocs"}
}
])
The last two stages are just to restore the original structure, you can drop them if you don't care about it.

MongoDB aggregate $lookup to return unmatched items from the main collection

I would like unmatched data from the USERS collection. Here I have two collections 1) USERS, 2) COMPANY.I am able to get the matched data from both USERS using aggregate function. but in this case I want data from USERS table which are not assigned to a company.
USERS table
{
_id: "AAA",
fullName:"John Papa"
},
{
_id: "BBB",
fullName:"Robin Son"
}
COMPANY table
{
_id: "1sd1s",
Name:"Lumbar Company"
User:"AAA"
},
{
_id: "23s1dfs3",
Name:"Patricia"
User:"AAA"
}
$lookup works like LEFT OUTER JOIN so it will remove empty array when there's no match. Then you can use $size to get only empty arrays:
db.users.aggregate([
{
$lookup: {
from: "company",
localField: "_id",
foreignField: "User",
as: "companies"
}
},
{
$match: {
$expr: {
$eq: [ { "$size": "$companies" }, 0 ]
}
}
}
])
Mongo Playground

Mongodb lookup and get output flat tree structure?

I have two collections points collection and users collection here i want to do aggregation based on userid.
here i am trying aggregation and everything is working fine but i need to modify result
db.points.aggregate([
$match: {
store: "001",
date: {$lte: ISODate("2017-11-10T08:15:39.736Z"), $gte: ISODate("2017-11-10T08:15:39.736Z")}
},
{
$lookup: {
from: "users",
localField: "userid",
foreignField: "_id",
as: "user"
}
},
{
$project:
{
_id:0,
"userdata.purchasedetails.purchaseid" : 1,
"userdata.purchasedetails.data" : 1,
usermobinumber: { "$arrayElemAt": [ "$user.mobinumber", 0 ] }
}}
Data stored like this
{
"userpoint": "2",
"date":ISODate("2017-11-10T08:15:39.736Z"),
"store":"001",
"userid":[
objectID("5a7565ug8945rt67"),
objectID("8a35553d3446rt78")
],
"userdata":{
"profile":[],
"purchasedetails":
[{
"purchaseid":"dj04944",
"data":"awe"
}]
}
}
getting result like this :
{
"usermobinumber":"9611",
"userdata":{
"purchasedetails":
[{
"purchaseid":"dj04944",
"data":"awe"
}]
}
my expecting result:
I dont want tree structure output i need like this
{
"usermobinumber":"9611",
"purchaseid":"dj04944",
"data":"awe"
}
how can i do this help me out
You can do something like this here,
db.points.aggregate([
//Whatever you are doing here now (lookup and match)
{
$project:{
"usermobinumber":"$usermobinumber",
"purchaseid":"$userdata.purchasedetails.purchaseid"
}
}
])
This will give you:
{
"usermobinumber" : "9611",
"purchaseid" : [
"dj04944"
]
}
EDIT:
db.points.aggregate([
//Whatever you are doing here now (lookup and match)
{
{
$project:{
"usermobinumber":"$usermobinumber",
"purchaseid":"$userdata.purchasedetails.purchaseid",
"data":"$userdata.purchasedetails.data"
}
}
}
])
This will give you
{
"usermobinumber":"9611",
"purchaseid":["dj04944"],
"data":["awe"]
}
purchaseid and data are array because purchasedetails is an array.
You can add an $unwind stage to flatten the result....