How do I capitalize the first letter of a string in mongoDB? - mongodb

I have the following document in my MongoDB:
{
name: "BRIAN"
}
I'm trying to change it to this format:
{
name: "Brian"
}
I've tried the following:
{
$project: {
name: { $concat: [ { $toLower: '$name' } ]}
}
}
So this makes the string lowercase, then I just need to make the first letter uppercase. Is it possible to do that within the same project aggregation?

As #Wernfried Domscheit suggested, you can use $substrCP to segment name and perform $toLower separately for the "tail" and keep the first character upper case. Use $concat to put back the 2 results together.
db.collection.update({},
[
{
"$addFields": {
"name": {
"$concat": [
{
"$substrCP": [
"$name",
0,
1
]
},
{
"$toLower": {
"$substrCP": [
"$name",
1,
{
"$strLenCP": "$name"
}
]
}
}
]
}
}
}
],
{
multi: true
})
Mongo Playground

Related

Perform search with facets unknown upfront Atlas MongoDB

I have the following document structure in MongoDB:
{
// other keys,
tags: [
tagA: "red",
tagB: "green"
]
},
{
// other keys,
tags: [
tagA: "orange",
tagB: "green",
tagC: "car"
]
}
I want to perform a $facets search that gives me the following output (name of each tag + values that occur on that tag + count of these value):
{
[
tagA: {
red: 1,
orange: 1
},
tagB: {
green: 2
},
tagC: {
car: 1
}
]
}
The tricky part is that the facets are unknown upfront (they can vary), and every tutorial I found only works for a predefined set of facets.
Is it possible?
P.S.: how to get the output of this to come alongside with a given query? So that the return is something like:
{
queryResults: [all the results, as in a normal query],
facets: [result showed in accepted answer]
}
If you consider having this as input (i've added bracket around object in your array) :
[
{
tags: [
{
tagA: "red"
},
{
tagB: "green"
}
]
},
{
tags: [
{
tagA: "orange"
},
{
tagB: "green"
},
{
tagC: "car"
}
]
}
]
You could then do an aggregation pipeline as follow :
db.collection.aggregate([
{
"$unwind": "$tags"
},
{
"$addFields": {
"kv": {
"$objectToArray": "$tags"
}
}
},
{
"$unwind": "$kv"
},
{
"$group": {
"_id": {
key: "$kv.k",
value: "$kv.v"
},
"count": {
"$sum": 1
}
}
},
{
"$group": {
"_id": "$_id.key",
"value": {
"$push": {
"k": "$_id.value",
"v": "$count"
}
}
}
},
{
$project: {
val: [
{
k: "$_id",
v: {
"$arrayToObject": "$value"
}
}
]
}
},
{
$project: {
res: {
"$arrayToObject": "$val"
}
}
},
{
$replaceRoot: {
newRoot: "$res"
}
}
])
It would give you this result :
[
{
"tagA": {
"orange": 1,
"red": 1
}
},
{
"tagB": {
"green": 2
}
},
{
"tagC": {
"car": 1
}
}
]
You can see this on mongoplayground : https://mongoplayground.net/p/FZbM-BGJRBm
Hope this answer your question.
Detailled explanation :
I use $unwind on the tags field in order to get one object per object in tags array.
I use $objectToArray to get keys (tagsA, tagsB) as values.
$unwind to go from an array to objets.
$group with $sum accumulator to calculate the occurence of each unique combination.
$group by tagsA,tagsB, etc with $push accumulator to add value in array (will be usufull afterwards)
$arrayToObject to go from array to object
Same
$replaceRoot to display results better.
If you want to understand more each step, consider reading mongo doc of each pipeline aggregator i used. You can also use the mongoplayground link above, delete some code to see what happens after each step.

Variable set of attributes $facets MongoDB [duplicate]

I have the following document structure in MongoDB:
{
// other keys,
tags: [
tagA: "red",
tagB: "green"
]
},
{
// other keys,
tags: [
tagA: "orange",
tagB: "green",
tagC: "car"
]
}
I want to perform a $facets search that gives me the following output (name of each tag + values that occur on that tag + count of these value):
{
[
tagA: {
red: 1,
orange: 1
},
tagB: {
green: 2
},
tagC: {
car: 1
}
]
}
The tricky part is that the facets are unknown upfront (they can vary), and every tutorial I found only works for a predefined set of facets.
Is it possible?
P.S.: how to get the output of this to come alongside with a given query? So that the return is something like:
{
queryResults: [all the results, as in a normal query],
facets: [result showed in accepted answer]
}
If you consider having this as input (i've added bracket around object in your array) :
[
{
tags: [
{
tagA: "red"
},
{
tagB: "green"
}
]
},
{
tags: [
{
tagA: "orange"
},
{
tagB: "green"
},
{
tagC: "car"
}
]
}
]
You could then do an aggregation pipeline as follow :
db.collection.aggregate([
{
"$unwind": "$tags"
},
{
"$addFields": {
"kv": {
"$objectToArray": "$tags"
}
}
},
{
"$unwind": "$kv"
},
{
"$group": {
"_id": {
key: "$kv.k",
value: "$kv.v"
},
"count": {
"$sum": 1
}
}
},
{
"$group": {
"_id": "$_id.key",
"value": {
"$push": {
"k": "$_id.value",
"v": "$count"
}
}
}
},
{
$project: {
val: [
{
k: "$_id",
v: {
"$arrayToObject": "$value"
}
}
]
}
},
{
$project: {
res: {
"$arrayToObject": "$val"
}
}
},
{
$replaceRoot: {
newRoot: "$res"
}
}
])
It would give you this result :
[
{
"tagA": {
"orange": 1,
"red": 1
}
},
{
"tagB": {
"green": 2
}
},
{
"tagC": {
"car": 1
}
}
]
You can see this on mongoplayground : https://mongoplayground.net/p/FZbM-BGJRBm
Hope this answer your question.
Detailled explanation :
I use $unwind on the tags field in order to get one object per object in tags array.
I use $objectToArray to get keys (tagsA, tagsB) as values.
$unwind to go from an array to objets.
$group with $sum accumulator to calculate the occurence of each unique combination.
$group by tagsA,tagsB, etc with $push accumulator to add value in array (will be usufull afterwards)
$arrayToObject to go from array to object
Same
$replaceRoot to display results better.
If you want to understand more each step, consider reading mongo doc of each pipeline aggregator i used. You can also use the mongoplayground link above, delete some code to see what happens after each step.

How to use $addFields in mongo to add elements to just existing documents?

I am trying to add a new field to an existing document by using a combination of both $ifnull and $cond but an empty document is always added at the end.
Configuration:
[
{
line: "car",
number: "1",
category: {
FERRARI: {
color: "blue"
},
LAMBORGHINI: {
color: "red"
}
}
},
{
line: "car",
number: "2",
category: {
FERRARI: {
color: "blue"
}
}
}
]
Query approach:
db.collection.aggregate([
{
$match: {
$and: [
{ line: "car" },
{ number: { $in: ["1", "2"] } }
]
}
},
{
"$addFields": {
"category.LAMBORGHINI.number": {
$cond: [
{ "$ifNull": ["$category.LAMBORGHINI", false] },
"$number",
"$$REMOVE"
]
}
}
},
{
$group: {
_id: null,
CATEGORIES: {
$addToSet: "$category.LAMBORGHINI"
}
}
}
])
Here is the link to the mongo play ground:
https://mongoplayground.net/p/RUnu5BNdnrR
I tried the mentioned query but I still get that ugly empty set added at the end.
$$REMOVE will remove last field/key, from your field category.LAMBORGHINI.number the last field is number that is why it is removing number from the end, you can try another approach,
specify just category.LAMBORGHINI, if condition match then it will return object of current category.LAMBORGHINI and number object after merging using $mergeObjects
{
"$addFields": {
"category.LAMBORGHINI": {
$cond: [
{ "$ifNull": ["$category.LAMBORGHINI", false] },
{
$mergeObjects: [
"$category.LAMBORGHINI",
{ number: "$number" }
]
},
"$$REMOVE"
]
}
}
}
Playground

MongoDB/Mongoose how to return a nested subdocument by _id

MongoDB newbie here.
I have a 'client' document that looks like this:
{
name: "myClient",
products: [{
name: "myProduct1",
environments: [{
name: "myEnvironment1",
changeLogs: [
{ "some": "fields21" },
{ "some": "fields22" }
]
},
{
name: "myEnvironment2",
changeLogs: [
{ "some": "fields11" },
{ "some": "fields12" }
]
}
]
},
{
name: "myProduct2",
environments: [{
name: "myEnv1",
changeLogs: [
{ "some": "fields1" },
{ "some": "fields2" }
]
},
{
name: "myEnv1",
changeLogs: [
{ "some": "fields3" },
{ "some": "fields4" }
]
}
]
}]
}
So a client has many products, which has many environments, which has many changeLogs. I am looking to return a list of changeLogs for a given environment, with only the environment._id to go on.
I can find the correct client document using just this _id:
db.clients.find({'products.environments._id': ObjectId("5a1bf4b410842200043d56ff")})
But this returns the entire client document. What I want is to return just the changeLogs array from the environment with _id: ObjectId("5a1bf4b410842200043d56ff")
Assuming I have the _id of the first environment of the first product, my desired output is the following:
[
{ "some": "fields21" },
{ "some": "fields22" }
]
What query would you recommend I use to achieve this?
Many thanks in advance for any help. The docs thus far have only been confusing, but I'm sure I'll get there in the end!
The idea here is to $unwind the products array so that its environments can be fed as input to $filter after a $match on the _id.
(lets assume the enviroment _id is 1)
db.collection.aggregate([
{
$unwind: "$products"
},
{
$match: {
"products.environments._id": 1
}
},
{
$project: {
"logsArray": {
$filter: {
input: "$products.environments",
as: "env",
cond: {
$eq: [
"$$env._id",
1
]
}
}
}
}
},
{
$unwind: "$logsArray"
}
])
O/P Should be like:
[
{
"_id": ObjectId("5a934e000102030405000000"),
"logsArray": {
"changeLogs": [
{
"some": "fields21"
},
{
"some": "fields22"
}
],
"id": 1,
"name": "myEnvironment1"
}
}
]
Note: notice the last stage $unwind of logsArray which I think is just pretty-fying the ouput. Otherwise without it also the resultant is acceptable (if you agree, can remove that).
This is just another way of doing the aggregation query. This gets the desired result.
Note I am using the "name" field of the "environments" from the sample document you had provided. The "name" can be substituted with "id" as needed.
var ENV = "myEnvironment1";
db.env.aggregate( [
{ $match: {
{ $unwind: "$products" },
{ $unwind: "$products.environments" },
{ $match: { "products.environments.name": ENV} },
{ $project: { _id: 0, changeLogs: "$products.environments.changeLogs" } },
] )
The result:
{ "changeLogs" : [ { "some" : "fields21" }, { "some" : "fields22" } ] }
If the variable ENV's value is changed, then the result will be accordingly; e.g.,: ENV = "myEnv1";
{ "changeLogs" : [ { "some" : "fields1" }, { "some" : "fields2" } ] }
{ "changeLogs" : [ { "some" : "fields3" }, { "some" : "fields4" } ] }
db.clients.aggregate([
{
$unwind: "$products"
},
{
$unwind: "$products.environments"
},
{
$match: { "products.environments._id": ObjectId("5a1bf4b410842200043fffff") }
},
{
$project: { _id: 0, changeLogs: "$products.environments.changeLogs" }
}
]).pretty()
Results in:
{
"changeLogs": [
{ "some": "fields21" },
{ "some": "fields22" }
]
}
For those finding that code confusing I found it very useful to just add one aggregate method at a time, look at the results, and then add then next method to the pipeline. Once the pipeline was complete I also experimented with removing intermediary steps to see if I could get the same results with less piping.

MongoDB aggregate array documents

I have a MongoDB collection containing elements like this:
{
"name": "test",
"instances": [
{
"year": 2015
},
{
"year": 2016
},
]
}
How can I get the minimum and maximum value for year within the document named test? E.g. I want to aggregate all documents inside that array, but I can't find the syntax for that. Thanks in advance!
Both $min and $max takes an array as a parameter and in your case path instances.year returns an array so your query can look like below:
db.col.aggregate([
{
$match: { name: "test" }
},
{
$project: {
minYear: { $min: "$instances.year" },
maxYear: { $max: "$instances.year" }
}
}
])
You can use below aggregation
db.collection.aggregate([
{ "$project": {
"maxYear": {
"$arrayElemAt": [
"$instances",
{
"$indexOfArray": [
"$instances.year",
{ "$max": "$instances.year" }
]
}
]
},
"minYear": {
"$arrayElemAt": [
"$instances",
{
"$indexOfArray": [
"$instances.year",
{ "$min": "$instances.year" }
]
}
]
}
}}
])