MongoDB. Remove the oldest element from array inside document - arrays

I have this document:
[
{
"username_id": "user01",
"passwordList": [
{
"tstamp": 101,
"tempInd": 0,
"pass": "aaaa"
},
{
"tstamp": 102,
"tempInd": 0,
"pass": "bbbbb"
},
{
"tstamp": 103,
"tempInd": 0,
"pass": "ccccc"
},
{
"tstamp": 100,
"tempInd": 1,
"pass": "99999"
}
]
}
]
What I want is to remove from the passwordList the element which has the lowest tstamp with tempInd equal to 0. This is my expected output:
[
{
"username_id": "user01",
"passwordList": [
{
"tstamp": 102,
"tempInd": 0,
"pass": "bbbbb"
},
{
"tstamp": 103,
"tempInd": 0,
"pass": "ccccc"
},
{
"tstamp": 100,
"tempInd": 1,
"pass": "99999"
}
]
}
]
This is my attempt:
db.collection.update([
{"username_id": "user01" } ,
{"$pull":{"passwordList": { "$elemMatch": { "tempInd": 0 , "tstamp": {"$min": "$passwordList.tstamp"} } } } }
])
Any suggestion?
Thanks!

You can do it like this:
db.collection.update(
{ "username_id": "user01" } ,
[
{
$set: {
passwordList: {
$filter: {
input: '$passwordList',
as: 'filter1Password',
cond: {
$ne: [
'$$filter1Password',
{
$first: {
$sortArray: {
input: {
$filter: {
input: '$passwordList',
as: 'filter2Password',
cond: {
$eq: ['$$filter2Password.tempInd', 0]
}
}
},
sortBy: {
tstamp: 1
}
}
}
}
]
}
}
}
}
}
]
)
Working from the inside out:
The innermost $filter operator discards all array elements whose tempInd is not 0.
The $sortArray operator sorts the result of step 1 by tstamp, ascending. (note that $sortArray is only available in Mongo 5.2 and newer)
The $first operator returns the first element of the array returned by step 2 (this would be the element with the lowest tstamp whose tempInd is 0)
The $filter operator returns all elements of the passwordList array that are NOT equal to the result of step 3. (note that if the array has multiple elements that all match the result of step 3, all of them will be removed)
The $set operator sets passwordList to be the result of step 4.

Related

Show the output in an array of values

Hello I have this query result
{
sac: 1,
sac_db: 0,
kafka: 1,
platform: 13700,
}
now I just want to show the values in an array, but I can't find how to do it:
[1,0,1,13700]
You can get this done using $map and $objectToArray, like so:
db.collection.aggregate([
{
$project: {
_id: 0,
results: {
$map: {
input: {
$filter: {
input: {
"$objectToArray": "$$ROOT"
},
cond: {
$ne: [
"$$this.k",
"_id"
]
}
}
},
in: "$$this.v"
}
}
}
}
])
Mongo Playground

Access same field of every object in array of objects in mongodb aggregation

I have the following documents in collection of mongodb:
banks:[{name:"ABC", amt:0},{name:"PQR", amt:-1},{name"XYZ", amt:3400}]
banks:[{name:"ABC", amt:-2},{name:"PQR", amt:2344},{name"XYZ", amt:7600}]
Like this say I have 10 documents and each document contains one banks array. Each banks array has 30 objects in it as shown above.
I am trying to write aggregation query in mongodb to get the count of objects that have "amt" less than equal to zero and greater than zero but so far unable to get it. Please help. Thanks in advance!
The output for above sample documents should be
{"greaterThanZero": 1, "lessThanEqualToZero": 2 }
{"greaterThanZero": 2, "lessThanEqualToZero": 1 }
First you have to separate yours documents with $unwind
Then with a $project and a $cond you tell for each document if it's greaterThanZero or lessThanEqualToZero
Finally you sum up greaterThanZero and lessThanEqualToZero with a $group
You can test it here : Mongo Playground
[
{
"$unwind": "$banks"
},
{
"$project": {
"greaterThanZero": {
"$cond": [
{
"$gt": [
"$banks.amt",
0
]
},
1,
0
]
},
"lessThanEqualToZero": {
"$cond": [
{
"$lte": [
"$banks.amt",
0
]
},
1,
0
]
}
}
},
{
"$group": {
"_id": "$_id",
"greaterThanZero": {
"$sum": "$greaterThanZero"
},
"lessThanEqualToZero": {
"$sum": "$lessThanEqualToZero"
}
}
}
]
You can do it with $reduce,
it checks condition using $cond if match then add one to value,
db.collection.aggregate([
{
$project: {
lessThanEqualToZero: {
$reduce: {
input: "$banks",
initialValue: 0,
in: {
$cond: [
{ $lte: ["$$this.amt", 0] },
{ $add: ["$$value", 1] },
"$$value"
]
}
}
},
greaterThanZero: {
$reduce: {
input: "$banks",
initialValue: 0,
in: {
$cond: [
{ $gt: ["$$this.amt", 0] },
{ $add: ["$$value", 1] },
"$$value"
]
}
}
}
}
}
])
Playground

