how to fix this aggregation issue in mongodb redact - mongodb

i have people dataset in mongodb, where i want to get all people whom friends has id greater than 1. i tried using $redact aggregation pipeline but it is not working. here is sample data
{
"_id" : "5d7a16904d08c0c435e4255b",
"name" : {
"first" : "Roberta",
"last" : "Jackson"
},
"company" : "YURTURE",
"email" : "roberta.jackson#yurture.io",
"age":26,
"registered" : "Sunday, May 5, 2019 2:44 PM",
"latitude" : "36.56389",
"longitude" : "-72.518115",
"friends" : [
{
"id" : 0,
"name" : "Vicki Peck"
},
{
"id" : 1,
"name" : "Jeanie Boyd"
},
{
"id" : 2,
"name" : "Terra Curtis"
}
],
}
i tried using aggregation pipeline redact but i am not getting any data in friends list.
db.getCollection('test_redact').aggregate([ { $project:{
"name" : 1,
"friends" : 1,
"greeting" : 1,
}},
{
$redact:{
$cond: {
if: { $gte: [ "$friends.id", 1 ] },
then: "$$DESCEND",
else: "$$PRUNE"
}
}
}
])
here is sample output i am getting after executing aggregation
{
"_id" : "5d7a16904d08c0c435e4255b",
"age" : 26,
"friends" : [],
"greeting" : "Hello, Roberta! You have 9 unread messages."
}

The following query can get us the expected output:
db.getCollection("collection").find({"friends.id":{$gte:1}},{"name":1,"friends":1,"greeting":1}).pretty()
To get only those friends which has id greater than 1:
db.getCollection("collection").aggregate([
{
$match:{
"friends.id":{
$gte:1
}
}
},
{
$project:{
"name":1,
"friends":{
$filter:{
"input":"$friends",
"as":"friend",
"cond":{
$gte:["$$friend.id",1]
}
}
},
"greeting":1
}
}
]).pretty()
Using $redact:
db.getCollection("collection").aggregate([
{
$redact:{
$cond:[
{
$or:[
{
$gte:["$id",1]
},
{
$eq:["$id",undefined]
}
]
},
"$$DESCEND",
"$$PRUNE"
]
}
},
{
$project:{
"name":1,
"friends":1,
"greeting":1
}
}
]).pretty()

Related

MongoDB Aggregation with match shows all the records

I have json object as following.
{
"_id" : ObjectId("123209sfekjern"),
"Name" : "Test1",
"Orders" : [
{
"Date" : "2020-05-05",
"Total" : "100.00"
},
{
"Date" : "2020-05-10",
"Total" : "110.00"
},
{
"Date" : "2020-05-11",
"Total" : "100.00"
},
{
"Date" : "2020-05-14",
"Total" : "110.00"
},
{
"Date" : "2020-05-20",
"Total" : "100.00"
},
{
"Date" : "2020-05-15",
"Total" : "100.00"
},
{
"Date" : "2020-05-12",
"Total" : "110.00"
},
{
"Date" : "2020-05-18",
"Total" : "100.00"
},
{
"Date" : "2020-05-31",
"Total" : "110.00"
}
]
}
I need customername, orders.Date and Order.Total for all the orders which is greater than 100.00
I tried following query..
db.Customers.aggregate
(
[
{
$match: {
$and: [
{"Orders.Date":{$gte:"2020-05-15"}},//ISODate('2020-05-15 10:00:00.000Z')
{ "Orders.Total": { $gte: "100.00" } },
]
}
},
{ $project: { _id:0, Name: 1, "Orders.Total": 1, "Orders.Date": 1} },
]
)
The above query returns all the records. I m still beginner and learning mongodb.
any help would be appreciated.
thank you.
$match filters on a document level so entire document will be returned if at least one subdocument matches your conditions. In order to filter a nested array you need $filter:
db.Customers.aggregate([
{
$project: {
_id: 0,
Name: 1,
Orders: {
$filter: {
input: "$Orders",
cond: {
$and: [
{ $gte: [ "$$this.Date", "2020-05-15" ] },
{ $gte: [ "$$this.Total", "100.00" ] },
]
}
}
}
}
}
])
Mongo Playground

