MongoDB Trasnform string array to string concatenated in alphabetical order - mongodb

Playground
Lets say I have this collection:
[
{ "Topics": [ "a", "b" ] },
{ "Topics": [ "x", "a" ] },
{ "Topics": [ "k", "c", "z" ] }
]
I want to transform this string array to a single string with the itens of it in alphabetical order. The result would be:
[
{ Topic: "a/b"},
{ Topic: "a/x"},
{ Topic: "c/k/z"}
]
How can I project this result? Using Map? Reduce?
I have Mongo 5.0
Playground
cheers

just found the solution after some tries...
Just A Unwind, Sort, Group, Project with Reduce made the job...
Data
[
{
"Topics": [
"a",
"b"
]
},
{
"Topics": [
"x",
"a"
]
},
{
"Topics": [
"k",
"c",
"z"
]
}
]
Query
db.collection.aggregate([
{
"$unwind": "$Topics"
},
{
"$sort": {
"Topics": 1
}
},
{
"$group": {
"_id": "$_id",
Topics: {
"$push": "$Topics"
}
}
},
{
"$project": {
Topic: {
$reduce: {
input: "$Topics",
initialValue: "1T1",
in: {
$concat: [
"$$value",
"/",
"$$this"
]
}
}
}
}
}
])
Result:
[
{
"Topic": "1T1/a/x",
"_id": ObjectId("5a934e000102030405000001")
},
{
"Topic": "1T1/c/k/z",
"_id": ObjectId("5a934e000102030405000002")
},
{
"Topic": "1T1/a/b",
"_id": ObjectId("5a934e000102030405000000")
}
]

The common way to do this is
unwind
sort
group by id
reduce to 1 string
Bellow is a way to not unwind all collection but do a "local unwind".
Query
lookup with a dummy collection of 1 empty document [{}]
(this is "trick" that allows us to use stage operators like sort inside 1 document array) you need that collection in your database
unwind topics, sort them, group in 1 array, reduce them and create 1 string
we will have only 1 joined document (the transformed root document),
we replace the root with that
remove the "/" from start (it could be done on the reduce stage also)
added one extra case where topics are empty array to return ""
Test code here
db.topics.aggregate([
{
"$lookup": {
"from": "dummy",
"let": {
"topics": "$Topics"
},
"pipeline": [
{
"$set": {
"Topics": "$$topics"
}
},
{
"$unwind": {
"path": "$Topics"
}
},
{
"$sort": {
"Topics": 1
}
},
{
"$group": {
"_id": null,
"Topics": {
"$push": "$Topics"
}
}
},
{
"$project": {
"_id": 0
}
},
{
"$set": {
"Topics": {
"$reduce": {
"input": "$Topics",
"initialValue": "",
"in": {
"$let": {
"vars": {
"s": "$$value",
"t": "$$this"
},
"in": {
"$concat": [
"$$s",
"/",
"$$t"
]
}
}
}
}
}
}
}
],
"as": "joined"
}
},
{
"$replaceRoot": {
"newRoot": {
"$cond": [
{
"$eq": [
"$joined",
[]
]
},
{
"Topics": ""
},
{
"$arrayElemAt": [
"$joined",
0
]
}
]
}
}
},
{
"$set": {
"Topics": {
"$cond": [
{
"$gt": [
{
"$strLenCP": "$Topics"
},
0
]
},
{
"$substrCP": [
"$Topics",
1,
{
"$strLenCP": "$Topics"
}
]
},
""
]
}
}
}
])

Related

Filter out items from mongoDb nested array and add new field

