Remove aggregate with conditions - mongodb

I have the a collection of documents as follows an example document:
{
'publicacao' : { 'data': '2013-13-13', 'hora': '13:13:13'},
'conteudo' : 'https://docs.google.com/document/d/1EQynJTiBa6FNI2O8XfoV0clMPxS5uOAu0_jKyEwsTBE/edit?usp=sharing',
'titulo' : 'As histórias de ciclano',
'categoria' : 'Romance',
'autor' : 'Ciclano',
'avaliacoes': [
{
'leitor': 'Fulano',
'nota': 1
},
{
'leitor': 'Beltrano',
'nota': 0
}
],
'denuncias': [
{
'denunciante': 'Ciclano'
},
{
'denunciante': 'Beltrano'
}
]
}
then i made an aggregate to define some documents to be removed:
var cursor = db.livro.aggregate( [
{
$project: {
id:1,
remover: {
$gt: [
{ $size: "$denuncias" },
{
$divide: [
{ $size: "$avaliacoes" },
2
]
}
]
}
}
}
]);
this aggregation returns the following docs:
{ "_id" : ObjectId("5cf5a9be7d48c53504974439"), "remover" : false }
{ "_id" : ObjectId("5cf5a9be7d48c5350497443a"), "remover" : false }
{ "_id" : ObjectId("5cf5a9be7d48c5350497443b"), "remover" : true }
{ "_id" : ObjectId("5cfd746e40d53565ca52132b"), "remover" : false }
{ "_id" : ObjectId("5cfd746e40d53565ca52132c"), "remover" : false }
{ "_id" : ObjectId("5cfd746e40d53565ca52132d"), "remover" : true }
I need to remove all docs with "remove": true.
I don't know how to get those ones!

Resolved:
I used the cursor.forEach() to iterate the objects and find the objects with remove = true.
cursor.forEach(function (doc){
if(doc.remover == true) {
db.livro.remove({"_id": doc._id});
print("Doc removido: "+ doc._id)
}
});

Instead of doing this with code after the query it is better overall to do it in the actual aggregation. Just check with a $match if the array has an element:
db.collection.aggregate([
{
$match: {
"denuncias.0": {
$exists: true
}
}
},
{
$project: {
id: 1,
remover: {
$gt: [
{
$size: "$denuncias"
},
{
$divide: [
{
$size: "$avaliacoes"
},
2
]
}
]
}
}
}
])
You can see if working here

Related

How to combine results in a Mongo aggregation query

I'm new to aggregation queries in Mongo and been really struggling trying to produce the output I want. I have the following aggregation query:
db.events.aggregate([
{ $match: { requestState: "APPROVED" } },
{ $unwind: { path: "$payload.description" } },
{ $group: { _id: { instr: "$payload.description", bu: "$createdByUser", count: { $sum: 1 } } } }
]);
that returns the following results:
{ "_id" : { "instr" : "ABC-123", "bu" : "BU2", "count" : 1 } }
{ "_id" : { "instr" : "ABC-123", "bu" : "BU1", "count" : 1 } }
{ "_id" : { "instr" : "DEF-456", "bu" : "BU1", "count" : 1 } }
How can I amend the aggregation query so that there are only 2 documents returned instead of 3? With the two "ABC-123" results combined into a single result with a new array of counts with the "bu" and "count" fields i.e.
{ "_id" : { "instr" : "ABC-123", "counts": [ { "bu" : "BU1", "count" : 1 }, { "bu" : "BU2", "count" : 1 } ] } }
Many thanks
You can add another stage to only $group by _id.instr and another stage to $project to your desired output shape
db.events.aggregate([
{
$match: { requestState: "APPROVED" }
},
{
$unwind: { path: "$payload.description" }
},
{
$group: {
_id: { instr: "$payload.description", bu: "$createdByUser", count: { $sum: 1 } }
}
},
{
$group: {
_id: { instr: "$_id.instr" },
counts: { $push: { bu: "$_id.bu", count: "$_id.count" } }
}
},
{
$project: {
_id: { instr: "$_id.instr", counts: "$counts" }
}
}
]);

Filter keys not in collection

