MongoDB: How to count number of values in key - arrays

I'm very new to MongoDB and I need help figuring out how to perform aggregation on a key in MongoDB and use that result to return matches.
For example, if I have a collection called Fruits with the following documents:
{
"id": 1,
"name": "apple",
"type": [
"Granny smith",
"Fuji"
]
}, {
"id": 2,
"name": "grape",
"type": [
"green",
"black"
]
}, {
"id": 3,
"name": "orange",
"type": [
"navel"
]
}
How do I write a query that will return the names of the fruits with 2 types, ie apple and grape?
Thanks!

Demo - https://mongoplayground.net/p/ke3VJIErhvb
use $size to get records with 2 number of type
https://docs.mongodb.com/manual/reference/method/db.collection.find/#mongodb-method-db.collection.find
The $size operator matches any array with the number of elements specified by the argument. For example:
db.collection.find({
type: { "$size": 2 } // match document with type having size 2
},
{ name: 1 } // projection to get name and _id only
)

To get the length of the array you should use $size operator in $project pipeline stage
So the pipeline $project stage should look like this
{
"$project": {
"name": "$name",
type: {
"$size": "$type"
}
}
}
Here is an working example of the same ⇒ https://mongoplayground.net/p/BmS9BGhqsFg

Related

PyMongo - How to compare the given array exactly matches with the document

I have a MongoDB document with the following attributes:
{
"label": [
"ibc",
"ibd",
"ibe"
],
"location": "vochelle st"
}
and I have to return the document only if the documents label exactly matches the given array i.e., ["ibc","ibd"] and for the same, I am using the query:
db.collection.find({"location":"vochelle st","dock_label":{"$all":["ibc", "ibd"]}})
Actual Response:
{
"label": [
"ibc",
"ibd",
"ibe"
],
"location": "vochelle st"
}
Expected Response:
{}
Since the label "ibe" doesn't exist in the given array, the expected result has to be the empty dictionary.
Give $size in your query
db.collection.find({
location: "vochelle st",
label: {
$all: [
"ibc",
"ibd"
],
$size: 2
}
})
mongoplayground
Use $setIntersection to intersect both label and input array.
Compare both intersected array (from 1) and label arrays are matched via $eq.
db.collection.find({
"location": "vochelle st",
$expr: {
$eq: [
{
$setIntersection: [
"$label",
[
"ibc",
"ibd"
]
]
},
"$label"
]
}
})
Sample Mongo Playground
If you want to check if the array exactly matches your input, you don't need any operator, just compare it with your value:
db.collection.find({"location":"vochelle st","label": ["ibc", "ibd"]})

How to access nested array of objects in mongodb aggregation pipeline?

I have a document like this(this is the result after few pipeline stages)
[
{
"_id": ObjectId("5e9d5785e4c8343bb2b455cc"),
"name": "Jenny Adams",
"report": [
{ "category":"Beauty", "status":"submitted", "submitted_on": [{"_id": "xyz", "timestamp":"2022-02-23T06:10:05.832+00:00"}, {"_id": "abc", "timestamp":"2021-03-23T06:10:05.832+00:00"}] },
{ "category":"Kitchen", "status":"submitted", "submitted_on": [{"_id": "mnp", "timestamp":"2022-05-08T06:10:06.432+00:00"}] }
]
},
{
"_id": ObjectId("5e9d5785e4c8343bb2b455db"),
"name": "Mathew Smith",
"report": [
{ "category":"Household", "status":"submitted", "submitted_on": [{"_id": "123", "timestamp":"2022-02-23T06:10:05.832+00:00"}, {"_id": "345", "timestamp":"2021-03-23T06:10:05.832+00:00"}] },
{ "category":"Garden", "status":"submitted", "submitted_on": [{"_id": "567", "timestamp":"2022-05-08T06:10:06.432+00:00"}] },
{ "category":"BakingNeeds", "status":"submitted", "submitted_on": [{"_id": "891", "timestamp":"2022-05-08T06:10:06.432+00:00"}] }
]
}
]
I have user input for time period -
from - 2021-02-23T06:10:05.832+00:00
to - 2022-02-23T06:10:05.832+00:00
Now I wanted to filter the objects from the report which lie in a certain range of time, I want to only keep the object if the "submitted_on[-1]["timestamp"]" is in range of from and to date timestamp.
I am struggling with accessing the timestamp because of the nesting
I tried this
$project: {
"name": 1,
"report": {
"category": 1,
"status": 1,
"submitted_on": 1,
"timestamp": {
$arrayElemAt: ["$report.cataloger_submitted_on", -1]
}
}
}
But this gets the last object of the report array {"_id": "bcd", "timestamp":"2022-05-08T06:10:06.432+00:00"} for all the items inside the report. How can I do this to select the last timestamp of each obj.
You can replace your phase in the aggregation pipeline with two phases: $unwind and $addFields in order to get what I think you want:
{
$unwind: "$report"
},
{
"$addFields": {
"timestamp": {
$arrayElemAt: [
"$report.submitted_on",
-1
]
}
}
},
The $unwind phase is breaking the external array into documents since you want to perform an action on each one of them. See the playground here with your example. If you plan to continue the aggregation pipeline with more steps, you can probably skip the $addFields phase and include the condition inside your next $match phase.