There is a mongoDb collection, looks like this:
[
{
"_id": {
"$oid": "63110728d74738cdc48a7de0"
},
"listName": "list_name",
"alloweUidList": [
{
"uid": "prQUKkIxljVqbHlCKah7T1Rh7l22",
"role": "creator",
"boolId": 1,
"crDate": "2022-09-01 21:25",
"modDate": null
}
],
"offerModelList": [
{
"offerListenerEntity": {
"_id": "6311072ed74738cdc48a7de1",
"uid": "prQUKkIxljVqbHlCKah7T1Rh7l22",
"itemName": "sometehing",
"crDate": "2022-09-01 21:25",
"boolId": 1,
"modDate": null,
"imageColorIndex": 3,
"shoppingListId": "63110728d74738cdc48a7de0",
"checkFlag": 0,
"itemCount": 1
},
"offers": [
{
"id": "62fa7983b7f32cc089864a3b",
"itemId": 127382,
"itemName": "item_1",
"itemCleanName": "item_clean_name",
"imageUrl": "item.png",
"price": 10,
"measure": "measure",
"salesStart": "N.a",
"source": "source",
"runDate": "2022.08.15-14:11:15",
"shopName": "shop_name",
"isSales": 1,
"insertType": "automate",
"timeKey": "2022_08_15_18_51",
"imageColorIndex": 0,
"isSelectedFlag": 1,
"selectedBy": "not_selected",
"itemCount": 1
},
{
"id": "62fa7983b7f32cc089864a3b",
"itemId": 127382,
"itemName": "item_2",
"itemCleanName": "item_clean_name",
"imageUrl": "image.png",
"price": 20,
"measure": "measure",
"salesStart": "N.a",
"source": "source",
"runDate": "2022.08.15-14:11:15",
"shopName": "shop_name",
"isSales": 1,
"insertType": "automate",
"timeKey": "2022_08_15_18_51",
"imageColorIndex": 0,
"isSelectedFlag": 0,
"selectedBy": "not_selected",
"itemCount": 1
}
]
},
{
"offerListenerEntity": {
"_id": "6311a5c0d74738cdc48a7de2",
"uid": "prQUKkIxljVqbHlCKah7T1Rh7l22",
"itemName": "anything",
"crDate": "2022-09-02 08:42",
"boolId": 1,
"modDate": null,
"imageColorIndex": 1,
"shoppingListId": "63110728d74738cdc48a7de0",
"checkFlag": 0,
"itemCount": 2
},
"offers": []
}
],
"crDate": "2022-09-01 21:25",
"modDate": "2022-09-01 21:25",
"boolId": 1,
"imageColorIndex": 1
}
]
So it has an array, with a nested array.
I would like to filter out the entire item from the offerModelList array, if the offerModelList.offerListenerEntity.boolId == 0 It's working with this aggregate query:
[
{
"$match": {
"alloweUidList": {
"$elemMatch": {
"uid": "prQUKkIxljVqbHlCKah7T1Rh7l22",
"boolId": 1
}
},
"boolId": 1,
}
},
{
"$addFields": {
"offerModelList": {
"$filter": {
"input": "$offerModelList",
"as": "i",
"cond": {
"$eq": [
"$$i.offerListenerEntity.boolId",
1
]
}
}
}
},
}
]
The problem comes, when I try to filter out items from the offerModelList.offers array based on isSelectedFlag field.
I modified my query to this:
db.collection.aggregate([
{
"$match": {
"alloweUidList": {
"$elemMatch": {
"uid": "prQUKkIxljVqbHlCKah7T1Rh7l22",
"boolId": 1
}
},
"boolId": 1,
}
},
{
"$addFields": {
"offerModelList": {
"$filter": {
"input": "$offerModelList",
"as": "i",
"cond": {
"$eq": [
"$$i.offerListenerEntity.boolId",
1
]
}
}
}
},
},
{
"$addFields": {
"offerModelList.offers": {
"$filter": {
"input": "$offerModelList.offers",
"as": "x",
"cond": {
"$eq": [
"$$x.isSelectedFlag",
1
]
}
}
}
},
}
])
The problem is, it alwas return empty offers array.
Here comes an example: https://mongoplayground.net/p/kksRpoNKr1k in this specific case the offers array should cointains only 1 item.
Don't think that you are able to directly filter from offerModelList.offers.
Instead, for the last stage,
$set - Set offerModelList field.
1.1. $map - Iterate element in offerModelList array and return a new array.
1.1.1. $mergeObjects - Merge current iterated document with the document resulted from 1.1.1.1.
1.1.1.1. Document with offers array. Via $filter to filter the document(s) with isSelectedFlag: 1.
db.collection.aggregate([
{
"$match": {
"alloweUidList": {
"$elemMatch": {
"uid": "prQUKkIxljVqbHlCKah7T1Rh7l22",
"boolId": 1
}
},
"boolId": 1,
}
},
{
"$addFields": {
"offerModelList": {
"$filter": {
"input": "$offerModelList",
"as": "i",
"cond": {
"$eq": [
"$$i.offerListenerEntity.boolId",
1
]
}
}
}
},
},
{
"$set": {
"offerModelList": {
$map: {
input: "$offerModelList",
as: "offerModel",
in: {
$mergeObjects: [
"$$offerModel",
{
offers: {
$filter: {
input: "$$offerModel.offers",
as: "x",
cond: {
$eq: [
"$$x.isSelectedFlag",
1
]
}
}
}
}
]
}
}
}
}
}
])
Demo # Mongo Playground

