Merge two documents in the same collection in MongoDB - mongodb

I've been tryng to merge some duplicate documents in my collection called Cities.
Consider the following:
{
"_id": 1,
"Name": "Santa Monica",
"State": "California"
},
{
"_id": {"$oid":"5ec5fcc993ce00388429278a"},
"Name": "Santa Monica",
"State": "California",
"TimeZone": "UTC−8",
"Population": 90401,
"State": "California"
}
The second entry does have more information, like TimeZone and Population.
I want to merge both in only one object, preserving the first _id like this:
{
"_id": 1
"Name": "Santa Monica",
"State": "California",
"TimeZone": "UTC−8",
"Population": 90401,
"State": "California"
}
I know that I have to use aggregations but don't know how. The documentation lacks a clear example of how to do that at the same collection.
Thanks in advance.

You need to group documents by Name and State fields and merge them.
Try this one:
db.Cities.aggregate([
{
$group: {
_id: {
"Name": "$Name",
"State": "$State"
},
data: {
$push: "$$ROOT"
}
}
},
{
"$replaceWith": {
"$mergeObjects": {
"$reverseArray": "$data"
}
}
}
])
MongoPlayground
Note: $reverseArray allows flip grouped array

Related

Rename key in a mongoDB array of object

I need to rename the key "street" to "block" in every object of the "address" array in a mongo document. The structure of the document is as follows,
{
"_id": 1234,
"name": "Jack",
"address": [
{
"no": 1,
"street": "streetx",
"country": "countryx"
},
{
"no": 1,
"street": "streety",
"country": "countryy"
}
]
}
Note : The mongoDB version is '4.0.0'
You can use $map to assign the new field to the address objects. For MongoDB v4.0, use javascript to iterate and update back to the collection.
db.collection.aggregate([
{
"$addFields": {
"address": {
"$map": {
"input": "$address",
"as": "a",
"in": {
"$mergeObjects": [
"$$a",
{
"no": "$$a.no",
"country": "$$a.country",
"block": "$$a.street"
}
]
}
}
}
}
}
]).forEach(function(doc){
db.collection.save(doc);
})
Here is the Mongo playground for your reference.

Add field to mongo that contain the value of the document's length

Sample document
[{
"_id": "1111",
"name": "Dani"
},
{
"_id": "2222",
"name": "Guya",
"address": "Arlozorov",
"city": "Tel Aviv"
}]
Expected output, i want to add the length field
[{
"_id": "1111",
"name": "Dani",
"length": 2
},
{
"_id": "2222",
"name": "Guya",
"address": "Arlozorov",
"city": "Tel Aviv",
"length": 4
}]
Query
$$ROOT is the document (system variable)
convert it to an array
take the array length and $set
Playmongo
aggregate(
[{"$set": {"length": {"$size": {"$objectToArray": "$$ROOT"}}}}])
There's a fair amount of ambiguity in your description of "length". For example, if there is an array, does that count as one, or should the array contents be counted too? Same for embedded/nested fields documents, etc. And should the ever present "_id" field be counted?
Anyway, given your example documents and desired output, here's one way you could update each document with your "length" field.
db.collection.update({},
[
{
"$set": {
"length": {
"$subtract": [
{
"$size": {
"$objectToArray": "$$ROOT"
}
},
1
]
}
}
}
],
{
"multi": true
})
Try it on mongoplayground.net.

MongoDB query double nested array with matching sets

Being not familiar with Mongo and still progressing I came accross a problem I can't think to fix (and I don't know if it's feasible)
I have an Event document whose structure looks like this:
{
"_id": "6138451fb3a7d9564a0229fd"
"title": "Event 1",
"cohortsGroups": [
[
{
"_id": "6124beef59d728c82088fd59",
"name": "2022",
"type": "promotion"
},
{
"_id": "6124bf2159d728c82088fd60",
"name": "Toronto",
"type": "city"
}
],
[
{
"_id": "6124beef59d728c82088fd57",
"name": "2024",
"type": "promotion"
},
{
"_id": "6124bf2159d728c82088fd68",
"name": "Tokyo",
"type": "city"
}
],
]
},
{
"_id": "6138451fb3a7d9564a0229fe"
"title": "Event 2",
"cohortsGroups": [
[
{
"_id": "6124beef59d728c82088fd59",
"name": "2022",
"type": "promotion"
}
]
]
},
{
"_id": "6138451fb3a7d9564a0229fh"
"title": "Event 3",
"cohortsGroups": [
[
{
"_id": "6124beef59d728c82088fd21",
"name": "2022",
"type": "promotion"
},
{
"_id": "6124beef59d728c82088fd43",
"name": "Amsterdam",
"type": "city"
}
]
]
}
As you can see the field cohortsGroups is a double array of Objects. I would like to retrieve those events based on my user's object who possess also an array (simple) of cohorts
So for example let's say my user looks like this:
{
"firstName": "John",
"lastName": "Doe",
"cohortsRef": [
{
"_id": "6124beef59d728c82088fd59",
"name": "2022",
"type": "promotion"
},
{
"_id": "6124bf2159d728c82088fd60",
"name": "Toronto",
"type": "city"
}
]
}
To make it simple I would like to retrieve an event only if one of the set of cohorts in in his cohortsGroups have his cohorts all presents in the user object.
Taking that in mind and the above example I would only be able to retrive Event 1 and Event 2.
I can't retrieve Event 3 because even tho I have the Cohort 2022 in my user, it's paired with the Cohort Amsterdam which is not present in my user's cohorts, (eq: none of the cohort subarrays sets have their values entirely present in my user's cohorts).
I really hope someone can give me a hand on that problem, so far I've tried to map all the user's cohorts ID in an array and query by $elemMatch
Events.find({ cohortsGroups: { $elemMatch: { $elemMatch: { _id: { $in: [ '6124beef59d728c82088fd59', '6124bf2159d728c82088fd60' ] } } } } })
But this solution just retrieves every event that have a subarray cohort matching, it doesn't take into account the sets, so in this case it would also retrieve Event 3 - because 2022 is present - but it's paired with the cohort Amsterdam which is wrong.
Let me know if I wasn't clear enough, any help would be appreciated. At least to know if it's something doable as a mongo query.
Thanks a lot for reading !
This should work:
db.events.find({ cohortsGroups: { $elemMatch: { $not: {$elemMatch: { _id: { $nin: [ '6124beef59d728c82088fd59', '6124bf2159d728c82088fd60' ] } } } } } })
The outer $elemMatch, matches any document where any array in cohortsGroups matches the inner condition.
The inner condition
{ $not: {$elemMatch: { _id: { $nin: [ '6124beef59d728c82088fd59', '6124bf2159d728c82088fd60' ] } } } } }
is using a double negative with $not and $nin to find subarrays that have no element with an _id except those that you are searching for.

