I am new to MongoDB and need some help with building a query.
I have a collection of documents like this:
{
"userId": "AAAA",
"settings": [
{
"key": "theme",
"value": "Dark"
},
{
"key": "btnColor",
"value": "blue"
},
{
"key": "otherSetting",
"value": "otherValue"
}
]
}
I have an array of keys (passing to the server), like [theme, btnColor, someNonExistingKey]
And I need to get a document by userId with settings which keys are inside the array of keys, so I need to get that:
{
"userId": "AAAA",
"settings": [
{
"key": "theme",
"value": "Dark"
},
{
"key": "btnColor",
"value": "blue"
}
]
}
Can anyone please help me with correct query?
$match - Filter the document by userId and settings.key in provided input array.
$project - Decorate output documents. For settings field, filter the document by key is existed in provided input array.
db.collection.aggregate([
{
$match: {
userId: "AAAA",
"settings.key": {
$in: [
"theme",
"btnColor",
"abc"
]
}
}
},
{
$project: {
"userId": 1,
"settings": {
$filter: {
input: "$settings",
cond: {
$in: [
"$$this.key",
[
"theme",
"btnColor",
"abc"
]
]
}
}
}
}
}
])
Sample Mongo Playground
Related
I have the following Mongo collection
{
"_id": ObjectId("5524d12d2702a21830bdb8e5"),
"code": "Apple",
"name": "iPhone",
"parameters": [
{
"code": "xxx",
"name": "Andrew",
"value": "9",
},
{
"code": "yyy",
"name": "Joy",
"value": "7",
},
]
}
I am using the following query to push into the parameters array object
db.coll.update({
"parameters.name": "Andrew"
},
{
$push: {
"parameters": {
"code": "$code",
"name": "bar",
"value": "10",
}
}
},
{
multi: true
})
However, for the value of code, I want to use the value of the object that matched (i.e. the object with parameters.name == "Andrew", which here is xxx.
Here's a playground link to the problem https://mongoplayground.net/p/v-j1tCCjiWq
Also, I am using a really old version (3.2) of MongoDb. It would be preferable if the solution worked with that.
With MongoDB v4.4+, you can first $match with your criteria. Chain up $first with $filter to extract the array element you want. Use $concatArrays to append a new element(i.e. same as $push) to the array and $merge to update back into the collection.
db.coll.aggregate([
{
$match: {
"parameters.name": "Andrew"
}
},
{
$set: {
parameters: {
"$concatArrays": [
"$parameters",
[
{
"$mergeObjects": [
// get the object matched
{
"$first": {
"$filter": {
"input": "$parameters",
"as": "p",
"cond": {
$eq: [
"Andrew",
"$$p.name"
]
}
}
}
},
// update the object matched with other fields with constant value
{
"name": "bar",
"value": "10"
}
]
}
]
]
}
}
},
{
"$merge": {
"into": "coll1",
"on": "_id"
}
}
])
Mongo Playground
Here is the collection:
db.employees.insertMany([
{
"data": {
"category": [
{
"name": "HELLO",
"subcategory": [
"EDUCATION",
"ART",
]
},
{
"name": "HELLO",
"subcategory": [
"GG",
"ART",
]
},
{
"name": "HELLO",
"subcategory": [
"EDUCATION",
"SHORE",
]
}
]
}
},
{
"data": {
"category": [
{
"name": "HELLO",
"subcategory": [
"EDUCATION",
"HELLO",
]
}
]
}
},
{
"data": {
"category": [
{
"name": "HELLO",
"subcategory": [
"GG",
"ART",
]
}
]
}
}
]);
What I want is to locate the elements in 'category' with a 'subcategory' that contains 'EDUCATION' and replace 'EDUCATION' with another string, let's say 'SPORTS'.
I tried a couple of commands but nothing really did the job:
db.employees.updateMany({
"data.category.subcategory": "EDUCATION"
},
{
"$set": {
"data.category.$": {
"subcategory": "SPORTS"
}
}
})
What I saw is that it doesn't update the element by replacing it and it doesn't replace every element that meets the criteria.
Think that MongoDB Update with Aggregation Pipeline fulfills your scenario.
$set - Set data.category value.
1.1. $map - Iterate each element in data.category and return an array.
1.1.1. $mergeObjects - Merge the current document with the document with subcategory field from 1.1.1.1.
1.1.1.1 $map - Iterate each value from the subcategory array. With $cond to replace the word EDUCATION with SPORTS if fulfilled, else use existing value ($$this).
db.employees.updateMany({
"data.category.subcategory": "EDUCATION"
},
[
{
"$set": {
"data.category": {
$map: {
input: "$data.category",
in: {
$mergeObjects: [
"$$this",
{
subcategory: {
$map: {
input: "$$this.subcategory",
in: {
$cond: {
if: {
$eq: [
"$$this",
"EDUCATION"
]
},
then: "SPORTS",
else: "$$this"
}
}
}
}
}
]
}
}
}
}
}
]
Sample Mongo Playground
Here's another way to do it using "arrayFilters".
db.collection.update({
"data.category.subcategory": "EDUCATION"
},
{
"$set": {
"data.category.$[].subcategory.$[elem]": "SPORTS"
}
},
{
"arrayFilters": [
{ "elem": "EDUCATION" }
],
"multi": true
})
Try it on mongoplayground.net.
My collection has array "name" with objects inside. I need to remove only those objects inside array where "name.x" is blank.
"name": [
{
"name.x": [
{
"_id": "607e7fcca57aa56e2a06b57b",
"name": "abc",
"type": "123"
}
],
"_id": {
"$oid": "62232cd70ce38c5007de31e6"
},
"qty": "1.0",
"Unit": "pound,lbs"
},
{
"name.x": [
{
"_id": "607e7fcca57aa56e2a06b430",
"name": "xyz",
"type": "123"
}
],
"_id": {
"$oid": "62232cd70ce38c5007de31e7"
},
"qty": "1.0",
"Unit": "pound,lbs"
},{
"name.x": []
,
"_id": {
"$oid": "62232cd70ce38c5007de31e7"
},
"qty": "1.0",
"Unit": "pound,lbs"
}
I tried to get all the ids where name.x is blank using python and used $pull to remove objects base on those ids.But the complete array got deleted.How can I remove the objects that meet the condition.
Think MongoDB update with aggregation pipeline meets your requirement especially to deal with the field name with ..
$set - Update the name array field by $filter name.x field is not an empty array.
db.collection.update({},
[
{
$set: {
name: {
$filter: {
input: "$name",
cond: {
$ne: [
{
$getField: {
field: "name.x",
input: "$$this"
}
},
[]
]
}
}
}
}
}
],
{
multi: true
})
Sample Mongo Playground
I have a problem with filtering fields(objects) from my document using find function.
Here is my db.collection.find(..) document:
{
"_id": BinData(3, "Uz+QwtoVMt7hjpqMrLxVhQ=="),
"name": "jeorge",
"permissions": [
{
"key": "group.staff",
"value": true,
"context": [
{ "key": "server", "value": "test" }
]
},
{
"key": "group.tester",
"value": true,
"context": [
{ "key": "server", "value": "test" }
]
},
{
"key": "test.test",
"value": true
},
{
"key": "group.default",
"value": true
},
{
"key": "group.helper",
"value": true
}
]
}
How can I filter my document in two ways?
a) Display only fields with nested context object.
b) Display only fields, which hasn't got nested context objects.
Also I need to check if parent's object property key contains string "group" as value (If not - skip it).
For situation b, I have tried this function, but it prints result with only the first matched element (based on mongodb's documentation).
db.collection.find(
{
"_id": BinData(3, "Uz+QwtoVMt7hjpqMrLxVhQ==")
},
{
"permissions": {
$elemMatch: {
"key": {
$regex: "group",
},
"value": true
}
}
}
);
Is it possible by single query? Thanks!
db.collection.aggregate([
{
$match: { _id: BinData(3, "Uz+QwtoVMt7hjpqMrLxVhQ==") }
},
{
$set: {
permissions: {
$filter: {
input: "$permissions",
as: "p",
cond: {
$and: [
"$$p.context",
{ $regexMatch: { input: "$$p.key", regex: "group" } }
]
}
}
}
}
}
])
mongoplayground
db.collection.aggregate([
{
$match: {
_id: BinData(3, "Uz+QwtoVMt7hjpqMrLxVhQ==")
}
},
{
$set: {
permissions: {
$filter: {
input: "$permissions",
as: "p",
cond: {
$and: [
{
$not: [ "$$p.context" ]
},
{
$regexMatch: {
input: "$$p.key",
regex: "group"
}
}
]
}
}
}
}
}
])
mongoplayground
I have following data.
[
{
"_id": 1,
"array": [
{
"name": "name1",
"nestedArray": [
{
"key": "key1",
"value": "value1"
},
{
"key": "key2",
"value": "value2"
}
]
},
{
"name": "name2",
"nestedArray": [
{
"key": "key1",
"value": "abc"
},
{
"key": "key2",
"value": "value2"
}
]
}
]
}
]
playground
I want to keep the entry whose nestedArray contains 2 matching elements and remove others. The 2 elements are below
{
"key": "key1",
"value": "abc"
},
{
"key": "key2",
"value": "value2"
}
So that the result will be below
[
{
"_id": 1,
"array": [
{
"name": "name2",
"nestedArray": [
{
"key": "key1",
"value": "abc"
},
{
"key": "key2",
"value": "value2"
}
]
}
]
}
]
The one whose name="name1" is removed since it has only one matching element.
Feels like we could use $elemMatch but couldn't figure it out.
First, Unwind the array so that you can easily access the nestedArray .
Second, use $all and $elementMatch on nestedArray
db.collection.aggregate([
{
$unwind: "$array"
},
{
$match: {
"array.nestedArray": {
$all: [
{
"$elemMatch": {
key: "key1",
value: "abc"
}
},
{
"$elemMatch": {
key: "key2",
value: "value2"
}
}
]
}
}
},
{
$group: {
_id: "$_id",
array: {
"$push": "$array"
}
}
}
])
playground
You didn't really specify how you input looks but the strategy will stay the same regardless, We will iterate over the array with $filter and match only documents that their subarray's size is equal to a filtered subarray based on the given input, like so:
// the current input structure.
inputArray = [
{
"key": "key1",
"value": "abc"
},
{
"key": "key2",
"value": "value2"
}
];
db.collection.aggregate([
{
$project: {
array: {
$filter: {
input: "$array",
as: "element",
cond: {
$and: [
{
$eq: [
{
$size: {
$filter: {
input: "$$element.nestedArray",
as: "nested",
cond: {
"$setIsSubset": [
[
"$$nested"
],
inputArray
]
}
}
},
},
{
$size: "$$element.nestedArray"
}
]
},
{ // this is required for an exact match otherwise subsets are possible, if you wan't to allow it delete this equality completly.
$eq: [
{
$size: "$$element.nestedArray"
},
{
$size: inputArray
}
]
}
]
}
}
}
}
}
])
Again if the input is coming in a different structure you'll have to change the $eq conditions