MongoDB aggregation grouping documents without accumaltion - mongodb

Using MongoDB's aggregate pipeline, I want to be able to group documents by a value without any accumulation.
For example, I want to group this collection by key:
[
{
"_id":"323232",
"name":"Something",
"key":"A",
"price":"100"
},
{
"_id":"157236",
"name":"Another thing",
"key":"B",
"price":75
},
{
"_id":"555232",
"name":"Something or another",
"key":"B",
"price":78
}
]
Desired result:
[
{
"_id":"A",
"results": [
{
"_id":"323232",
"name":"Something",
"key":"A",
"price":"100"
}
]
},
{
"_id":"A",
"results": [
{
"_id":"157236",
"name":"Another thing",
"key":"B",
"price":75
},
{
"_id":"555232",
"name":"Something or another",
"key":"B",
"price":78
}
]
}
]

How about this?
db.test.aggregate([
{
$sort : {"price" : 1}
},
{
$group : {
_id : '$key',
results : {
$push : {
"_id":"$_id",
"name":"$name",
"key":"$key",
"price":"$price"
}
}
}
}
]).pretty()

check this
db.coll.aggregate([
{ "$group" : {
'_id' :'$key',
result: {$push:'$$ROOT'}
}
}
]);

Related

How can i get records such that my input is array of ids and collection field just id using mongodb aggregate

Input : "program_id": ["5e995225a9cdc44ffc335bb5","5eb3e9e03edcda4db73c2ba8","5e99522fa9cdc44ffc335bb6"]
how can i match with "program" field in below collection
{
"_id" : ObjectId("5eddcaa6783db57d44ffd188"),
"name" : "Dennis",
"program" : ObjectId("5e995225a9cdc44ffc335bb5"),
"deleted" : false,
"__v" : 0,
"updated_date" : ISODate("2020-06-08T05:21:35.221Z")
}
with $in
Remember: Your array of ids should be ObjectIDs
db.collection.findOne({ program: {
$in: array
}})
You need to convert the program ObjectIds to strings and then use $in operator.
db.collection.aggregate([
{
$addFields: {
program_str: {
$toString: "$program"
}
}
},
{
$match: {
program_str: {
$in: [
"5e995225a9cdc44ffc335bb5",
"5eb3e9e03edcda4db73c2ba8",
"5e99522fa9cdc44ffc335bb6"
]
}
}
},
{
$project: {
program_str: 0
}
}
])
MongoPlayGroundLink
More shorter version of the above query:
db.collection.aggregate([
{
$match: {
$expr: {
$in: [
{
$toString: "$program"
},
[
"5e995225a9cdc44ffc335bb5",
"5eb3e9e03edcda4db73c2ba8",
"5e99522fa9cdc44ffc335bb6"
]
]
}
}
}
])
MongoPlayGroundLink

MongoDB $cond with embedded document array

I am trying to generate a new collection with a field 'desc' having into account a condition in field in a documment array. To do so, I am using $cond statement
The origin collection example is the next one:
{
"_id" : ObjectId("5e8ef9a23e4f255bb41b9b40"),
"Brand" : {
"models" : [
{
"name" : "AA"
},
{
"name" : "BB"
}
]
}
}
{
"_id" : ObjectId("5e8ef9a83e4f255bb41b9b41"),
"Brand" : {
"models" : [
{
"name" : "AG"
},
{
"name" : "AA"
}
]
}
}
The query is the next:
db.runCommand({
aggregate: 'cars',
'pipeline': [
{
'$project': {
'desc': {
'$cond': {
if: {
$in: ['$Brand.models.name',['BB','TC','TS']]
},
then: 'Good',
else: 'Bad'
}
}
}
},
{
'$project': {
'desc': 1
}
},
{
$out: 'cars_stg'
}
],
'allowDiskUse': true,
})
The problem is that the $cond statement is always returning the "else" value. I also have tried $or statement with $eq or the $and with $ne, but is always returning "else".
What am I doing wrong, or how should I fix this?
Thanks
Since $Brand.models.name returns an array, we cannot use $in operator.
Instead, we can use $setIntersection which returns an array that contains the elements that appear in every input array
db.cars.aggregate([
{
"$project": {
"desc": {
"$cond": [
{
$gt: [
{
$size: {
$setIntersection: [
"$Brand.models.name",
[
"BB",
"TC",
"TS"
]
]
}
},
0
]
},
"Good",
"Bad"
]
}
}
},
{
"$project": {
"desc": 1
}
},
{
$out: 'cars_stg'
}
])
MongoPlayground | Alternative $reduce

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" ] }

How to get values in mongdb

This is my my data in Mongodb
{
"d" : {
"results" : [
{
"slack_id" : "RAGHU#TN.COM",
"connector_id" : "GRECLNT900",
"sys_role" : "DEV",
"user_id" : "RAGHU"
},
{
"slack_id" : "RAGHU#TN.COM",
"connector_id" : "GRECLNT900",
"sys_role" : "PRD",
"user_id" : "RAGHU",
"question" : "What is your favorite color?",
"Answer" : "Orange"
},
]
}
}
If i am giving RAGHU#TN.COM. then i want display sys_role. Output like this[DEV, PRD]
I am trying this way
x = mydb.mycollection.distinct("sys-role")
But I get an empty array like [ ]
You have to treat the cursor as a reference(personally I see it as a reference in C), and then de-reference it to see the result.(What is inside the address)
For the specific column, here is the result from command prompt:
my_cursor = mydb.mycollection.distinct("sys-role")
for x in my_cursor:
print('{0}'.format(x['sys_role']))
The distinct operator is not inter-operatable thus it's hard to filter by slack_id first. I would recommande using aggregation pipelines.
Here is an example.
[
{
'$match': {
'slack_id': 'RAGHU#TN.COM'
}
}, {
'$group': {
'_id': 'slack_id',
'result': {
'$addToSet': 'sys_role'
}
}
}
]
With this pipeline, your sys_role set will be in the .result field.
Using Mongo aggregation query you will get required result set. Try this:
db.collection.aggregate([
{
"$match": {
"d.results.slack_id": "RAGHU#TN.COM"
}
},
{
$group: {
_id: "$d.results.slack_id",
sys_role: {
$push: "$d.results.sys_role"
}
}
}
])
db.getCollection("collection").aggregate(
// Pipeline
[
// Stage 1
{
$project: {
results: {
$filter: {
input: "$d.results",
as: "item",
cond: { $eq: [ "$$item.slack_id", 'RAGHU#TN.COM' ] }
}
}
}
},
// Stage 2
{
$unwind: {
path : "$results",
preserveNullAndEmptyArrays : false // optional
}
},
// Stage 3
{
$group: {
_id:'$results.slack_id',
sys_roles:{$addToSet:'$results.sys_role'}
}
},
]
);

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" ] } }
}
}
]
}
}
}
])