MongoDB group query - count by result

db.tickets.aggregate([
{$project:
{_id: 0, dayssince:
{$divide: [{ $subtract: [ 2020, {$convert:{input:{$substrCP:["$data.DATE_BIRTH", 6, 4]}, to: "int"}}]}, 45]}}},
{$match:{dayssince:{$gte: 1}}},
{$group:{_id:{day:"$dayssince"}},count:{$sum:1}}]);
Please, tell me whats wrong, i cant understend, i need to find count of all values
Please explain your problem. Just by indenting your code, its seems count property is outside of the $group operator.
here is your indented and fixed query:
db.tickets.aggregate([
{
$project: {
_id: 0,
dayssince: {
$divide: [
{
$subtract: [2020, { $convert: { input: { $substrCP: ["$data.DATE_BIRTH", 6, 4] }, to: "int" } }]
},
45]
}
}
},
{
$match: { dayssince: { $gte: 1 } }
},
{
$group: {
_id: {
day: "$dayssince"
},
count: {
$sum: 1
}
}
}
]);

Using $size and $addToSet to compare multiple arrays from the same cluster

Here is an example of the kind of documents I'm querying:
INPUT:
}
"_id": ObjectId("2786872873872"),
"data_shop" : {
"records_data" : [
{
"artist_name" : [
{
"val" : "BEYONCE",
},
],
"album_name" : [
{
"val" : "COUNTDOWN",
}
],
"qty" : [
0,
1,
2,
3
]
},
{
"artist_name" : [
{
"val" : "MUSE",
},
],
"album_name" : [
{
"val" : "THE RESISTANCE",
}
],
"qty" : [
0,
1,
2,
3,
3
]
}
],
},
}
}
"_id": ObjectId("2786872855555"),
"data_shop" : {
"records_data" : [
{
"artist_name" : [
{
"val" : "MAC MILLER",
},
],
"album_name" : [
{
"val" : "SWIMMING",
}
],
"qty" : [
0,
1,
2,
3,
]
},
{
"artist_name" : [
{
"val" : "DAFT PUNK",
},
],
"album_name" : [
{
"val" : "RANDOM ACCESS MEMORIES",
}
],
"qty" : [
0,
1,
2,
3,
4,
]
}
],
},
}
What I've done so far:
I'm trying to use both $size and $addtoSet in order to return the ObjectIds that have repeated numbers in the qty field. As you can see, only the first ObjectId has a repeated number (3) in the qty field.
This is what I've done so far:
db.mycollection.aggregate(
[
{$match: {"data_shop.records_data.qty.1": {$lte: 1}}},
{
$project: {Album_Cluster:"$data_shop.records_data"}
},
{
$unwind: "$Album_Cluster"
},
{
$project: {qty: "$Album_Cluster.qty"},
},
{
$project: {qty_size: {$size: "$qty"}, qty:1}
},
{ $match: {"qty_size.1": {$exists: false}, qty_size: {$gt: 1} }},
{$group:
{_id: "$_id",
totalSize: {$push: "$qty_size"},
realSize: {$addToSet: "$qty"},
}
},
],
{allowDiskUse: true}
)
And this is the result of the query above, in order to check the functionality of the query:
{"_id":ObjectId("2786872873872"), "totalSize": [4, 5], "realSize":[[0, 1, 2, 3]]}
{"_id":ObjectId("2786872855555"), "totalSize": [4, 5], "realSize":[[0, 1, 2, 3], [0, 1, 2, 3, 4]]}
I'm a little bit stuck at this part since I want to compare the total size of each array versus the real size of the array (by real size I mean non-repeating numbers)
OUTPUT
This is how the output of the query should look like:
{"_id":ObjectId("2786872873872"), "isRepeating": true}
{"_id":ObjectId("2786872855555"), "isRepeating": false}
EDIT:
I've improved my query in order to get this output schema:
db.mycollection.aggregate(
[
{$match: {"data_shop.records_data.qty.1": {$lte: 1}}},
{
$project: {Album_Cluster:"$data_shop.records_data"}
},
{
$unwind: "$Album_Cluster"
},
{
$project: {qty: "$Album_Cluster.qty"},
},
{
$project: {qty_size: {$size: "$qty"}, qty:1}
},
{$group:
{
_id: "$_id",
totalSize: {$addToSet: "$qty_size"},
realSize: {$addToSet: "$qty"},
}
},
{$unwind: "$realSize"},
{
$project:
{
totalSize:1,
real_count: {$size: "$realSize"}
}
},
{$unwind: "$totalSize"},
{
$group: {
_id: "$_id",
total_size: {$addToSet: "$totalSize"},
real_size: {$addToSet: "$real_count"}
}
},
],
{allowDiskUse: true}
)
And now I'm getting this as my output:
{"_id":ObjectId("2786872873872"), "total_size": [4, 5], "real_size":[4]}
{"_id":ObjectId("2786872855555"), "total_size": [4, 5], "real_size":[5, 4]}
Now my question is, does $in allow me to validate that [4, 5] is valid in [5, 4] so my output will be isRepeating = false?
Altought you can achieve this with a stack og unwind / group stages, it can be very expensive in resources consumption.
Unfortunaltely, the $addToSet oerator in available only in $group stage.
But... There's a trick, with the $setUnion operator.
$setUnion performs set operation on arrays, treating arrays as sets. If an array contains duplicate entries, $setUnion ignores the duplicate entries.
Knowing this, performing a $setUnion on your qty array, without any other array, will just... remove the duplicates.
Here's a implementation of this approach, using only 2 project stages
db.collection.aggregate([
{
$project: {
"data_shop.records_data": {
$map: {
input: "$data_shop.records_data",
as: "data",
in: {
qty_size: {
$size: "$$data.qty"
},
qty_size_unique: {
$size: {
$setUnion: [
"$$data.qty"
]
}
}
}
}
}
}
},
{
$project: {
isRepeating: {
$cond: {
if: {
$eq: [
"$data_shop.records_data.qty_size",
"$data_shop.records_data.qty_size_unique"
]
},
then: false,
else: true
}
}
}
}
])
It will return the expected output :
[
{
"_id": 1,
"isRepeating": true
},
{
"_id": 2,
"isRepeating": false
}
]

