Remove object in nested array mongodb - arrays

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

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

How I can replace the object key with root, in MongoDB

"data": {
"abc": {
"Id": "100",
"print": "number",
"uploadAt" : "2021-22-01",
"servicesAt" : "2020-01-12"
},
"xyz": {
"Id": "123",
"print": "number",
"uploadAt" : "2021-22-01",
"servicesAt" : "2020-01-12"
}
}
Explanation
I want to remove the root data, rest of the in side the data objects abc, xyz will remain as it is, in the 2nd step I want to replace the object value number with id key , I want to to do this dynamically,
Expected Output
{
"abc": {
"number": "100",
"print": "number",
"uploadAt" : "2021-22-01",
"servicesAt" : "2020-01-12"
},
"xyz": {
"number": "123",
"print": "number",
"uploadAt" : "2021-22-01",
"servicesAt" : "2020-01-12"
}
}
UPDATE based on comments
Solution #3:
db.testCollection.aggregate([
{
$addFields: {
"array": { $objectToArray: "$data" }
}
},
{ $unwind: "$array" },
{
$addFields: { "array.v.number": "$array.v.Id" }
},
{
$project: { "array.v.Id": 0 } // Optional
},
{
$group: {
_id: "$_id",
array: { $push: "$array" }
}
},
{
$replaceRoot: {
newRoot: { $arrayToObject: "$array" }
}
}
]);
Solution #2: If you do not wat to rename filed Id to number then the solution is simple:
db.testCollection.aggregate([
{
$replaceRoot: {
newRoot: {
$arrayToObject: { $objectToArray: "$data" }
}
}
}
]);
Output:
{
"abc" : {
"Id" : "100",
"print" : "number",
"uploadAt" : "2021-22-01",
"servicesAt" : "2020-01-12"
},
"xyz" : {
"Id" : "123",
"print" : "number",
"uploadAt" : "2021-22-01",
"servicesAt" : "2020-01-12"
}
}
OLD SOLUTION
Try this
db.testCollection.aggregate([
{
$replaceRoot: {
newRoot: {
$arrayToObject: {
$map: {
input: { $objectToArray: "$data" },
as: "item",
in: {
k: "$$item.k",
v: {
number: "$$item.v.Id",
print: "$$item.v.print",
uploadAt: "$$item.v.uploadAt",
servicesAt: "$$item.v.servicesAt"
}
}
}
}
}
}
}
]);
Output
{
"abc" : {
"number" : "100",
"print" : "number",
"uploadAt" : "2021-22-01",
"servicesAt" : "2020-01-12"
},
"xyz" : {
"number" : "123",
"print" : "number",
"uploadAt" : "2021-22-01",
"servicesAt" : "2020-01-12"
}
}

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

MongoDB count array in array

How to realize count array in array using aggregate? I have this document structure:
{
"_id" : 1,
"link" : [
{
"linkHistory" : [
{
"_id" : 1,
},
{
"_id" : 2,
}
]
}
]
}
and MongoDB code:
db.emailGroup.aggregate([
{
"$lookup":
{
"from": "link",
"localField": "_id",
"foreignField": "emailGroupId",
"as": "link"
},
},
{
"$unwind": "$link"
},
{
"$match": {
'link.originalLink': ""
}
},
{
"$group" : {
_id: '$_id',
link: {
$push: '$link'
}
}
}
])
I want to get count in other field for linkHistory. Is this possible?

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