Get Total numbers of each element in Array of MongoDB Object - arrays

I have a set of objects in a MongoDB. The object includes an array of types. Now I am connecting to the DB with Mongoose and would like to now the number of objects for each Type.
For example my objects look like
{
"name": "abc",
"tags": ["a","b","c"]
}
Now I would like to get the total number of Objects which for example have the Tag "a"
I am connecting to the MongoDB with a NodeJS Backend using Mongoose
Thanks for your Ideas on how to query this efficiently.
Addition:
In the case where I don't know what kind of different tags are existing in the different objects and I would like to get an overview of all tags, how do I need to query on this one? For better understanding I give an example.
Two objects in the database:
{
"name": "abc",
"tags": ["a","b","c"]
},
{
"name": "abc",
"tags": ["a","b"]
}
the function/query should give a response of something like this:
{
"a": 2,
"b": 2,
"c": 1
}

collection.aggregate([
{$unwind: "$tags" },
{$group: {
_id: "$tags",
count: {$sum : 1}
}},
]);
this will give output like this:
/* 1 */
{
"_id" : "c",
"count" : 2
}
/* 2 */
{
"_id" : "b",
"count" : 3
}
/* 3 */
{
"_id" : "a",
"count" : 2
}

Use count which is a collection method. It returns the count only instead of all documents. If You need the documents , replace count with find
collection.count({tags:"a"})

Related

How to project a specific index inside a multilevel nested array in mongodb

I have a particular field in my document which has a multilevel nested array structure. The document looks like something this
{
"_id" : ObjectId("62171b4207476091a17f595f"),
"data" : [
{
"id" : "1",
"content" : [
{
"id" : "1.1",
"content" : []
},
{
"id" : "1.2",
"content" : [
{
"id" : "1.2.1",
"content" : [
{
"id" : "1.2.1.1",
"content" : []
}
]
},
{
"id" : "1.2.2",
"content" : []
}
]
}
]
}
]
}
(The ids in my actual data is a random string, I have added a more defined id here just for readability)
In my application code the nesting level is controlled so it won't go more than 5 levels deep.
I need to project a particular object inside one of the deeply nested arrays.
I have all the information needed to traverse the nested structure. For example if I need to fetch the object with id "1.2.2" my input will look something like this:
[{id: 1, index: 0}, {id: 1.2, index: 1}, {id: 1.2.2, index: 1}]
In the above array, each element represents one level of nesting. I have the Id and the index. So in the above example, I know I first need to travel to index 0 at the top level, then inside that to index 1 , then inside that to index 1 again to find my object.
Is there a way I can only get the inner object that I want directly using a query. Or will I need to get the whole "data" field and do the traversal in my application code. I have been unable to figure out any way to construct a query that would satisfy my need.
Query
if you know the path, you can do it using a series of nested
$getField
$arrayElemAt
you can do it in one stage with nested calls, or with many new fields like i did bellow, or with mongodb variables
*i am not sure what output you need, this goes inside to get the 2 using the indexes (if this is not what you need add if you can the expected output)
Test code here
Data
[
{
"_id": ObjectId( "62171b4207476091a17f595f"),
"data": [
{
"id": "1",
"content": [
{
"id": "1.1",
"content": []
},
{
"id": "1.2",
"content": [
{
"id": "1.2.1",
"content": [
{
"id": "1.2.1.1",
"content": []
}
]
},
{
"id": "1.2.2",
"content": [1,2]
}
]
}
]
}
]
}
]
Query
aggregate(
[{"$set":
{"c1":
{"$getField":
{"field":"content", "input":{"$arrayElemAt":["$data", 0]}}}}},
{"$set":
{"c2":
{"$getField":
{"field":"content", "input":{"$arrayElemAt":["$c1", 1]}}}}},
{"$set":
{"c3":
{"$getField":
{"field":"content", "input":{"$arrayElemAt":["$c2", 1]}}}}},
{"$project":{"_id":0, "c4":{"$arrayElemAt":["$c3", 1]}}}])
Results
[{
"c4": 2
}]

Which is the efficient way of updating an element in MongoDB?

