MongoDB Array Filters - mongodb-query

I am trying to update an nested array using Array filters, for hands on first i am trying with basic array filter update query, i copied pasted the update query from the mongodb tutorial, But i am getting error like: Error:"No array filter found for identifier 'elem' in path 'grades.$[elem].mean'
"and i am using
'db version v4.0.2' and
'MongoDB shell version v4.0.2
Here is my collection Details,
{
"_id" : 1,
"grades" : [
{
"grade" : 80,
"mean" : 75,
"std" : 6
},
{
"grade" : 85,
"mean" : 90,
"std" : 4
},
{
"grade" : 85,
"mean" : 85,
"std" : 6
}
]
}
//End of First Record
{
"_id" : 2,
"grades" : [
{
"grade" : 90,
"mean" : 75,
"std" : 6
},
{
"grade" : 87,
"mean" : 90,
"std" : 3
},
{
"grade" : 85,
"mean" : 85,
"std" : 4
}
]
}
//End of Second record
update Query:
db.getCollection('students2').update(
{ },
{ $set: { "grades.$[elem].mean" : 100 } },
{
multi: true,
arrayFilters: [ { "elem.grade": { $gte: 85 } } ]
}
)
Throw's the Error:
No array filter found for identifier 'elem' in path 'grades.$[elem].mean'

Read the comments of this StackOverflow issue:
arrayFilters not working
It doesn't work in "older shells". I'm using the Robo 3T client and running into the same issue. The shell is apparently removing the arrayFilters object.

Related

Mongo Group By query

I have data stored in a Mongo collection that is structured like this:
{
"numberAtPending" : 3,
"numberAtInProgress" : 5,
"numberAtCancelled" : 1,
"numberAtShipped" : 50,
"timeOfRequest" : ISODate("2022-01-10T12:52:15.813Z"),
"requestingSupplier" : "SUPPLIER_1",
},
{
"numberAtPending" : 5,
"numberAtInProgress" : 3,
"numberAtCancelled" : 4,
"numberAtShipped" : 35,
"timeOfRequest" : ISODate("2022-01-15T09:11:02.992Z"),
"requestingSupplier" : "SUPPLIER_1",
},
{
"numberAtPending" : 12,
"numberAtInProgress" : 3,
"numberAtCancelled" : 1,
"numberAtShipped" : 21,
"timeOfRequest" : ISODate("2022-01-10T14:21:55.221Z"),
"requestingSupplier" : "SUPPLIER_2",
}
I wish to construct a query that would let me sum up each count in each entry and group by requestingSupplier.
For example, I would like to answer the question, for the month of January '22, what was the sum of each entity and get a response similar to:-
"TotalNumberAtPending": 300
"TotalNumberAtInProgress" : 150,
"TotalNumberAtCancelled" : 70,
"TotalNumberAtShipped" : 400
"Supplier" : "SUPPLIER_1",
"TotalNumberAtPending": 230
"TotalNumberAtInProgress" : 110,
"TotalNumberAtCancelled" : 40,
"TotalNumberAtShipped" : 300
"Supplier" : "SUPPLIER_2",
Any help most appreciated!
thanks and regards
You can try this query (also I'm assuming the output you show is an example and not the real values because I don't know from where can you get 300, 150, 400...)
So, try this:
You have two options to match values in the range of two dates. If you want to input the name of the month and the year you can try something like:
Use $expr and $eq with $year and $month. And then you can use as input exaclty the number of the desired month or year.
{
"$match": {
"$expr": {
"$and": [
{
"$eq": [
{
"$month": "$timeOfRequest"
},
1
]
},
{
"$eq": [
{
"$year": "$timeOfRequest"
},
2022
]
}
]
}
}
}
Or you can match by the date range. If you want to get all documents from 01-2022 you can use this $match stage where the range is from the first second of January (equal) to the first second of February (not equal, so i.e. is the last second of January).
{
"$match": {
"timeOfRequest": {
"$gte": ISODate("2022-01-01T00:00:00Z"),
"$lt": ISODate("2022-02-01T00:00:00Z")
}
}
}
So, with the filter done you only need to use $group like this to generate the desired fields values.
{
"$group": {
"_id": "$requestingSupplier",
"TotalNumberAtPending": {
"$sum": "$numberAtPending"
},
"TotalNumberAtInProgress": {
"$sum": "$numberAtInProgress"
},
"TotalNumberAtCancelled": {
"$sum": "$numberAtCancelled"
},
"TotalNumberAtShipped": {
"$sum": "$numberAtShipped"
},
"Supplier": {
"$first": "$requestingSupplier"
}
}
}
Example here and here

