Sorting nested Array in MongoDB using aggregate - arrays

I am trying to sort the nested array "subCategoryList on top of sorting by _id on the parent document. Newbie to mongo. Any help will be appreciated
Here is sample documents:
[
{
"_id": 1000,
"name": "Automobiles",
"parentId": "",
"helpText": "Year, Brand, Model, Color, Size"
},
{
"_id": 1004,
"name": "RV / Campers",
"parentId": 1000,
"helpText": ""
},
{
"_id": 1001,
"name": "Car / SUV / Truck",
"parentId": 1000,
"helpText": ""
}
]
Here is what I tried:
db.Category.aggregate([
{
"$match": {
"parentId": ""
}
},
{
"$lookup": {
"from": "Category", "localField": "_id", "foreignField": "parentId", "as": "subCategoryList"
}
},
{
$sort: {
_id: 1
}
}
]).pretty()
But I get:
{
"_id" : 17000,
"name" : "Music",
"parentId" : "",
"helpText" : "Help Text - Brand, Model, Title",
"subCategoryList" : [
{
"_id" : 17001,
"name" : "DVD / Blu-ray",
"parentId" : 17000,
"helpText" : ""
},
{
"_id" : 17002,
"name" : "Player",
"parentId" : 17000,
"helpText" : ""
}
]
}
{
"_id" : 20000,
"name" : "Sports Gear",
"parentId" : "",
"helpText" : "Help Text - Brand, Model, Gear Type, Size, Color,",
"subCategoryList" : [
{
"_id" : 20002,
"name" : "Football",
"parentId" : 20000,
"helpText" : ""
},
{
"_id" : 20007,
"name" : "Tennis",
"parentId" : 20000,
"helpText" : ""
},
{
"_id" : 20008,
"name" : "Cricket",
"parentId" : 20000,
"helpText" : ""
},
{
"_id" : 20004,
"name" : "Hockey",
"parentId" : 20000,
"helpText" : ""
},
{
"_id" : 20003,
"name" : "Golf",
"parentId" : 20000,
"helpText" : ""
},
{
"_id" : 20006,
"name" : "Basketball",
"parentId" : 20000,
"helpText" : ""
},
{
"_id" : 20005,
"name" : "Soccer",
"parentId" : 20000,
"helpText" : ""
},
{
"_id" : 20010,
"name" : "Camping / Hiking",
"parentId" : 20000,
"helpText" : ""
},
{
"_id" : 20009,
"name" : "Cycling",
"parentId" : 20000,
"helpText" : ""
},
{
"_id" : 20001,
"name" : "Baseball",
"parentId" : 20000,
"helpText" : ""
},
{
"_id" : 20012,
"name" : "Skiing",
"parentId" : 20000,
"helpText" : ""
},
{
"_id" : 20011,
"name" : "Swimming",
"parentId" : 20000,
"helpText" : ""
},
{
"_id" : 20099,
"name" : "Other",
"parentId" : 20000,
"helpText" : ""
}
]
}

db.hardwares.aggregate([
{$unwind: "$subCategoryList"},
{$sort: {"subCategoryList._id": 1}},
{$group: {_id:"$_id", subCategoryList: {$push:"$subCategoryList"}}}
]);
It could be helps you, try like this...
May be it didn't work but it gives an idea

Mongo doesn't have a built in function to sort an inner array, However Mongo v4.4 introduces the $function aggregation operator. this allows us to use custom javascript functions within a pipeline.
You could use it like so:
db.Category.aggregate([
{
$addFields: {
"subCategoryList":
{
$function: {
body: function (categories) {
return categories.sort((a, b) => a._id - b._id);
},
args: ["$subCategoryList"],
lang: "js"
}
}
}
}
])
For lesser Mongo versions you will have to first $unwind the array. then $sort it and finally constructing the original structure or in your case because you are $lookuping the inner array you could use the other $lookup syntax introduced at v3.6 to $sort within the $lookup:
db.Category.aggregate([
{
"$match": {
"parentId": ""
}
},
{
"$lookup": {
"from": "Category",
let: {id: "$_id"},
pipeline: [
{
$match: {
$expr: {
$eq: ["$$id", "$parentId"]
}
}
},
{
$sort: {
_id: 1
}
}
],
"as": "subCategoryList"
}
},
{
$sort: {
_id: 1
}
}
]);

Related

How to query Array in MongoDB?

