Match in an array within the current document - mongodb

In an aggregation pipeline operating on documents like
{
"availablePackages": [
{
"title": "Silver",
"code": "001",
},
{
"title": "Gold",
"code": "002",
},
{
"title": "Platinum",
"code": "003",
},
"selectedPackageCode": "002"
}
I need to replace everything in the above document with the title of the package whose code matches the selectedPackageCode. So I want to the pipeline to end up with
{
"packageTitle": "Gold"
}
This is not a lookup, because it's in the current document. I thought I might be able to use $let to create a variable and then a $match to find the right array element, but I have not found a syntax that works.

You need $filter to match availablePackages with selectedPackageCode and $arrayElemAt to get first matching element. In order to make it in one aggregation stage you can use $let to define temporary variable:
db.col.aggregate([
// ... other stages
{
$project: {
packageTitle: {
$let: {
vars: {
selectedPackage: {
$arrayElemAt: [
{ $filter: { input: "$availablePackages", cond: { $eq: [ "$$this.code", "$selectedPackageCode" ] } } }, 0
]
}
},
in: "$$selectedPackage.title"
}
}
}
}
])

Related

MongoDB - How to match the value of a field with nested document field value

I have a structure where I want to match the value of a field on root level with the value of a field inside another object in the same document, I got to his structure by unwinding on the nested field. So I have a structure like this:
{
"name": "somename",
"level": "123",
"nested":[
{
"somefield": "test",
"file": {
level:"123"
}
},
{
"somefield": "test2",
"file": {
level:"124"
}
}
]
}
After unwinding I got the structure like:
{
"name": "somename",
"level": "123",
"nested": {
"somefield": "test",
"file": {
level:"123"
}
}
}
So I want to match on level = nested.file.level and return only documents which satisfy this condition.
I tried using
$match: {
"nested.file.level": '$level'
}
also
$project: {
nested: {
$cond: [{
$eq: [
'nested.file.level',
'$level'
]
},
'$nested',
null
]
}
}
Nothing seems to work. Any idea on how I can match based on the mentioned criteria?
Solution 1: With $unwind stage
After $unwind stage, in the $match stage you need to use the $expr operator.
{
$match: {
$expr: {
$eq: [
"$nested.file.level",
"$level"
]
}
}
}
Demo Solution 1 # Mongo Playground
Solution 2: Without $unwind stage
Without $unwind stage, you may work with $filter operator.
db.collection.aggregate([
{
$match: {
$expr: {
$in: [
"$level",
"$nested.file.level"
]
}
}
},
{
$project: {
nested: {
$filter: {
input: "$nested",
cond: {
$eq: [
"$$this.file.level",
"$level"
]
}
}
}
}
}
])
Demo Solution 2 # Mongo Playground

Mongodb: Get value from $[<identifier>] in update

I want to rename a field inside a object itself inside a nested array.
As example, I want to rename the all tags m2 to m6 in this document:
{
"_id": 1,
"tagsGroup": [
{
"id": "1234",
"tags": {
"m1": 1,
"m2": 2
}
},
{
"id": "456",
"tags": {
"m3": 1,
"m2": 2
}
},
{
"id": "1234",
"tags": {
"m4": 2,
"m5": 2
}
},
]
}
This is my current state of work:
db.collection.update({},
{
"$set": {"tagsGroup.$[tGp].tags.m6": "$tagsGroup.$[tGp].tags.m2"},
"$unset": {"tagsGroup.$[tGp].tags.m2": ""}
},
{
arrayFilters: [{"tGp.tags.m2": {$exists: 1}}],
multi: true}
)
Unfortunately, the $tagsGroup.$[tGp].tags.m6 is not interpreted.
Do you guys, have a way to do this?
Thanks!
Probably similar to this question MongoDB rename database field within array,
There is no straight way to rename fields within arrays with a single command. You can try update with aggregation pipeline starting from MongoDB v4.2,
$map to iterate loop of tagsGroup array
$map to iterate loop of tags object after converting to array using $objectToArray, it will return in k and v format
$replaceOne will replace specific string on find field, this is starting from MongoDB v4.4
$arrayToObject convert tags array returned by second $map back to object format
$mergeObjects to merge current object with updated tags object
db.collection.update(
{ "tagsGroup.tags.m2": { $exists: true } },
[{
$set: {
tagsGroup: {
$map: {
input: "$tagsGroup",
in: {
$mergeObjects: [
"$$this",
{
tags: {
$arrayToObject: {
$map: {
input: { $objectToArray: "$$this.tags" },
in: {
k: {
$replaceOne: {
input: "$$this.k",
find: "m2",
replacement: "m6"
}
},
v: "$$this.v"
}
}
}
}
}
]
}
}
}
}
}],
{ multi: true }
)
Playground

MongoDB Aggregation: How to check if an object containing multiple properties exists in an array

I have an array of objects and I want to check if there is an object that matches multiple properties. I have tried using $in and $and but it does not work the way I want it to.
Here is my current implementation.
I have an array like
"choices": [
{
"name": "choiceA",
"id": 0,
"l": "k"
},
{
"name": "choiceB",
"id": 1,
"l": "j"
},
{
"name": "choiceC",
"id": 2,
"l": "l"
}
]
I am trying to write aggregation code that can check if there is an object that contains both "id":2 and "l":"j" properties. My current implementation checks if there is an object containing the first property then checks if there is an object containing the second one.
How can I get my desired results?
Below, see my aggregation query. The full code is here
db.poll.aggregate([
{
"$match": {
"_id": 100
}
},
{
$project: {
numberOfVotes: {
$and: [
{
$in: [
2,
"$choices.id"
]
},
{
$in: [
"j",
"$choices.l"
]
}
]
},
}
}
])
The above query returns true yet there is no object in the array both of the properties id:2 and "l":"J". I know the code works as expected. How can I get my desired results?
You want to use something like $elemMatch
db.collection.find({
choices: {
$elemMatch: {
id: 2,
l: "j"
}
}
})
MongoPlayground
EDIT
In an aggregation $project stage I would use $filter
db.poll.aggregate([
{
"$match": {
"_id": 100
}
},
{
$project: {
numberOfVotes: {
$gt: [
{
$size: {
$filter: {
input: "$choices",
as: "choice",
cond: {
$and: [
{
$eq: [
"$$choice.id",
2
]
},
{
$eq: [
"$$choice.l",
"j"
]
}
]
}
}
}
},
0
]
}
}
}
])
MongoPlayground

Given an array of objects, how can I filter the result of the existing objects in database according to each match of the array?

I have a collection with a structure like this:
{
"toystore": 22,
"toystore_name": "Toystore A",
"toys": [
{
"toy": "buzz",
"code": 17001,
"price": 500
},
{
"toy": "woddy",
"code": 17002,
"price": 1000
},
{
"toy": "pope",
"code": 17003,
"price": 300
}
]
},
{
"toystore": 11,
"toystore_name": "Toystore B",
"toys": [
{
"toy": "jessie",
"code": 17005,
"price": 500
},
{
"toy": "rex",
"code": 17006,
"price": 2000
}
]
}
]
I have n toy stores, and within each toy store I have the toys that this store has available within the toys field (is an array).
There may be repeated codes that I want to search for
[ { "toys.code": 17001 }, { "toys.code": 17003 }, { "toys.code": 17005 }, { "toys.code": 17005 }]
and I want the result to be generated by each of these toys.code no matter if they are repeated, currently the result is not repeated (for example with the code 17005)
this is my current output:
[
{
"_id": "Toystore A",
"toy_array": [
{
"price_original": 500,
"toy": "buzz"
},
{
"price_original": 300,
"toy": "pope"
}
]
},
{
"_id": "Toystore B",
"toy_array": [
//**********
//as i searched 2 times the code:17005, this object should be shown 2 times. only is showed 1 time.
{
"price_original": 500,
"toy": "jessie"
}
]
}
]
how can I get a result to return for every match in my array?
this is my live code:
db.collection.aggregate([
{
$unwind: "$toys"
},
{
$match: {
$or: [
{
"toys.code": 17001
},
{
"toys.code": 17003
},
{
"toys.code": 17005
},
{
"toys.code": 17005
}
],
}
},
{
$group: {
_id: "$toystore_name",
toy_array: {
$push: {
price_original: "$toys.price",
toy: "$toys.toy"
},
},
},
},
])
https://mongoplayground.net/p/g1-oST015y0
The $match stage examines each document in the pipeline and evaluates the provided criteria, and either eliminates the document, or passes it along to the next stage. It does not iterate the match criteria and examine the entire stream of documents for each one, which is what needs to happen in order to duplicate the document that is referenced twice.
This can be done, but you will need to pass the array of codes twice in the pipeline, once to eliminate documents that don't match at all, and again to allow the duplication you are looking for.
The stages needed are:
$match to eliminate toy store that don't have any of the requested toy
$project using
o $map to iterate the search array
o $filter to selection matching toys
o $reduce to eliminate empty arrays, and recombine the entries into a single array
an additional $project to remove the codes from toy_array
var codearray = [17001, 17003, 17005, 17005];
db.collection.aggregate([
{$match: {"toys.code": {$in: codearray }}},
{$project: {
_id: "$toystore_name",
toy_array: {
$reduce: {
input: {
$map: {
input: codearray,
as: "qcode",
in: {
$filter: {
input: "$toys",
as: "toy",
cond: {$eq: [ "$$toy.code","$$qcode" ]}
}
}
}
},
initialValue: [],
in: {
$cond: {
if: {$eq: ["$$this",[]]},
then: "$$value",
else: {$concatArrays: ["$$value", "$$this"]}
}
}
}
}
}},
{$project: {"toy_array.code": 0}}
])
Playground

Projecting a field in MongoDB based of the value of another field

I have the following document in a MongoDB 4 collection:
{
"_id": <ObjectID>,
"active_version": "3",
"versions": {
"1": "The first very long document",
"2": "The second very long document",
"3": "The third very long document"
}
}
There are many documents like this in the collection, and each document may have many versions.
When querying for this document, I would like to retrieve only the text of the active version, e.g. "The third very long document". How can I accomplish that?
You can use $objectToArray to transform versions into an array of k-v pairs and then use $let and $filter with $arrayElemAt to get first matching version:
db.col.aggregate([
{
$project: {
current_version: {
$let: {
vars: {
version: {
$arrayElemAt: [
{
$filter: {
input: { $objectToArray: "$versions" },
as: "v",
cond: { $eq: [ "$$v.k", "$active_version" ] }
}
}
,0]
}
},
in: "$$version.v"
}
}
}
}
])
Outputs:
{ "_id" : ..., "current_version" : "The third very long document" }