MongoDB. Get every element from array in new field

I have a document with a nested array array_field:
{
"_id": {
"$oid": "1"
},
"id": "1",
"array_field": [
{
"data": [
{
"regions": [
{
"result": {
"item": [
"4",
"5",
"3"
]
}
},
{
"result": {
"item": [
"5"
]
}
},
{
"result": {
"item": [
"1"
]
}
}
]
}
]
}
]
}
I need add new field, new_added_field for example, with each array element from array_field.data.regions.result.item and remove array_field from document.
For example:
{
"_id": {
"$oid": "1"
},
"id": "1",
"new_added_field": [4,5,3,5,1]
}
I think i can do this with help of $unwind or $map but have difficulties and need dome hint, how i can do it with help op aggregation?
As you said,
db.collection.aggregate([
{
"$project": {
newField: {
"$map": {
"input": "$array_field",
"as": "m",
"in": "$$m.data.regions.result.item"
}
}
},
},
{ "$unwind": "$newField" },
{ "$unwind": "$newField" },
{ "$unwind": "$newField" },
{ "$unwind": "$newField" },
{
"$group": {
"_id": "$_id",
"newField": { "$push": "$newField" }
}
}
])
Working Mongo playground

How to combine mongodb original output of query with some new fields?

Collection:
[
{
"name": "device1",
"type": "a",
"para": {
"number": 3
}
},
{
"name": "device2",
"type": "b",
"additional": "c",
"para": {
"number": 1
}
}
]
My query:
db.collection.aggregate([
{
"$addFields": {
"arrayofkeyvalue": {
"$objectToArray": "$$ROOT"
}
}
},
{
"$unwind": "$arrayofkeyvalue"
},
{
"$group": {
"_id": null,
"allkeys": {
"$addToSet": "$arrayofkeyvalue.k"
}
}
}
])
The output currently:
[
{
"_id": null,
"allkeys": [
"additional",
"_id",
"para",
"type",
"name"
]
}
]
Detail see Playground
What I want to do is add a new column which includes all of top key of the mongodb query output, exclude "para". And then combine it with the old collection to form a new json.
Is it possible?
The expected result:
{
"column": [{"prop": "name"}, {"prop": "type"}, {"prop": "additional"}],
"columnData": [
{
"name": "device1",
"type": "a",
"para": {
"number": 3
}
},
{
"name": "device2",
"type": "b",
"additional": "c",
"para": {
"number": 1
}
}
]
}
You have the right general idea in mind, here's how I would do it by utilizing operators like $filter, $map and $reduce to manipulate the objects structure.
I separated the aggregation into 3 parts for readability but you can just merge stage 2 and 3 if you wish.
db.collection.aggregate([
{
"$group": {
"_id": null,
columnData: {
$push: "$$ROOT"
},
"keys": {
"$push": {
$map: {
input: {
"$objectToArray": "$$ROOT"
},
as: "field",
in: "$$field.k"
}
}
}
}
},
{
"$addFields": {
unionedKeys: {
$filter: {
input: {
$reduce: {
input: "$keys",
initialValue: [],
in: {
"$setUnion": [
"$$this",
"$$value"
]
}
}
},
as: "item",
cond: {
$not: {
"$setIsSubset": [
[
"$$item"
],
[
"_id",
"para"
]
]
}
}
}
}
}
},
{
$project: {
_id: 0,
columnData: 1,
column: {
$map: {
input: "$unionedKeys",
as: "key",
in: {
prop: "$$key"
}
}
}
}
}
])
Mongo Playground

Is there a way in mongodb to group at multiple levels