i am new to MongoDB and I have documents as below
{ "_id" : ObjectId("604b7d62b19a72a2b89028e6"), "name" : "ram", "tags" : [ "mobile", "iphone", "india" ] }
{"_id" : ObjectId("604b7d83b19a72a2b89028e7"), "name" : "shyam", "tags" : [ "mobile", "iphone", "india" ] }
{ "_id" : ObjectId("604b7d9bb19a72a2b89028e8"), "name" : "ravi", "tags" : [ "mobile", "android", "india" ] }
{ "_id" : ObjectId("604b7db5b19a72a2b89028e9"), "name" : "aman", "tags" : [ "mobile", "android", "india" ] }
{ "_id" : ObjectId("604b7db5b19a72a2b89028e9"), "name" : "aman", "tags" : [ "windows", "usa" ] }
{ "_id" : ObjectId("604b7db5b19a72a2b89028e9"), "name" : "aman", "tags" : [ "tech", "apple", "microsoft" ] }
How to write query so that if i query for following tags ["mobile", "android", "12", "pro"] i would get following result
{ "_id" : ObjectId("604b7d9bb19a72a2b89028e8"), "name" : "ravi", "tags" : [ "mobile", "android", "india" ] }
{ "_id" : ObjectId("604b7db5b19a72a2b89028e9"), "name" : "aman", "tags" : [ "mobile", "android", "india" ] }
{ "_id" : ObjectId("604b7d62b19a72a2b89028e6"), "name" : "ram", "tags" : [ "mobile", "iphone", "india" ] }
{ "_id" : ObjectId("604b7d83b19a72a2b89028e7"), "name" : "shyam", "tags" : [ "mobile", "iphone", "india" ] }
Demo - https://mongoplayground.net/p/EMW8xgV1yrU
Use $in
The $in operator selects the documents where the value of a field equals any value in the specified array. To specify an $in expression, use the following prototype:
db.collection.find({
tags: { $in: [ "mobile", "android", "12", "pro" ] }
})

MongoDB aggregation match criteria to find if a field exists with value in array

{
"attributes" : [
{
"dept" : "accounts",
"location" : "onshore"
},
{
"dept" : "HR",
"location" : "offshore"
},
{
"dept" : "technology"
"location": "NL"
}
]
},
{
"attributes" : [
{
"dept" : "accounts",
"location" : "onshore"
},
{
"dept" : "technology"
"location": "London"
}
]
},
{
"attributes" : [
{
"dept" : "accounts",
"location" : "onshore"
},
{
"dept" : "HR"
"location": "London"
}
]
}
I want to get those documents where attributes array has dept :technology with location NOTequal to london or the attributes array does not have field dept :technology. So the final output will be like below:
{
"attributes" : [
{
"dept" : "accounts",
"location" : "onshore"
},
{
"dept" : "HR",
"location" : "offshore"
},
{
"dept" : "technology"
"location": "NL"
}
]
},
{
"attributes" : [
{
"dept" : "accounts",
"location" : "onshore"
},
{
"dept" : "HR"
"location": "London"
}
]
}
I have tried this solution but it gives me all the documents:
{
"attributes": {
"$elemMatch": {
"$or": [
{
"dept": {
"$nin": ["technology"]
}
},
{
"$and": [
{
"dept": "technology"
},
{
"location": {"$ne" : "London"}
}
]
}
]
}
}
}
Since $elemMatch evaluates each item of the array, we cannot use $nin or $ne operators if the condition is only to negate the boolean expression.
Instead, we should use the $not to performs a logical NOT operation on the specified < operator-expression >.
Try this one:
db.collection.find({
"$or": [
{
"attributes": {
$not: {
"$elemMatch": {
dept: "technology"
}
}
}
},
{
"attributes": {
"$elemMatch": {
dept: "technology",
location: {
$ne: "London"
}
}
}
}
]
})
MongoPlayground

Working with Mongodb References inside array next to other content