Upserting a value at an array position using $min

I would like to either insert a new document with a default value as part of an array, or update that part of the array if the document already exists.
What I thought of was:
db.test.update(
{ "a": 5 },
{ $setOnInsert: { "b": [0, 0] }, $min: { "b.0": 5 } },
{ upsert: true }
);
If I do that, then I get:
Cannot update 'b' and 'b.0' at the same time
Another idea was to remove $setOnInsert and just keep $min, since the minimum between nothing and 5 should be 5.
db.test.update(
{ "a": 5 },
{ $min: { "b.0": 5 } },
{ upsert: true }
);
This doesn't raise an error, but now the document I get is:
{ "a" : 5, "b" : { "0" : 5 } }
I need an array with 5 at position 0 however, not an object with a 0 property.
How can I achieve this?
You can use .bulkWrite() for this, and it's actually a prime use case of why this exists. It only sends "one" actual request to the server and has only one response. It's still two operations, but they are more or less tied together and generally atomic anyway:
db.junk.bulkWrite([
{ "updateOne": {
"filter": { "a": 1 },
"update": { "$setOnInsert": { "b": [ 5,0 ] } },
"upsert": true
}},
{ "updateOne": {
"filter": { "a": 1 },
"update": { "$min": { "b.0": 5 } }
}}
])
Run for the first time will give you an "upsert", note that it's "inserted" and not "modified" in the response:
{
"acknowledged" : true,
"deletedCount" : 0,
"insertedCount" : 0,
"matchedCount" : 1,
"upsertedCount" : 1,
"insertedIds" : {
},
"upsertedIds" : {
"0" : ObjectId("5947c412d6eb0b7d6ac37f09")
}
}
And the document of course looks like:
{
"_id" : ObjectId("5947c412d6eb0b7d6ac37f09"),
"a" : 1,
"b" : [
5,
0
]
}
Then run with a different value to $min as you likely would in real cases:
db.junk.bulkWrite([
{ "updateOne": {
"filter": { "a": 1 },
"update": { "$setOnInsert": { "b": [ 5,0 ] } },
"upsert": true
}},
{ "updateOne": {
"filter": { "a": 1 },
"update": { "$min": { "b.0": 3 } }
}}
])
And the response:
{
"acknowledged" : true,
"deletedCount" : 0,
"insertedCount" : 0,
"matchedCount" : 2,
"upsertedCount" : 0,
"insertedIds" : {
},
"upsertedIds" : {
}
}
Which "matched" 2 but of course $setOnInsert does not apply, so the result is:
{
"_id" : ObjectId("5947c412d6eb0b7d6ac37f09"),
"a" : 1,
"b" : [
3,
0
]
}
Just like it should be

Resources