I have a document which contains an array of array as given below.
This is the first document.
{
"_id": "5d932a2178fdfc4dc41d75da",
"data": [
{
"nestedData": [
{
"_id": "5d932a2178fdfc4dc41d75e1",
"name": "Special 1"
},
{
"_id": "5d932a2178fdfc4dc41d75e0",
"name": "Special 2"
}
]
}
]
}
I need to lookup(join) to another collection with the _id in the nestedData array in the aggregation framework.
The 2nd document from which I need to lookup is
{
"_id": "5d8b1ac3b15bc72d154408e1",
"status": "COMPLETED",
"rating": 4
}
I know I need to $unwind it twice to convert nestedData array into object.
But how do I group back again to form the same object like given below
{
"_id": "5d932a2178fdfc4dc41d75da",
"data": [
{
"array": [
{
"_id": "5d932a2178fdfc4dc41d75e1",
"name": "Special 1",
"data": {
"_id": "5d8b1ac3b15bc72d154408e1",
"status": "COMPLETED",
"rating": 4
},
{
"_id": "5d932a2178fdfc4dc41d75e0",
"name": "Special 2",
"data": {
"_id": "5d8b1ac3b15bc72d154408e0",
"status": "COMPLETED",
"rating": 4
},
}
]
}
]
}
Try this query
db.testers.aggregate([
{$lookup: {
from: 'demo2',
pipeline: [
{ $sort: {'_id': 1}},
],
as: 'pointValue',
}},
{
$addFields:{
"data":{
$map:{
"input":"$data",
"as":"doc",
"in":{
$mergeObjects:[
"$$doc",
{
"nestedData":{
$map:{
"input":"$$doc.nestedData",
"as":"nestedData",
"in":{
$mergeObjects:[
{ $arrayElemAt: [ {
"$map": {
"input": {
"$filter": {
"input": "$pointValue",
"as": "sn",
"cond": {
"$and": [
{ "$eq": [ "$$sn._id", "$$nestedData._id" ] },
]
}
}
},"as": "data",
"in": {
"name": "$$nestedData.name",
"data":"$$data",
}}
}, 0 ] },'$$nestedData'
],
}
}
}
}
]
}
}
}
}
},
{$project: { pointValue: 0 } }
]).pretty()

Zip two array and create new array of object

hello all i'm working with a MongoDB database where each data row is like:
{
"_id" : ObjectId("5cf12696e81744d2dfc0000c"),
"contributor": "user1",
"title": "Title 1",
"userhasRate" : [
"51",
"52",
],
"ratings" : [
4,
3
],
}
and i need to change it to be like:
{
"_id" : ObjectId("5cf12696e81744d2dfc0000c"),
"contributor": "user1",
"title": "Title 1",
rate : [
{userhasrate: "51", value: 4},
{userhasrate: "52", value: 3},
]
}
I already try using this method,
db.getCollection('contens').aggregate([
{ '$group':{
'rates': {$push:{ value: '$ratings', user: '$userhasRate'}}
}
}
]);
and my result become like this
{
"rates" : [
{
"value" : [
5,
5,
5
],
"user" : [
"51",
"52",
"53"
]
}
]
}
Can someone help me to solve my problem,
Thank you
You can use $arrayToObject and $objectToArray inside $map to achieve the required output.
db.collection.aggregate([
{
"$project": {
"rate": {
"$map": {
"input": {
"$objectToArray": {
"$arrayToObject": {
"$zip": {
"inputs": [
"$userhasRate",
"$ratings"
]
}
}
}
},
"as": "el",
"in": {
"userhasRate": "$$el.k",
"value": "$$el.v"
}
}
}
}
}
])
Alternative Method
If userhasRate contains repeated values then the first solution will not work. You can use arrayElemAt and $map along with $zip if it contains repeated values.
db.collection.aggregate([
{
"$project": {
"rate": {
"$map": {
"input": {
"$zip": {
"inputs": [
"$userhasRate",
"$ratings"
]
}
},
"as": "el",
"in": {
"userhasRate": {
"$arrayElemAt": [
"$$el",
0
]
},
"value": {
"$arrayElemAt": [
"$$el",
1
]
}
}
}
}
}
}
])
Try below aggregate, first of all you used group without _id that grouped all the JSONs in the collection instead set it to "$_id" also you need to create 2 arrays using old data then in next project pipeline concat the arrays to get desired output:
db.getCollection('contens').aggregate([
{
$group: {
_id: "$_id",
rate1: {
$push: {
userhasrate: {
$arrayElemAt: [
"$userhasRate",
0
]
},
value: {
$arrayElemAt: [
"$ratings",
0
]
}
}
},
rate2: {
$push: {
userhasrate: {
$arrayElemAt: [
"$userhasRate",
1
]
},
value: {
$arrayElemAt: [
"$ratings",
1
]
}
}
}
}
},
{
$project: {
_id: 1,
rate: {
$concatArrays: [
"$rate1",
"$rate2"
]
}
}
}
])