MongoDB - Match all document not containing a combination of value

Let's say I have three document structured like so :
{
"_id": 1,
"conditions": [
["Apple", "Orange"],
["Lemon"],
["Strawberry"]
]
},
{
"_id": 2,
"conditions": [
["Apple"],
["Strawberry"]
]
},
{
"_id": 3,
"conditions": [
["Apple", "Lime"]
]
}
And I have an array, I'll call it ARC for this example :
ARC = [
"Apple",
"Lime",
"Banana",
"Avocado",
"Cherry"
]
I would like to return all document in which all conditions subarray values can't be found in the ARC array.
For example, with the data above, the first document should be returned because :
The Apple AND Orange combination is not in the ARC array
Lemon is not in the ARC array
Strawberry is not in the ARC array
The second document shouldn't be returned because :
Apple is in the ARC array
And the third document shouldn't be returned because :
The Apple AND Lime combination is in the ARC array
I've tried
db.example.find({"conditions": {$not: {$elemMatch: {$all: [ARC]}}}})
But it seems way too simple.. So, as expected, it doesn't work.
I know mongoDB is pretty powerful with all the aggregation and stuff but I'm a bit lost.
Do you know if it's possible with a query alone and if so, what should I look for ?
The query below should solve your problem.
var ARC = [
"Apple",
"Lime",
"Banana",
"Avocado",
"Cherry"
];
db.test.find(
{ $expr: {
$eq: [
{ $filter: { input: "$conditions", as: "c", cond: { $setIsSubset: [ "$$c", ARC] } } },
[ ]
]
}
}
)
It's made up of lots of parts so I'll try to break it down a bit, The first part is $expr within a find (or can be used within a $match in an aggregation) this allows us aggregation expressions within the query. So this allows us to use a $filter.
The $filter expression allows us to filter down the arrays in the condition field to check if any are a subset of the array ARC passed in.
We can actually take that filter an execute it on its own using an aggregation query:
db.test.aggregate([
{ $project: {
"example" : { $filter: { input: "$conditions", as: "c", cond: { $setIsSubset: [ "$$c", ARC] } } }
} }])
{ "_id" : 1, "example" : [ ] }
{ "_id" : 2, "example" : [ [ "Apple" ] ] }
{ "_id" : 3, "example" : [ [ "Apple", "Lime" ] ] }
The last part of the query is the $eq which is taking the value that is created with the filter and then matching it against an empty array [ ].
This is an aggregation approach. You should use $setIsSubset.
Below should be helpful:
db.collection.aggregate([
{
$match: {
$expr: {
$eq: [
true,
{
$allElementsTrue: {
$map: {
input: "$conditions",
as: "c",
in: {
$not: {
$setIsSubset: [
"$$c",
[
"Apple",
"Lime",
"Banana",
"Avocado",
"Cherry"
]
]
}
}
}
}
}
]
}
}
}
])
MongoPlayGroundLink

Return one array of data in sub-document of Mongodb

I'm using Nodejs with Mongoose package.
Given I've something like this:-
let people = [
{
"_id": 1,
"name": "Person 1",
"pets": [
{
"_id": 1,
"name": "Tom",
"category": "cat"
},
{
"_id": 2,
"name": "Jerry",
"category": "mouse"
}
]
}
]
I want to get only the data of Jerry in pets array using it's _id (result shown below)
{
"_id": 2,
"name": "Jerry",
"category": "mouse"
}
Can I get it without needing to specify the _id of person 1 when using $elemMatch? Right now I code like this:-
const pet = People.find(
{ "_id": "1"}, // specifying 'person 1 _id' first
{ pets: { $elemMatch: { _id: 2 } } } // using 'elemMatch' to get 'pet' with '_id' of '2'
)
And it gave me what I want like I've shown you above. But is there any other way I can do this without needing to specify the _id of it's parent first (in this case, the _id of the people array)
Assuming nested array's _id's are unique you can filter by nested array elements directly:
const pet = People.find(
{ "pets._id": 2 },
{ pets: { $elemMatch: { _id: 2 } } }
)

Find array in array data in MongoDB

I want find in this document groups:
"document": {
"groups": [
{
"id": "5ccd5f7f34f82b0e3315b2f6"
},
{
"id": "73b43unbfkfmdmddfdf84jjk"
}
]
}
are contains some of my query array groups ID:
[ '5ccd5f7f34f82b0e3315b2f6',
'5cdeded7ace07216f5873b5d',
'5cdee5d114edac2cc00bb333' ]
A simple find query suffices:
db.collection.find({ 'groups.id' : {$in : [ '5ccd5f7f34f82b0e3315b2f6',
'5cdeded7ace07216f5873b5d',
'5cdee5d114edac2cc00bb333' ] }})

Resources