I want to use the mongodb aggregation framework on 3 Collections that have to "come together" to one Query. The problem is, when I'm referencing to another collection with $lookup it deletes the other content of the array the reference is in.
Heres the collection my aggregation starts in (users):
{
"_id" : ObjectId("5c9bea89f4fe8c37175ade58"),
"kundennummer" : "000001",
"passwort" : "xxx",
"status" : "1",
"onlinestatus" : true,
"kontakt" : {
"email" : "test#test.net"
},
"thing" : [
{
"thing_id" : 2,
"onlinestatus" : false,
"status" : true,
"site" : [
{
"site_id" : 3,
"status": true
},
{
"site_id" : 4,
"status": true
}
],
"refs" : [
{
"thing_id" : 11,
"status" : true
},
{
"thing_id" : 22,
"status" : true
}
]
}
]
}
when I know want site to be extended by the content given in site collection like this:
{
"_id": 11,
"name": "test"
},
{
"_id": 22,
"name": "test2"
}
I tried to use $lookup and the status: true disappeared.
db.users.aggregate([{
$lookup:
{
from: "sites",
localField: "things.site.site_id",
foreignField: "_id",
as: "things.site"
}
}])
edit:
What I want to achieve is the following:
{
"_id" : ObjectId("5c9bea89f4fe8c37175ade58"),
"kundennummer" : "000001",
"passwort" : "xxx",
"status" : "1",
"onlinestatus" : true,
"kontakt" : {
"email" : "test#test.net"
},
"thing" : [
{
"thing_id" : 2,
"onlinestatus" : false,
"status" : true,
"site" : [
{
"site_id" : 11,
"status": true,
"name": "test"
},
{
"site_id" : 12,
"status": true,
"name": "test2"
}
],
"refs" : [
{
"thing_id" : 11,
"status" : true,
"name": "test"
},
{
"thing_id" : 12,
"status" : true,
"name": "test2"
}
]
}
]
}
To use $lookup on the embedded document in MongoDB aggregation you need to use $unwind. It will output a document for each element and then you can use $lookup. You will get results from sites as well with the help of the below query:
db.user.aggregate([
{
$unwind: "$thing"
},
{
$unwind: "$thing.site"
},
{
$unwind: "$thing.refs"
},
{
$lookup: {
from: 'sites',
let: { 'siteId': '$thing.site.site_id' },
pipeline: [
{
$match: {
$expr: {
$eq: ['$_id', '$$siteId']
}
}
},
],
as: 'siteObject'
}
},
{
$lookup: {
from: 'things',
let: { 'thingId': '$thing.refs.thing_id' },
pipeline: [
{
$match: {
$expr: {
$eq: ['$_id', '$$thingId']
}
}
},
],
as: 'thingObject'
}
},
{
$unwind: "$siteObject"
},
{
$unwind: "$thingObject"
},
{
$project: {
"_id" : 1,
"kundennummer" : 1,
"passwort" : 1,
"status" : 1,
"onlinestatus" : 1,
"kontakt" : 1,
"thing": [{
"thing_id" : "$thing.thing_id",
"onlinestatus" : "$thing.onlinestatus",
"status" : "$thing.status",
"site" : {
"site_id":"$thing.site.site_id",
"status":"$thing.site.status",
"siteName":"$siteObject.siteName",
},
"refs" : {
"thing_id":"$thing.refs.thing_id",
"status":"$thing.refs.status",
"siteName":"$thingObject.thingName",
},
}]
}
},
{
$unwind: "$thing"
},
{
$group: {
_id: {
"_id": "$_id",
"kundennummer": "$kundennummer",
"passwort": "$passwort",
"status":"$status",
"onlinestatus":"$onlinestatus",
"kontakt":"$kontakt"
},
thing: { $push: { thing_id: "$thing.thing_id", onlinestatus: "$thing.onlinestatus", status: "$thing.status",site:"$thing.site",refs: "$thing.refs" } }
}
}
])
This will result as below:
{
"_id" : {
"_id" : ObjectId("5c9bea89f4fe8c37175ade58"),
"kundennummer" : "000001",
"passwort" : "xxx",
"status" : "1",
"onlinestatus" : true,
"kontakt" : {
"email" : "test#test.net"
}
},
"thing" : [
{
"thing_id" : 2,
"onlinestatus" : false,
"status" : true,
"site" : {
"site_id" : ObjectId("5caf395e512ab5123bb4b0af"),
"status" : true,
"siteName" : "Moto"
},
"refs" : {
"thing_id" : ObjectId("5cb042ea512ab5123bb4b0b0"),
"status" : true,
"siteName" : "hi"
}
},
{
"thing_id" : 2,
"onlinestatus" : false,
"status" : true,
"site" : {
"site_id" : ObjectId("5caf395e512ab5123bb4b0af"),
"status" : true,
"siteName" : "Moto"
},
"refs" : {
"thing_id" : ObjectId("5cb042ea512ab5123bb4b0b1"),
"status" : true,
"siteName" : "world"
}
},
{
"thing_id" : 2,
"onlinestatus" : false,
"status" : true,
"site" : {
"site_id" : ObjectId("5caf395e512ab5123bb4b0ae"),
"status" : true,
"siteName" : "hello"
},
"refs" : {
"thing_id" : ObjectId("5cb042ea512ab5123bb4b0b0"),
"status" : true,
"siteName" : "hi"
}
},
{
"thing_id" : 2,
"onlinestatus" : false,
"status" : true,
"site" : {
"site_id" : ObjectId("5caf395e512ab5123bb4b0ae"),
"status" : true,
"siteName" : "hello"
},
"refs" : {
"thing_id" : ObjectId("5cb042ea512ab5123bb4b0b1"),
"status" : true,
"siteName" : "world"
}
}
]
}
Result is near by your desired result and i think you should not follow such schema it will make your every query more complex.