How to find document that contains array with two equal values?

I have chats's collection with participants array
[{
"_id": ObjectId("5d12b2a10507cfe0bad6d93c"),
"participants": [{
"_id": ObjectId("5ce4af580507cfe0ba1c6f5b"),
"firstname": "John",
"lastname": "Anderson",
"icon": "/assets/images/avatars/small/2.jpg"
},
{
"_id": ObjectId("5ce4af580507cfe0ba1c6f5b"),
"firstname": "John",
"lastname": "Anderson",
"icon": "/assets/images/avatars/small/2.jpg"
}
]
}, {
"_id": ObjectId("5d1124a50507cfe0baba7909"),
"participants": [{
"_id": ObjectId("5ce4af580507cfe0ba1c6f5b"),
"firstname": "John",
"lastname": "Anderson",
"icon": "/assets/images/avatars/small/2.jpg"
},
{
"_id": ObjectId("5ce54cb80507cfe0ba25d74b"),
"firstname": "Milosh",
"lastname": "Jersi",
"icon": "/assets/images/avatars/small/3.jpg"
}
]
}]
I fetch it by
req.db.collection('chats').findOne({'participants._id': {$all: [req.userID, new mongo.ObjectID(req.params.to)]}});
where userID is also ObjectID and equals.
Usually it have different participants, but our user can also send messages to itself, is allowed option in many social networks.
So in this situation, our user "John Anderson" sent message to himself and we inserted chat document for it.
And now i have problem, how to get document with equal array values
{'participants._id': { '$all': [ 5ce4af580507cfe0ba1c6f5b, 5ce4af580507cfe0ba1c6f5b] }}
// return every chat contains our id in atleast one field, but we need both to be equal
// same for $in
{'participants._id': { '$eq': [ 5ce4af580507cfe0ba1c6f5b, 5ce4af580507cfe0ba1c6f5b] }}
// return nothing
what else can I do ?
you can achieve this with the aggregation framework, using a $group stage. First, group by chat._id and use $addToSet to keep only unique users in a new array, ant then add a filter to keep only the documents with one participant:
db.collection.aggregate([
{
"$unwind": "$participants"
},
{
"$group": {
"_id": "$_id",
"participants": {
"$addToSet": "$participants._id"
}
}
},
{
"$match": {
"participants": {
"$size": 1
}
}
}
])
result:
[
{
"_id": ObjectId("5d12b2a10507cfe0bad6d93c"),
"participants": [
ObjectId("5ce4af580507cfe0ba1c6f5b")
]
}
]
you can try it online: mongoplayground.net/p/plB-gsNIxRd

Mongodb aggregate and return multiple document value

Assuming I have the following JSON structure I want to group by gender and want to return multiple document values on in the same field:
[
{
"id": 0,
"age": 40,
"name": "Tony Bond",
"gender": "male"
},
{
"id": 1,
"age": 30,
"name": "Nikki Douglas",
"gender": "female"
},
{
"id": 2,
"age": 23,
"name": "Kasey Cardenas",
"gender": "female"
},
{
"id": 3,
"age": 25,
"name": "Latasha Burt",
"gender": "female"
}
]
Now I know I can do something like this but I need to join both age and name into one field
.aggregate().group({ _id:'$gender', age: { $addToSet: "$age" }, name: { $addToSet: "$name"}})
Yes you can, just have a sub-document as the argument:
db.collection.aggregate([
{ "$group": {
"_id": "$gender",
"person": { "$addToSet": { "name": "$name", "age": "$age" } }
}}
])
Of course if you actually expect no duplicates here the $push does the same thing:
db.collection.aggregate([
{ "$group": {
"_id": "$gender",
"person": { "$push": { "name": "$name", "age": "$age" } }
}}
])
In addition to what Neil mentioned for the aggregation query, you have to consider the fact that the document that contains the grouped individual record cannot be beyond 16 MB (in v2.5+) or the entire aggregate result cannot be more than 16MB (on v2.4 and below).
So in case you have huge data set, you have to be aware of limitations that may impose on the model you use to aggregate data.