How to flatten an array after db call in express? - arrays

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

Related

Mongodb get all documents in object array with only same values that is passed

I have the below document in my collection.
{ "_id" : ObjectId("ObjectId"), "MobNo" : "23232", "data" : [ { "status" : "PASS" }, { "status" : "PASS" } ] }
{ "_id" : ObjectId("ObjectId"), "MobNo" : "232323", "data" : [ { "status" : "PASS" }, { "status" : "FAIL" } ] }
{ "_id" : ObjectId("ObjectId"), "MobNo" : "32322", "data" : [ { "status" : "PASS" }, { "status" : "PASS" } ] }
I want a query to return all objects in data array not to have PASS status(All the objects in the array should NOT have the status as "PASS").
My Expected output here is
{ "_id" : ObjectId("ObjectId"), "MobNo" : "232323", "data" : [ { "status" : "PASS" }, { "status" : "FAIL" } ] }
My query for the above result :
db.Collection.aggregate({
$match: {
"data": {
$exists: true, $not: { $size: 0 }, $all: [
{ "$elemMatch": { "status": { $nin: ["PASS"] } } },
]
},
}
})
Now i want to my collection to return documents with data array having all object status as passed .
My expected output :
{ "_id" : ObjectId("ObjectId"), "MobNo" : "23232", "data" : [ { "status" : "PASS" }, { "status" : "PASS" } ] }
{ "_id" : ObjectId("ObjectId"), "MobNo" : "32322", "data" : [ { "status" : "PASS" }, { "status" : "PASS" }
How can i get the above output, my below query doesnt work and it return even the failure data array.
db.Collection.aggregate({
$match: {
"data": {
$exists: true, $not: { $size: 0 }, $all: [
{ "$elemMatch": { "status": { $in: ["PASS"] } } },
]
},
}
})
Output of the above query :
{ "MobNo" : "23232", "data" : [ { "status" : "PASS" }, { "status" : "PASS" } ] },
{ "MobNo" : "232323", "data" : [ { "status" : "PASS" }, { "status" : "FAIL" } ] },
{ "MobNo" : "32322", "data" : [ { "status" : "PASS" }, { "status" : "PASS" } ] }
I do not wanted the below record in my above query output:
{ "MobNo" : "232323", "data" : [ { "status" : "PASS" }, { "status" : "FAIL" } ] },
Any help ?
You can do this in several different ways, here is the most straight forward in my opinion using $size and $filter to see if any "bad" documents exist in the array.
db.collection.aggregate([
{
$match: {
$expr: {
$eq: [
{
$size: {
$filter: {
input: "$data",
cond: {
$ne: [
"$$this.status",
"PASS"
]
}
}
}
},
0
]
},
"data.0": {
$exists: true
}
}
}
])
Mongo Playground
Cause your status values are in the array field (data), so you must to $unwind it, then query in it, see this:
db.Collection.aggregate({
$unwind:{ path: "$data"},
$match: {
"data": {
$exists: true, $not: { $size: 0 }, $all: [
{ "$elemMatch": { "status": { $in: ["PASS"] } } },
]
},
}
})

Remove object in nested array mongodb

I have somwthing like the next json in mongo.
My objective is to delete all nested objects with "id_s": "1"
{
"_id": "5150a1199fac0e6910000002",
"name": "some name",
"p_a": [
{
"sub_name": "subname"
},
{
"sub_name": "subname2",
"p_p": [
{
"last_level": "toDelete",
"id_s": "1"
},
{
"last_level": "toKeep",
"id_s": "2"
}
]
},
{
"sub_name": "subname3",
"p_p": [
{
"last_level": "toDelete",
"id_s": "1"
},
{
"last_level": "toKeep",
"id_s": "2"
}
]
}
]
}
Expected JSON:
{
"_id": "5150a1199fac0e6910000002",
"name": "some name",
"p_a": [
{
"sub_name": "subname"
},
{
"sub_name": "subname2",
"p_p": [
{
"last_level": "toKeep",
"id_s": "2"
}
]
},
{
"sub_name": "subname3",
"p_p": [
{
"last_level": "toKeep",
"id_s": "2"
}
]
}
]
}
This will give you documents where id_s: 2 and ignore rest of them.
db.getCollection("test").aggregate(
[
{
"$match" : {
"_id" : ObjectId("5150a1199fac0e6910000002")
}
},
{
"$unwind" : {
"path" : "$p_a"
}
},
{
"$unwind" : {
"path" : "$p_a.p_p"
}
},
{
"$match" : {
"p_a.p_p.id_s" : "2"
}
}
],
{
"allowDiskUse" : false
}
);
Output:
/* 1 */
{
"_id" : ObjectId("606d83fe44c9fc09f60d0756"),
"name" : "some name",
"p_a" : {
"sub_name" : "subname2",
"p_p" : {
"last_level" : "toKeep",
"id_s" : "2"
}
}
}
/* 2 */
{
"_id" : ObjectId("606d83fe44c9fc09f60d0756"),
"name" : "some name",
"p_a" : {
"sub_name" : "subname3",
"p_p" : {
"last_level" : "toKeep",
"id_s" : "2"
}
}
}

Sorting nested Array in MongoDB using aggregate

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

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

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

Resources