How do we find keys which do not exist in collection.
Given an input list of keys ['3321', '2121', '5647'] , i want to return those that do not exist in the collection :
{ "_id" : { "$oid" : "5e2993b61886a22f400ea319" }, "scrip" : "5647" }
{ "_id" : { "$oid" : "5e2993b61886a22f400ea31a" }, "scrip" : "3553" }
So the expected output is ['3321', '2121']
This aggregation gets the desired output (works with MongoDB version 3.4 or later):
INPUT_ARRAY = ['3321', '2121', '5647']
db.test.aggregate( [
{
$match: {
scrip: {
$in: INPUT_ARRAY
}
}
},
{
$group: {
_id: null,
matches: { $push: "$scrip" }
}
},
{
$project: {
scrips_not_exist: { $setDifference: [ INPUT_ARRAY, "$matches" ] },
_id: 0
}
}
] )
The output:
{ "scrips_not_exist" : [ "3321", "2121" ] }

Mongodb aggregation match ends with using field value

note: I'm using Mongodb 4 and I must use aggregation, because this is a step of a bigger aggregation
Problem
How to find in a collection documents that contains fields that ends with value from another field in same document ?
Let's start with this collection:
db.regextest.insert([
{"first":"Pizza", "second" : "Pizza"},
{"first":"Pizza", "second" : "not pizza"},
{"first":"Pizza", "second" : "not pizza"}
])
and an example query for exact match:
db.regextest.aggregate([
{
$match : { $expr: { $eq: [ "$first" ,"$second" ] } } }
])
I will get a single document
{
"_id" : ObjectId("5c49d44329ea754dc48b5ace"),
"first" : "Pizza", "second" : "Pizza"
}
And this is good.
But how to do the same, but with endsWith?
I've openend another question for start with here that uses indexOfBytes . But indexOf return only first match, and not last one
Edit: I've found an acceptable answer (with a lot of custom logic, my hope is Mongodb team will solve this), here the solution:
db.regextest.aggregate([
{
$addFields : {
"tmpContains" : { $indexOfBytes: [ "$first", { $ifNull : [ "$second" , 0] } ] }
}
},
{
$match: { "tmpContains" : { $gt : -1 } }
},
{
$addFields : {
"firstLen" : { $strLenBytes: "$first" }
}
},
{
$addFields : {
"secondLen" : { $strLenBytes: "$second" }
}
},
{
$addFields : {
"diffLen" : { $abs: { $subtract : [ "$firstLen", "$secondLen"] } }
}
},
{
$addFields : {
"res" : { $substr: [ "$first", "$diffLen", "$firstLen"] }
}
},
{
$match : { $expr : { $eq: [ "$res" , "$second" ] }}
}
])
As you know the length of both fields ($strLenBytes) you can use $substr to get last n characters of second field and the compare it to first field, try:
db.regextest.aggregate([
{
$match: {
$expr: {
$eq: [
"$first",
{
$let: {
vars: { firstLen: { $strLenBytes: "$first" }, secondLen: { $strLenBytes: "$second" } },
in: { $substr: [ "$second", { $subtract: [ "$$secondLen", "$$firstLen" ] }, "$$firstLen" ] }
}
}
]
}
}
}
])
Above aggregation will give you the same result as string comparison is case-sensitive in MongoDB. To fix that you can apply $toLower operator both on $first and on calculated substring of $second, try:
db.regextest.aggregate([
{
$match: {
$expr: {
$eq: [
{ $toLower: "$first" },
{
$let: {
vars: { firstLen: { $strLenBytes: "$first" }, secondLen: { $strLenBytes: "$second" } },
in: { $toLower: { $substr: [ "$second", { $subtract: [ "$$secondLen", "$$firstLen" ] }, "$$firstLen" ] } }
}
}
]
}
}
}
])

Mongo Db query using if condition