How to arrive at the difference in values for minimum and maximum dates for each year in mongodb

How to arrive at the difference in values of a specific column for the records with minimum and maximum dates for each year in a collection using the aggregate pipeline or map reduce in mongodb?
I have the following collection:
/* 1 */
{
"_id" : 1,
"item" : "abc",
"price" : 10,
"quantity" : 2,
"date" : ISODate("2014-01-01T08:00:00.000Z")
},
/* 2 */
{
"_id" : 2,
"item" : "jkl",
"price" : 20,
"quantity" : 1,
"date" : ISODate("2014-02-03T09:00:00.000Z")
},
/* 3 */
{
"_id" : 3,
"item" : "xyz",
"price" : 5,
"quantity" : 5,
"date" : ISODate("2014-02-03T09:05:00.000Z")
},
/* 4 */
{
"_id" : 4,
"item" : "abc",
"price" : 10,
"quantity" : 10,
"date" : ISODate("2014-02-15T08:00:00.000Z")
},
/* 5 */
{
"_id" : 5,
"item" : "xyz",
"price" : 5,
"quantity" : 10,
"date" : ISODate("2014-02-15T09:05:00.000Z")
},
/* 6 */
{
"_id" : 6,
"item" : "abc",
"price" : 10,
"quantity" : 2,
"date" : ISODate("2013-01-01T08:00:00.000Z")
},
/* 7 */
{
"_id" : 7,
"item" : "jkl",
"price" : 20,
"quantity" : 1,
"date" : ISODate("2013-02-03T09:00:00.000Z")
},
/* 8 */
{
"_id" : 8,
"item" : "xyz",
"price" : 5,
"quantity" : 5,
"date" : ISODate("2013-02-03T09:05:00.000Z")
},
/* 9 */
{
"_id" : 9,
"item" : "abc",
"price" : 10,
"quantity" : 10,
"date" : ISODate("2013-02-15T08:00:00.000Z")
},
/* 10 */
{
"_id" : 10,
"item" : "xyz",
"price" : 5,
"quantity" : 10,
"date" : ISODate("2013-02-15T09:05:00.000Z")
},
/* 11 */
{
"_id" : 11,
"item" : "abc",
"price" : 10,
"quantity" : 2,
"date" : ISODate("2012-01-01T08:00:00.000Z")
},
/* 12 */
{
"_id" : 12,
"item" : "jkl",
"price" : 20,
"quantity" : 1,
"date" : ISODate("2012-02-03T09:00:00.000Z")
},
/* 13 */
{
"_id" : 13,
"item" : "xyz",
"price" : 5,
"quantity" : 5,
"date" : ISODate("2012-02-03T09:05:00.000Z")
},
/* 14 */
{
"_id" : 14,
"item" : "abc",
"price" : 10,
"quantity" : 10,
"date" : ISODate("2012-02-15T08:00:00.000Z")
},
/* 15 */
{
"_id" : 15,
"item" : "xyz",
"price" : 5,
"quantity" : 10,
"date" : ISODate("2012-02-15T09:05:00.000Z")
},
I would like the result to take the following form:
{
{"year": 2014}, {"minDtQuantity": 2}, {"maxDtQuantity": 10}, {"quantityDiff": 8},
{"year": 2013}, {"minDtQuantity": 2}, {"maxDtQuantity": 10}, {"quantityDiff": 8},
{"year": 2012}, {"minDtQuantity": 2}, {"maxDtQuantity": 10}, {"quantityDiff": 8},
}
For each year, we need to find the minimum and maximum dates and group them by year and then find the "quantity" values on those dates and then find the difference between the quantities for the min and max dates for each year.
Is that even possible with aggregate pipelines or map-reduce in mongodb?
This can be done using aggregation pipelines by sorting by date, then pushing the quantities into arrays when grouping by the year (Use a the $year operator to extract the year from the date object). The quantities for the minimum and maximum dates in that year are then the first and last values in the array respectively. These can be taken out of the array using $arrayElemAt.
db.collection.aggregate(
[
{
$sort: {
"date": 1
}
},
{
$group: {
"_id": { "$year": "$date" },
"quantityArray": { "$push": "$quantity" },
}
},
{
$project: {
"_id": 0,
"year": "$_id",
"minDtQuantity": { "$arrayElemAt": [ "$quantityArray", 0 ] },
"maxDtQuantity": { "$arrayElemAt": [ { "$reverseArray": "$quantityArray" }, 0 ] },
"quantityDiff": { "$subtract": [
{ "$arrayElemAt": [ { "$reverseArray": "$quantityArray" }, 0 ] },
{ "$arrayElemAt": [ "$quantityArray", 0 ] },
] }
}
},
]
);
This aggregation returns these results on your data:
{
"year" : NumberInt(2014),
"minDtQuantity" : NumberInt(2),
"maxDtQuantity" : NumberInt(10),
"quantityDiff" : NumberInt(-8)
},
{
"year" : NumberInt(2013),
"minDtQuantity" : NumberInt(2),
"maxDtQuantity" : NumberInt(10),
"quantityDiff" : NumberInt(-8)
},
{
"year" : NumberInt(2012),
"minDtQuantity" : NumberInt(2),
"maxDtQuantity" : NumberInt(10),
"quantityDiff" : NumberInt(-8)
}
This is not quite the format you specified. I am not exactly sure what you required, did you need the results returned in one document?