I have a collection like below
{
"doc_id": "1234",
"items": [
{
"item_no": 1,
"item": "car",
},
{
"item_no": 2,
"item": "bus",
},
{
"item_no": 3,
"item": "truck",
}
]
},
I need to update an element inside items list based on a search criteria. My search criteria is, if "item_no" is 3, "item" should be updated to "aeroplane".
I have written the following two approaches in Python to solve this.
Approach 1:
cursor = list(collection.find({"doc_id": 1234}))
for doc in cursor:
if "items" in doc:
temp = deepcopy(doc["items"])
for element in doc["items"]:
if ("item_no" and "item") in element:
if element["item_no"] == 3:
temp[temp.index(element)]["item"] = "aeroplane"
collection.update_one({"doc_id": 1234},
{"$set": {"items": temp}})
Approach 2:
cursor = list(collection.find({"doc_id": 1234}))
for doc in cursor:
if "items" in doc:
collection.find_one_and_update({"doc_id": 1234}, {'$set': {'items.$[elem]': {"item_no": 3, "item": "aeroplane"}}}, array_filters=[{'elem.item_no': {"$eq": 3}}])
Among the above two approaches, which one is better in terms of time complexity?
Use only a query and avoid loops:
db.collection.update({
"doc_id": "1234",
"items.item_no": 3
},
{
"$set": {
"items.$.item": "aeroplane"
}
})
Example here
Note how using "items.item_no": 3 into the find stage you can use $ into update stage to refer the object into the array.
So, doing
{
"doc_id": "1234",
"items.item_no": 3
}
When you use $ you are telling mongo: "Ok, do your action in the object where the condition is match" (i.e., the object in the collection with doc_id: "1234" and an array with items.item_no: 3)
Also if you want to update more than one document you can use multi:true like this example.
Edit: It seems you are using pymongo so you can use multi=True (insted of multi: true) or a cleaner way, using update_many.
collection.update_many( /* your query here */ )

Trying to filter values of an array within an array in MongoDB

I am new to MongoDB and I'm trying to filter values of an array within an array. An example of the schema is below. The schema is basically a dump of a 3 tiered Dictionary with a simple object of scalars as the leaf node.
The "I" member contains an array of documents (outer array) of key-value pairs with a string key (k), and the value (v) is an array of documents (middle array) of key-value pairs with a date as the key and value is another dictionary, which isn't part of this question.
Basically, what I need to do is retrieve the most recent data from the middle array (Date, key-value) for a given value of the outer array (string, key-value).
(Collection Sample)
{
"_id" : ObjectId("5eacfbe62758834aefdec003"),
"UserId" : UUID("46942978-29f4-4521-9932-840cead6743e"),
"Data" : {
"I" : [
{
"k" : "LRI39",
"v" : [
{
"k" : ISODate("2020-03-11T20:24:41.591Z"),
"v" : [
{
"k" : ISODate("2020-03-11T20:24:41.594Z"),
"v" : {
"Source" : 1,
"Value" : 19
}
}
]
},
{
"k" : ISODate("2020-01-22T11:37:23.393Z"),
"v" : [
{
"k" : ISODate("2020-01-22T11:37:23.412Z"),
"v" : {
"Source" : 1,
"Value" : 20
}
}
]
}
]
},
...
]
}
}
I have been able to generate a document which is basically what you see from "Data" to the end of the sample, being the entire record for LRI39, using:
db.threetier.aggregate([
{
$project: {
"Data.I": {
$filter: {
input: "$Data.I",
as: "item",
cond: {
$eq: [ "$$item.k", "LRI39" ]
}
}
}
}
}
])
However, no matter what I do, I cannot seem to return any subset of the records of the middle array: I get the 2020-03-11 and 2020-01-22 elements or I get nothing.
I have tried adding stages like the below to the projection above, figuring that I would get 1 record (the 2020-01-22 record) but I get both. If I change the date to be in 2019, I get nothing (as expected).
$project: {
"Data.I.v": {
$filter: {
input: "$Data.I.v",
as: "stamp",
cond: { $lt: [ "$$stamp.k", ISODate("2020-02-14T00:00:00Z") ] }
}
}
}
I have also tried:
{ $match: { $expr: { $lt: [ "Data.I.v.k", ISODate("2020-02-14T00:00:00Z") ] } } }
but that returns no results at all (probably because $match works on documents not arrays) as well as trying to unwind the array using $unwind: "$Data.I.v" before the match, but that returns nothing as well.
It seems like I am missing something fundamental here. I do realize that Mongo is designed (I think) to have those array items as documents, but I do want to see if this will work as is.
You will need to unwind both Data.I and Data.I.v, so that you can consider each of the sub-elements separately.
Then reverse sort by the date field.
Group by the _id and key, selecting only the first document in each group.
Finally, replaceRoot so the return is just the selected document.
db.collection.aggregate([
{$unwind: "$Data.I"},
{$unwind: "$Data.I.v"},
{$sort: {"Data.I.v.k": -1}},
{$group: {
_id: {
_id: "$_id",
key: "$Data.I.k"
},
document: {$first: "$$ROOT"}
}},
{$replaceRoot: {newRoot: "$document"}}
])
Playground