I have a Mongodb Data which looks like this
{
"userId" : "123",
"dataArray" : [
{
"scheduledStartDate" : ISODate("2018-08-30T11:34:36.000+05:30"),
"scheduledEndDate" : ISODate("2018-08-30T11:34:36.000+05:30"),
"Progress" : 0,
"ASD":""
},
{
"scheduledStartDate" : ISODate("2018-09-22T11:34:36.000+05:30"),
"scheduledEndDate" : ISODate("2018-10-01T11:34:36.000+05:30"),
"Progress" : 0,
"ASD":ISODate("2018-08-30T11:34:36.000+05:30"),
}
],
"userStatus" : 1,
"completionStatus" : "IP",
}
I want to find those document where condition is something like this
(PROGRESS<100||(PROGRESS==100&&ASD not exists)).
This should get you going ($elemMatch):
db.collection.find({
dataArray: {
$elemMatch: {
$or: [
{ Progress: { $lt: 100 } },
{ $and: [
{ Progress: { $eq: 100 } },
{ ASD: { $exists: false } }
]}
]
}
}
})
UPDATE based on your comment - this is even easier:
db.collection.find({
$or: [
{ "dataArray.Progress": { $lt: 100 } },
{ $and: [
{ "dataArray.Progress": { $eq: 100 } },
{ "dataArray.ASD": { $exists: false } }
]}
]
})

Mongodb Aggregation Rows to Columns

I have the following dataset. I need to group them by Account, and then turn the Element_Fieldname into a column.
var collection = [
{
Account:12345,
Element_Fieldname:"cars",
Element_Value:true
},
{
Account:12345,
Element_Fieldname:"boats",
Element_Value:false
}
]
This was my attempt to convert rows to columns, but its not working.
db.getCollection('my_collection').aggregate([{
$match : {
Element_Fieldname : {
$in : ["cars", "boats"]
}
}
}, {
$group : {
_id : "$Account",
values : {
$addToSet : {
field : "$Element_Fieldname",
value : "$Element_Value"
}
}
}
}, {
$project : {
Account : "$_id",
cars : {
"$cond" : [{
$eq : ["$Element_Fieldname", "cars"]
}, "$Element_Value", null]
},
boats : {
"$cond" : [{
$eq : ["$Element_Fieldname", "day_before_water_bottles"]
}, "$Element_Value", null]
},
}
}
])
This just gives me null in my cars and boats fields. Any help would be great.
And this is my desired results:
var desiredResult = [
{
Account:12345,
cars:true,
boats:false
}
]
this is a big tricky but you will get what you need :-)
please add $match on the top of aggregation pipeline
db.collection.aggregate([{
$project : {
_id : 0,
"Account" : 1,
car : {
$cond : [{
$eq : ["$Element_Fieldname", "cars"]
}, "$Element_Value", null]
},
boats : {
$cond : [{
$eq : ["$Element_Fieldname", "boats"]
}, "$Element_Value", null]
},
}
},
{
$group : {
_id : "$Account",
carData : {
$addToSet : "$car"
},
boatsData : {
$addToSet : "$boats"
}
}
}, {
$unwind : "$carData"
}, {
$match : {
carData : {
$ne : null
}
}
}, {
$unwind : "$boatsData"
}, {
$match : {
boatsData : {
$ne : null
}
}
},
])
and result
{
"_id" : 12345,
"carData" : true,
"boatsData" : false
}
It is not possible to do the type of computation you are describing with the aggregation framework, however there is a proposed $arrayToObject expression which will give you the functionality to peek into the key names, and create new key/values dynamically.
For example, you could do
db.collection.aggregate([
{
"$match": { "Element_Fieldname":{ "$in": ["cars", "boats"] } }
},
{
"$group": {
"_id": "$Account",
"attrs": {
"$push": {
"key": "$Element_Fieldname",
"val": "$Element_Value"
}
}
}
},
{
"$project": {
"Account": "$_id",
"_id": 0,
"newAttrs": {
"$arrayToObject": {
"$map": {
"input": "$attrs",
"as": "el",
in: ["$$el.key", "$$el.val"]
}
}
}
}
},
{
"$project": {
"Account": 1,
"cars": "$newAttrs.cars",
"boats": "$newAttrs.boats"
}
}
])
Vote for this jira ticket https://jira.mongodb.org/browse/SERVER-23310 to get this feature.
As a workaround, mapreduce seems like the available option. Consider running the following map-reduce operation:
db.collection.mapReduce(
function() {
var obj = {};
obj[this.Element_Fieldname] = this.Element_Value;
emit(this.Account, obj);
},
function(key, values) {
var obj = {};
values.forEach(function(value) {
Object.keys(value).forEach(function(key) {
obj[key] = value[key];
});
});
return obj;
},
{ "out": { "inline": 1 } }
)
Result:
{
"_id" : 12345,
"value" : {
"cars" : true,
"boats" : false
}
}