update deeply nested array mongodb

I want to push one value in attachments array using mongodb. I want to update query using following criteria.
_id:ObjectId("5b56bd2f3e18580edc85af73") "cardID": ObjectId("5b56c895d0a04836f71aa776") "commentId":"2"
I want to push value in attachments, any help would be appreciated
This is a collection object:
{
"_id" : ObjectId("5b56bd2f3e18580edc85af73"),
"orgId" : "90",
"createdBy" : "test",
"name" : "testname",
"Cards" : [
{
"cardID" : ObjectId("5b56c895d0a04836f71aa776"),
"cardName" : "test Name",
"cardCreated" : "",
"reviewer" : "",
"priority" : "",
"cardPosition" : "",
"membersAssigned" : [
"ggg",
"fff"
],
"labels" : [
"l1",
"l2"
],
"description" : "",
"attachements" : [],
"comments" : [
{
"commentId" : "2",
"commentedBy" : "test",
"date" : "",
"comment" : "Hello world",
"attachements" : [
"1",
"data"
],
"emojis" : [
":smile:",
":thumbsup:"
],
"updatedBy" : "arkadata",
"updatedOn" : "",
"subComments" : {
"commentedBy" : "jaril",
"date" : "",
"comment" : "Hello world inside dark"
}
},
{
"commentId" : "3",
"commentedBy" : "test",
"date" : "",
"comment" : "Hello world",
"attachements" : [
"1",
"raj"
],
"emojis" : [
":smile:",
":thumbsup:"
],
"updatedBy" : "arkadata",
"updatedOn" : "",
"subComments" : {
"commentedBy" : "jaril",
"date" : "",
"comment" : "Hello world inside dark"
}
},
{
"commentId" : 6.0
}
],
"dueDate" : "",
"createdDate" : "",
"lastUpdated" : "",
"checkList" : [],
"position" : "5",
"status" : "active"
},
"timestamp" : ISODate("2018-07-24T05:46:23.890Z")
}
You can try with mongodb 3.6 arrayFilters
db.collection.update(
{ "_id": ObjectId(5b56bd2f3e18580edc85af73) },
{ "$push": { "Cards.$[card].comments.$[comment].attachments": "2" } },
{ "arrayFilters": { "card.cardID": ObjectId("5b56c895d0a04836f71aa776"), "comment.commentId": 2 } }
)
Make sure you cast your ids to ObjectId
Edit:
db.collection.update(
{ "_id": ObjectId(5b56bd2f3e18580edc85af73) },
{ "$push": { "Cards.$[card].comments.$[comment].attachments": "2" } },
{ "arrayFilters": [
{ "card.cardID": ObjectId("5b56c895d0a04836f71aa776")},
{"comment.commentId": 2 }
]
}
)

How to flatten an array after db call in express?

I am using express to query my mongodb database using the native driver (not Mongoose.)
I am trying to do some data clean up after a MongoDB response. I don't mind if this could be achieved with MongoDB's aggregation query or some variation of it, for completeness the current query I'm using is: collection.find({'make.name': req.params.make}, {'model.name': 1, 'submodel.body': 1, '_id': 0}).toArray();
I have a response from mongodb:
[
{
"model" : {
"name" : "3 Series"
},
"submodel" : {
"body" : "Convertible"
}
},
{
"model" : {
"name" : "3 Series"
},
"submodel" : {
"body" : "Coupe"
}
},
{
"model" : {
"name" : "2 Series"
},
"submodel" : {
"body" : "Coupe"
}
},
{
"model" : {
"name" : "2 Series"
},
"submodel" : {
"body" : "Coupe"
}
},
{
"model" : {
"name" : "3 Series"
},
"submodel" : {
"body" : "Coupe"
}
},
{
"model" : {
"name" : "2 Series"
},
"submodel" : {
"body" : "Convertible"
}
},
{
"model" : {
"name" : "2 Series"
},
"submodel" : {
"body" : "Convertible"
}
},
{
"model" : {
"name" : "2 Series"
},
"submodel" : {
"body" : "Convertible"
}
},
{
"model" : {
"name" : "2 Series"
},
"submodel" : {
"body" : "Coupe"
}
},
{
"model" : {
"name" : "2 Series"
},
"submodel" : {
"body" : "Coupe"
}
},
{
"model" : {
"name" : "2 Series"
},
"submodel" : {
"body" : "Coupe"
}
},
{
"model" : {
"name" : "2 Series"
},
"submodel" : {
"body" : "Convertible"
}
},
{
"model" : {
"name" : "2 Series"
},
"submodel" : {
"body" : "Convertible"
}
},
{
"model" : {
"name" : "2 Series"
},
"submodel" : {
"body" : "Convertible"
}
},
{
"model" : {
"name" : "2 Series"
},
"submodel" : {
"body" : "Coupe"
}
},
{
"model" : {
"name" : "2 Series"
},
"submodel" : {
"body" : "Convertible"
}
},
{
"model" : {
"name" : "2 Series"
},
"submodel" : {
"body" : "Coupe"
}
}
]
which I would like to simplify to this:
[
"model" : {
"name": "2 Series"
}
"submodel" : {
"body": ["Convertible", "Coupe"]
}
"model" : {
"name": "3 Series"
}
"submodel" : {
"body": ["Convertible", "Coupe"]
}
]
New output:
{ "_id" : "M3", "submodel" : [ "Sedan" ] }
{ "_id" : "X5 eDrive", "submodel" : [ "SUV" ] }
{ "_id" : "X5 M", "submodel" : [ "SUV" ] }
{ "_id" : "M4 GTS", "submodel" : [ "Coupe" ] }
{ "_id" : "ActiveHybrid 5", "submodel" : [ "Sedan" ] }
{ "_id" : "X5", "submodel" : [ "SUV" ] }
{ "_id" : "X6 M", "submodel" : [ "SUV" ] }
{ "_id" : "i8", "submodel" : [ "Coupe" ] }
{ "_id" : "X4", "submodel" : [ "SUV" ] }
{ "_id" : "5 Series Gran Turismo", "submodel" : [ "Hatchback" ] }
{ "_id" : "M4", "submodel" : [ "Coupe", "Convertible" ] }
{ "_id" : "5 Series", "submodel" : [ "Sedan" ] }
{ "_id" : "M6 Gran Coupe", "submodel" : [ "Sedan" ] }
{ "_id" : "X1", "submodel" : [ "SUV" ] }
{ "_id" : "3 Series eDrive", "submodel" : [ "Sedan" ] }
{ "_id" : "2 Series", "submodel" : [ "Coupe", "Convertible" ] }
{ "_id" : "6 Series Gran Coupe", "submodel" : [ "Sedan" ] }
{ "_id" : "3 Series", "submodel" : [ "Wagon", "Sedan" ] }
{ "_id" : "Z4", "submodel" : [ "Convertible" ] }
{ "_id" : "6 Series", "submodel" : [ "Convertible", "Coupe" ] }
new output:
{
"_id": "2 Series",
"submodel": [
"Coupe",
"Convertible"
]
},
{
"_id": "3 Series",
"submodel": [
"Wagon",
"Sedan"
]
},
{
"_id": "3 Series Gran Turismo",
"submodel": [
"Hatchback"
]
},
{
"_id": "3 Series eDrive",
"submodel": [
"Sedan"
]
},
{
"_id": "4 Series",
"submodel": [
"Coupe",
"Convertible"
]
},
As you can see, model.name is a unique property, and "submodel.body" is now an array of unique body types.
How can I convert my array of non-unique models, to an array of unique models?
Current Query:
router.get('/test/:make', (req, res) => {
var collection = db.get().collection('styles');
collection.aggregate({
$match: {
$or: [{
"make.niceName": req.params.make
},
{
"make.name": req.params.make
}
]
}
}, {
$group: {
_id: "$model.name",
"submodels": {
$addToSet: "$submodel.body"
}
}
}, {
$sort: {
_id: 1
}
}, {
$project: {
models: "$_id",
submodel: 1,
_id: 0
}
}).toArray((err, docs) => {
res.send(docs)
})
You can try below aggregation pipeline. $addToSet to get distinct submodel in each model group.
collection.aggregate({
$match: {
"make.name": req.params.make
}
}, {
$group: {
_id: "$model.name",
submodel: {
$addToSet: "$submodel.body"
}
}
}, {
$sort: {
_id: 1
}
}, {
$project: {
models: "$_id",
submodel: 1,
_id: 0
}
})

Resources