How to create an array or embedded document from three existing fields in my collection in MongoDB

First of all, I am new to MongoDB, so this question may be quite simple to answer for some of you. I am trying to create an array or embedded document that includes existing fields from my collection. In order words, let's say I have a collection with the fields "borough", "street" and "zipcode". I would like to create an embedded document named, for example, "Location" and move to that document these three fields. Is this possible? The following is one of the many different ways I tried to achieve this:
db.cooking.aggregate([{$set:{"borough":"$borough", "street":"$street", "zipcode":"$zipcode"}},{$out:"Location"}])
Where I would then use the db.Location.copyTo(cooking) expression to add the data from the newly created aggregate collection "Location" to the main collection "cooking". Of course I would have to remove the existing three fields from the cooking collection since I have the same information in the embedded document Location to avoid having duplicate data.
You can create an embedded document with the existing fields, using one of the approaches:
Assume you have a collection with the documents like this:
{ _id: 1, fld1: 12, fld2: "red" },
{ _id: 2, fld1: 9, fld2: "blue" },
{ _id: 3, fld1: 34, fld2: "green" }
and you want to create an embedded document named location with the fields fld1 and fld2; the following aggregation will do that:
db.test.aggregate( [
{ $project: { "location.fld1": "$fld1", "location.fld2": "$fld2" } },
{ $out: "test" }
] )
Note that the original collection test will be overwritten and will be like this:
{ "_id" : 1, "location" : { "fld1" : 12, "fld2" : "red" } }
{ "_id" : 2, "location" : { "fld1" : 9, "fld2" : "blue" } }
{ "_id" : 3, "location" : { "fld1" : 34, "fld2" : "green" } }
The second approach:
This requires that you are using the MongoDB version 4.2. This update query just modifies the existing documents in the same collection (with the same result as above):
db.test.updateMany(
{ },
[
{ $set: { "location.fld1": "$fld1", "location.fld2": "$fld2" } },
{ $unset: [ "fld1", "fld2" ] }
]
)

How to find all documents with all specified array values in it in mongoDB?

This is my collection
{
"_id" : ...,
"name" : "sport",
}
{
"_id" : ...,
"name" : "art",
}
{
"_id" : ...,
"name" : "cars",
}
This is an array I have got ["cars","sport"]
I just want to make sure that I have got cars and sport in my collection, if not(or just one doesn't exist) I would like to receive nothing.
I'm looking for a array-friendly like query like $in but and mode.
Is this what you are looking ..??
db.colllection.find( {$and: [ {"name": {$in: ["cars"]}},
{"name": {$in: ["sports"]}}
]
})
following query should help you achieve this
//change test with your collection name
db.getCollection('test').aggregate(
{
$group: {
_id: "collectionOfNames",
"names": {$addToSet: "$name"}
}
},
{
$match: {"names": {$all: ["cars", "sport"]}}
}
)
We use $group to create an array "names" of the property name across all documents.
Then using $match and $all we check to make sure that the array "names" contains all of the elements from our query array.
Is this what you are looking for?

Resources