db.find vs db.aggregation to select nested array Object

I'v tried to perform the following query :
db.getCollection('fxh').find({"username": "user1", "pf.acc.accnbr" : 915177},{userid: true, "pf.pfid": true, "pf.acc.accid":true})
and my collection is the following :
{
"_id" : ObjectId("5932fd8f381d4c0a7de21942"),
"userid" : 1496513894,
"username" : "user1",
"email" : "user1#gmail.com",
"fullname" : "User 1",
"pf" : {
"acc" : [
{
"cyc" : [
{
"det" : {
"status" : "New",
"dcycid" : 1496513941
},
"status" : "New",
"name" : "QPT202017_M1",
"cycid" : 1496513940
}
],
"status" : "New",
"accnbr" : 915177,
"accid" : 1496513939
},
{
"cyc" : [
{
"det" : {
"status" : "New",
"dcycid" : 1496552643
},
"status" : "New",
"name" : "QPT202017_S8",
"cycid" : 1496552642
}
],
"status" : "New",
"accnbr" : 73497,
"accid" : 1496552641
}
],
"pfid" : 1496513935,
},
"lastupdate" : ISODate("2017-06-03T18:18:55.080Z"),
"__v" : 0
}
When I execute the query the result is the following :
{
"_id" : ObjectId("5932fd8f381d4c0a7de21942"),
"userid" : 1496513894,
"portfolio" : {
"acc" : [
{
"accid" : 1496513939
},
{
"accid" : 1496552641
}
],
"pfid" : 1496513935
}
}
And my problem is that I need to see only the concerned accid and the result returns the all accid !.
Any idea how just to return the selected accid of accnbr ?
NB : I have also tried to add $ sign at the end of my query , it
selects the right acc but it returns the all objects or I need just
only ONE returned object.
On 6/5/17
I also used the aggregate command instead of find and it get result by using this :
db.getCollection('fxh').aggregate([ { $unwind : "$pf.acc"} , { $match : {"username":"adh1", "pf.acc.accbr": 915177 } }, {$project : {_id:0, accid: "$pf.acc.accid"}}])
But could NOT get a lower level result, when I ran this :
db.getCollection('fxh').aggregate([ { $unwind : "$pf.acc.cyc"} , { $match : {"username":"adh1", "pf.acc.accbr": 915177, "pf.acc.cyc.name": "QPT202017_M1" } }, {$project : {_id:0, cycid: "$pf.acc.cyc.cycid"}}])
Any idea ?
You can try the below aggregation pipeline.
The idea is to $unwind one nested level at a time, starting from the outermost to the innermost.
For each nested level unwinding, you can apply the$match to limit the documents and continue till you have the desired shape.
You can $group it together at the end to get back to the original shape.
db.getCollection('fxh').aggregate([
{ $match : {"username":"adh1"} },
{ $unwind : "$pf.acc"} ,
{ $match : {"pf.acc.accbr": 915177 } },
{ $unwind : "$pf.acc.cyc"},
{ $match : {"pf.acc.cyc.name": "QPT202017_M1" } },
{$project : {_id:0, accid: "$pf.acc.accid", cycid: "$pf.acc.cyc.cycid"}}])