Partition data around a match query during aggregation

What I have been trying to get my head around is to perform some kind of partitioning(split by predicate) in a mongo query. My current query looks like:
db.posts.aggregate([
{"$match": { $and:[ {$or:[{"toggled":false},{"toggled":true, "status":"INACTIVE"}]} , {"updatedAt":{$gte:1549786260000}} ] }},
{"$unwind" :"$interests"},
{"$group" : {"_id": {"iid": "$interests", "pid":"$publisher"}, "count": {"$sum" : 1}}},
{"$project":{ _id: 0, "iid": "$_id.iid", "pid": "$_id.pid", "count": 1 }}
])
This results in the following output:
{
"count" : 3.0,
"iid" : "INT456",
"pid" : "P789"
}
{
"count" : 2.0,
"iid" : "INT789",
"pid" : "P789"
}
{
"count" : 1.0,
"iid" : "INT123",
"pid" : "P789"
}
{
"count" : 1.0,
"iid" : "INT123",
"pid" : "P123"
}
All good so far, but then I had realized that for the documents that match the specific filter {"toggled":true, "status":"INACTIVE"}, I would rather decrement the count (-1). (considering the eventual value can be negative as well.)
Is there a way to somehow partition the data after match to make sure different grouping operations are performed for both the collection of documents?
Something that sounds similar to what I am looking for is
$mergeObjects, or maybe $reduce, but not much that I can relate from the documentation examples.
Note: I can sense, one straightforward way to deal with this would be to perform two queries, but I am looking for a single query to perform the operation.
Sample documents for the above output would be:
/* 1 */
{
"_id" : ObjectId("5d1f7******"),
"id" : "CON123",
"title" : "Game",
"content" : {},
"status" : "ACTIVE",
"toggle":false,
"publisher" : "P789",
"interests" : [
"INT456"
],
"updatedAt" : NumberLong(1582078628264)
}
/* 2 */
{
"_id" : ObjectId("5d1f8******"),
"id" : "CON456",
"title" : "Home",
"content" : {},
"status" : "INACTIVE",
"toggle":true,
"publisher" : "P789",
"interests" : [
"INT456",
"INT789"
],
"updatedAt" : NumberLong(1582078628264)
}
/* 3 */
{
"_id" : ObjectId("5d0e9******"),
"id" : "CON654",
"title" : "School",
"content" : {},
"status" : "ACTIVE",
"toggle":false,
"publisher" : "P789",
"interests" : [
"INT123",
"INT456",
"INT789"
],
"updatedAt" : NumberLong(1582078628264)
}
/* 4 */
{
"_id" : ObjectId("5d207*******"),
"id" : "CON789",
"title":"Stack",
"content" : { },
"status" : "ACTIVE",
"toggle":false,
"publisher" : "P123",
"interests" : [
"INT123"
],
"updatedAt" : NumberLong(1582078628264)
}
What I am looking forward to as a result though is
{
"count" : 1.0, (2-1)
"iid" : "INT456",
"pid" : "P789"
}
{
"count" : 0.0, (1-1)
"iid" : "INT789",
"pid" : "P789"
}
{
"count" : 1.0,
"iid" : "INT123",
"pid" : "P789"
}
{
"count" : 1.0,
"iid" : "INT123",
"pid" : "P123"
}
This aggregation gives the desired result.
db.posts.aggregate( [
{ $match: { updatedAt: { $gte: 1549786260000 } } },
{ $facet: {
FALSE: [
{ $match: { toggle: false } },
{ $unwind : "$interests" },
{ $group : { _id : { iid: "$interests", pid: "$publisher" }, count: { $sum : 1 } } },
],
TRUE: [
{ $match: { toggle: true, status: "INACTIVE" } },
{ $unwind : "$interests" },
{ $group : { _id : { iid: "$interests", pid: "$publisher" }, count: { $sum : -1 } } },
]
} },
{ $project: { result: { $concatArrays: [ "$FALSE", "$TRUE" ] } } },
{ $unwind: "$result" },
{ $replaceRoot: { newRoot: "$result" } },
{ $group : { _id : "$_id", count: { $sum : "$count" } } },
{ $project:{ _id: 0, iid: "$_id.iid", pid: "$_id.pid", count: 1 } }
] )
[ EDIT ADD ]
The output from the query using the input data from the question post:
{ "count" : 1, "iid" : "INT123", "pid" : "P789" }
{ "count" : 1, "iid" : "INT123", "pid" : "P123" }
{ "count" : 0, "iid" : "INT789", "pid" : "P789" }
{ "count" : 1, "iid" : "INT456", "pid" : "P789" }
[ EDIT ADD 2 ]
This query gets the same result with different approach (code):
db.posts.aggregate( [
{
$match: { updatedAt: { $gte: 1549786260000 } }
},
{
$unwind : "$interests"
},
{
$group : {
_id : {
iid: "$interests",
pid: "$publisher"
},
count: {
$sum: {
$switch: {
branches: [
{ case: { $eq: [ "$toggle", false ] },
then: 1 },
{ case: { $and: [ { $eq: [ "$toggle", true] }, { $eq: [ "$status", "INACTIVE" ] } ] },
then: -1 }
]
}
}
}
}
},
{
$project:{
_id: 0,
iid: "$_id.iid",
pid: "$_id.pid",
count: 1
}
}
] )
[ EDIT ADD 3 ]
NOTE:
The facet query runs the two facets (TRUE and FALSE) on the same set of documents; it is like two queries running in parallel. But, there is some duplication of code as well as additional stages for shaping the documents down the pipeline to get the desired output.
The second query avoids the code duplication, and there are much lesser stages in the aggregation pipeline. This will make difference when the input dataset has a large number of documents to process - in terms of performance. In general, lesser stages means lesser iterations of the documents (as a stage has to scan the documents which are output from the previous stage).