How can i select all(selected) records in a mongo find?

Question: How can i select all(selected) records in a mongo find? It seems to only return one result in RoboMongo even though i think i chose to get back two?
In this case it is a set of "X/Y" so they need to match with $and i would believe unless i would start to merge them into one field. In a classical SQL Query i would probably write something like
select x,y from map_table where (x = 69 and y = 69) or (x = 68 and y = 69)
I believe this would return both rows. Maybe i'm making things too complicated or I've got a mistake in my model and this is not applicable to mongodb.
Background: I'm writing an application with coordinates "X/Y" a classical tile in my DB consists of a object containing these two variables inside. I've got a compound index on it in the same order "X/Y". In the middle of development I've realized that only one record is returned and started to put the query into RoboMongo and check it. The final idea is to select between 20-100 tiles in one go.
db.map.find({"$or" : [{
"$and" : [
{
"X" : 69
},
{
"Y" : 69
}
],
"$and" : [
{
"X" : 68
},
{
"Y" : 69
}]
}]
})
The result of that query is really:
{
"_id" : ObjectId("580aabcd7ad9aa2b404af79f"),
"X" : 68,
"Y" : 69,
"ID" : 4830
}
What i would expect?
{
"_id" : ObjectId("580aabcd7ad9aa2b404af79f"),
"X" : 68,
"Y" : 69,
"ID" : 4830
}
{
"_id" : ObjectId("580aabcd7ad9aa2b404af89f"),
"X" : 69,
"Y" : 69,
"ID" : 4830
}
If the second $and clause is changed to 69,69 it will show up that specific record, i don't know why this is happening maybe i'm missing some crucial information on how to "find" records in mongodb.
If you look carefully, the brackets positioning is incorrect.
Incorrect - 12
db.map.find({
$or: [{
$and: [{}, {}],
$and: [{}, {}]
}]
})
Correct - 14
db.map.find({
$or: [{
$and: [{}, {}]
}, {
$and: [{}, {}]
}]
})
So change your query to
db.map.find({
"$or": [{
"$and": [{
"X": 69
}, {
"Y": 69
}]
}, {
"$and": [{
"X": 68
}, {
"Y": 69
}]
}]
})

Why are items apparently duplicated in my mongoDB database when I use find()?

I am going through the try.mongodb.org tutorial on their website (embedded terminal emulator on the webpage). I am on items t4 and t5 (you type tx for items in the tutorial).
In t4 we populate a database.
> t4. Saving and Querying
> Try adding some documents to the scores collection:
> for(i=0; i<10; i++) { db.scores.save({a: i, exam: 5}) };
>
> Try that, then enter
> db.scores.find();
> to see if the save succeeded. Since the shell only displays 10 results at time,
> you'll need to enter the 'it' command to iterate over the rest.
>
> (enter 'next' when you're ready)
I made exam 5 + i just for fun:
for(i=0; i<10; i++) { db.scores.save({a: i, exam: 5+i}) };
So what is in the database? I type in db.scores.find(); and get the following, which is what I had expected, although the order seems random. Fine.
>
[
{ "exam" : 14, "a" : 9, "_id" : { "$oid" : "52b1d16bcc937439340649c4" } },
{ "exam" : 5, "a" : 0, "_id" : { "$oid" : "52b1d191cc937439340649c5" } },
{ "exam" : 6, "a" : 1, "_id" : { "$oid" : "52b1d191cc937439340649c6" } },
{ "exam" : 7, "a" : 2, "_id" : { "$oid" : "52b1d191cc937439340649c7" } },
{ "exam" : 8, "a" : 3, "_id" : { "$oid" : "52b1d191cc937439340649c8" } },
{ "exam" : 10, "a" : 5, "_id" : { "$oid" : "52b1d191cc937439340649c9" } },
{ "exam" : 9, "a" : 4, "_id" : { "$oid" : "52b1d191cc937439340649ca" } },
{ "exam" : 11, "a" : 6, "_id" : { "$oid" : "52b1d191cc937439340649cb" } },
{ "exam" : 12, "a" : 7, "_id" : { "$oid" : "52b1d191cc937439340649cc" } },
{ "exam" : 13, "a" : 8, "_id" : { "$oid" : "52b1d191cc937439340649cd" } }
]
In t5 we search for items in that database:
>
5. Basic Queries You've already tried a few queries, but let's make them more specific. How about finding all documents where a == 2:
db.scores.find({a: 2});
Or what about documents where a > 15? db.scores.find({a: {'$gt': 15}});
The a== 2 search worked, but the > 15 one did not. First of all, based on item t4, there should be no entry for a greater than 15.
So I try greater than 6: db.scores.find({a: {'$gt': 6}});
And I get the following output, which is really surprising to me since there should only be 3 entries for a == 7, a == 8, and a == 9.
>
[
{ "exam" : 14, "a" : 9, "_id" : { "$oid" : "52b1d16bcc937439340649c4" } },
{ "exam" : 12, "a" : 7, "_id" : { "$oid" : "52b1d191cc937439340649cc" } },
{ "exam" : 13, "a" : 8, "_id" : { "$oid" : "52b1d191cc937439340649cd" } },
{ "exam" : 14, "a" : 9, "_id" : { "$oid" : "52b1d191cc937439340649ce" } },
{ "exam" : 12, "a" : 7, "_id" : { "$oid" : "52b1d1a8cc937439340649d6" } },
{ "exam" : 13, "a" : 8, "_id" : { "$oid" : "52b1d1a8cc937439340649d7" } },
{ "exam" : 14, "a" : 9, "_id" : { "$oid" : "52b1d1a8cc937439340649d8" } },
{ "exam" : 5, "a" : 7, "_id" : { "$oid" : "52b1d49fcc937439340649f1" } },
{ "exam" : 5, "a" : 9, "_id" : { "$oid" : "52b1d49fcc937439340649f3" } },
{ "exam" : 5, "a" : 8, "_id" : { "$oid" : "52b1d49fcc937439340649f4" } }
]
If you look at the initially outputted db.scores.find() id's on the right, the last character goes up with each entry -- 4, 5, 6, 7, 8, 9, a, b, c, d. But in the duplicated entries, take a look at the entries for a == 9. We have one ending in 4, one ending in e, and one ending in 3. It seems like in the brains of the operation the database has 30 entries, not 10.
{ "exam" : 14, "a" : 9, "_id" : { "$oid" : "52b1d16bcc937439340649c4" } },
{ "exam" : 14, "a" : 9, "_id" : { "$oid" : "52b1d191cc937439340649ce" } },
{ "exam" : 5, "a" : 9, "_id" : { "$oid" : "52b1d49fcc937439340649f3" } },
I noticed is that if I try to repopulate the database using the loop in t4 it doesn't seem to re-write the values. i.e. if I use for(i=0; i<10; i++) { db.scores.save({a: i, exam: 5}) }; as the example had suggested instead of my just for fun for(i=0; i<10; i++) { db.scores.save({a: i, exam: 5+i}) };. Not sure if that is helpful to diagnose the problem but it is another observation.
You're missing something very special,
I noticed is that if I try to repopulate the database using the loop
in t4 it doesn't seem to re-write the values. i.e. if I use for(i=0;
i<10; i++) { db.scores.save({a: i, exam: 5}) }; as the example had
suggested instead of my just for fun for(i=0; i<10; i++) {
db.scores.save({a: i, exam: 5+i}) };. Not sure if that is helpful to
diagnose the problem but it is another observation.
Repopulate the database running the query more than once will create 10 rows every single time. db.scores.save doesn't know what document to update because you didn't refer to an _id field, in that case it will always create 10 records. To update existing records you should provide an _id field from the previous inserts. I'm sure you run it more than once and you expect to have always 10 records, what's happening is you're inserting 10 records every time.
Try it removing the collection, run the loop once and execute your find, it will work.
Are you sure you didn't run the commands more than once? What do you see if you run db.scores.find().count(), that will tell you how many items are in the table.