How to get percentage total of data with group by date in MongoDB

How to get percentage total of data with group by date in MongoDB ?
Link example : https://mongoplayground.net/p/aNND4EPQhcb
I have some collection structure like this
{
"_id" : ObjectId("5ccbb96706d1d47a4b2ced4b"),
"date" : "2019-05-03T10:39:53.108Z",
"id" : 166,
"update_at" : "2019-05-03T10:45:36.208Z",
"type" : "image"
}
{
"_id" : ObjectId("5ccbb96706d1d47a4b2ced4c"),
"date" : "2019-05-03T10:39:53.133Z",
"id" : 166,
"update_at" : "2019-05-03T10:45:36.208Z",
"type" : "image"
}
{
"_id" : ObjectId("5ccbb96706d1d47a4b2ced4d"),
"date" : "2019-05-03T10:39:53.180Z",
"id" : 166,
"update_at" : "2019-05-03T10:45:36.208Z",
"type" : "image"
}
{
"_id" : ObjectId("5ccbb96706d1d47a4b2ced4e"),
"date" : "2019-05-03T10:39:53.218Z",
"id" : 166,
"update_at" : "2019-05-03T10:45:36.208Z",
"type" : "image"
}
And I have query in mongodb to get data of collection, how to get percentage of total data. in bellow example query to get data :
db.name_collection.aggregate(
[
{ "$match": {
"update_at": { "$gte": "2019-11-04T00:00:00.0Z", "$lt": "2019-11-06T00:00:00.0Z"},
"id": { "$in": [166] }
} },
{
"$group" : {
"_id": {
$substr: [ '$update_at', 0, 10 ]
},
"count" : {
"$sum" : 1
}
}
},
{
"$project" : {
"_id" : 0,
"date" : "$_id",
"count" : "$count"
}
},
{
"$sort" : {
"date" : 1
}
}
]
)
and this response :
{
"date" : "2019-11-04",
"count" : 39
},
{
"date" : "2019-11-05",
"count" : 135
}
how to get percentage data total from key count ? example response to this :
{
"date" : "2019-11-04",
"count" : 39,
"percentage" : "22%"
},
{
"date" : "2019-11-05",
"count" : 135,
"percentage" : "78%"
}
You have to group by null to get total count and then use $map to calculate the percentage. $round will be a useful operator in such case. Finally you can $unwind and $replaceRoot to get back the same number of documents:
db.collection.aggregate([
// previous aggregation steps
{
$group: {
_id: null,
total: { $sum: "$count" },
docs: { $push: "$$ROOT" }
}
},
{
$project: {
docs: {
$map: {
input: "$docs",
in: {
date: "$$this.date",
count: "$$this.count",
percentage: { $concat: [ { $toString: { $round: { $multiply: [ { $divide: [ "$$this.count", "$total" ] }, 100 ] } } }, '%' ] }
}
}
}
}
},
{
$unwind: "$docs"
},
{
$replaceRoot: { newRoot: "$docs" }
}
])
Mongo Playground

Query documents in collection A having field in the result of collection B query

I want to get all messages from userA given Messages collection and Room (i.e conversation) but can not find a way to do it similar to sql
The logic is quite simple in sql: just find any room having userA in it and then all messages belonging to the roomId above. I tried this query but it does not work. I also tried $lookup but failed
db.getCollection("message").find(
{
"roomId" :{
"$in" : db.getCollection("room").find(
{
"usernames" : "userA",
"msgs" : {
"$gt" : 20.0
}
},
{
"_id" : 1
}
)
}
},
{
"msg" : 1,
"u" : 1,
"roomId" : 1,
"ts" : 1
}
).sort( { "ts" : 1 } );
The following query can get you the expected output:
db.messages.aggreagte([
{
$lookup:{
"from":"room",
"let":{
"roomId":"$roomId"
},
"pipeline":[
{
$match:{
"usernames": "userA",
"msgs":{
$gt: 20
},
$expr:{
$eq:["$_id","$$roomId"]
}
}
},
{
$limit:1
},
{
$project:{
"_id":0,
"found": true
}
}
],
"as":"roomLookup"
}
},
{
$unwind: "$roomLookup"
},
{
$project:{
"msg" : 1,
"u" : 1,
"roomId" : 1,
"ts" : 1
}
},
{
$sort:{
"ts": 1
}
}
]).pretty()
Note: We are limiting the output in lookup as we don't need anything from there. Also, the unwind stage would only pass when the lookup is successful.

How to use $unwind and $match with MongoDB?

I have a document of the following format:
{
"P": {
"Workspaces": [
{
"Key": "Value1",
"Size": 2.27,
"Status": 'something'
},
{
"Key": "Value2",
"Size": 3.27,
"Status": 'somethingelse'
}
]
}
}
The following query returns the average correctly.
db.collection.aggregate([
{ $unwind: "$P.Workspaces" },
{ $group: { _id: "$P.Workspaces.Key", average: { $avg: "$P.Workspaces.Size" } } }
])
I am trying to add a match to filter the status as shown below. However I am not getting no result even though there are documents with matching status. I am trying to filter the results before taking the average. Am I missing something here?
db.collection.aggregate([
{ $unwind: "$P.Workspaces" },
{ $match: { "P.Workspaces.Status":'something'}},
{ $group: { _id: "$P.Workspaces.Key", average: { $avg: "$P.Workspaces.Size" } } }
])
db.articles.aggregate(
[ { $match : { author : "dave" } } ]
);
The examples use a collection named articles with the following documents:
{ "_id" : ObjectId("512bc95fe835e68f199c8686"), "author" : "dave", "score" : 80, "views" : 100 } { "_id" : ObjectId("512bc962e835e68f199c8687"), "author" : "dave", "score" : 85, "views" : 521 } { "_id" : ObjectId("55f5a192d4bede9ac365b257"), "author" : "ahn", "score" : 60, "views" : 1000 } { "_id" : ObjectId("55f5a192d4bede9ac365b258"), "author" : "li", "score" : 55, "views" : 5000 } { "_id" : ObjectId("55f5a1d3d4bede9ac365b259"), "author" : "annT", "score" : 60, "views" : 50 }