Filter documents that have id in another collection in MongoDB with aggregation framework - mongodb

So I have two collection. collectionA and collectionB
collection A has following documents
db={
"collectiona": [
{
"_id": "6173ddf33ed09368a094e68a",
"title": "a"
},
{
"_id": "61wefdf33ed09368a094e6dc",
"title": "b"
},
{
"_id": "61wefdfewf09368a094ezzz",
"title": "c"
},
],
"collectionb": [
{
"_id": "6173ddf33ed0wef368a094zq",
"collectionaID": "6173ddf33ed09368a094e68a",
"data": [
{
"userID": "123",
"visibility": false,
"response": false
},
{
"userID": "2345",
"visibility": true,
"response": true
}
]
},
{
"_id": "6173ddf33ed09368awef4e68g",
"collectionaID": "61wefdf33ed09368a094e6dc",
"data": [
{
"userID": "5678",
"visibility": false,
"response": false
},
{
"userID": "674",
"visibility": true,
"response": false
}
]
}
]
}
So What I need is documents from collection A which has response false in collection B
and document should be sorted by first the ones that have visibility false and then the ones that have visibility true
for eg. userID : 123 should get 3 documents
{
"_id": "6173ddf33ed09368a094e68a",
"title": "a"
},
{
"_id": "61wefdf33ed09368a094e6dc",
"title": "b"
},
{
"_id": "61wefdfewf09368a094ezzz",
"title": "c"
},
whereas userID 2345 should get two
{
"_id": "61wefdf33ed09368a094e6dc",
"title": "b"
},
{
"_id": "61wefdfewf09368a094ezzz",
"title": "c"
},
User 674 will receive 3 objects from collection A but second would be in the last as it has visibility true for that document
{
"_id": "6173ddf33ed09368a094e68a",
"title": "a"
},
{
"_id": "61wefdfewf09368a094ezzz",
"title": "c"
},
{
"_id": "61wefdf33ed09368a094e6dc",
"title": "b"
},
MongoDB Playground link : https://mongoplayground.net/p/3rLry0FPlw-
Really appreciate the help. Thanks

You can start from collectionA:
$lookup the collectionB for the record related to the user specified
filter out collectionB documents according to response
assign a helper sortrank field based on the visibility and whether collectionaID is a match
$sort according to sortrank
wrangle back to the raw collection A
db.collectiona.aggregate([
{
"$lookup": {
"from": "collectionb",
let: {
aid: "$_id"
},
"pipeline": [
{
$unwind: "$data"
},
{
$match: {
$expr: {
$and: [
{
$eq: [
"$data.userID",
"2345"
]
},
{
$eq: [
"$collectionaID",
"$$aid"
]
}
]
}
}
}
],
"as": "collB"
}
},
{
$match: {
"collB.data.response": {
$ne: true
}
}
},
{
"$unwind": {
path: "$collB",
preserveNullAndEmptyArrays: true
}
},
{
"$addFields": {
"sortrank": {
"$cond": {
"if": {
$eq: [
"$collB.data.visibility",
false
]
},
"then": 1,
"else": {
"$cond": {
"if": {
$eq: [
"$collB.collectionaID",
"$_id"
]
},
"then": 3,
"else": 2
}
}
}
}
}
},
{
$sort: {
sortrank: 1
}
},
{
$project: {
collB: false,
sortrank: false
}
}
])
Here is the Mongo playground for your reference.

Related

Add Aggregate field in MongoDB pipeline depending on all elements of an array

Given the following documents in a collection:
[{
"_id": {
"$oid": "63f06283b80a395adf27780d"
},
"suppliers": [
{
"name": "S1",
"duesPaid": true
},
{
"name": "S2",
"duesPaid": true
}
]
},{
"_id": {
"$oid": "63f06283b80a395adf27780e"
},
"suppliers": [
{
"name": "S1",
"duesPaid": true
},
{
"name": "S2",
"duesPaid": false
}
]
}]
I would like to create an aggregateField in each document that does the following: If the suppliers array has at least 1 element and every element in that has the duesPaid field == true, then add a field to the document suppliersPaid = true. Otherwise add suppliersPaid = false. The resulting documents from the pipeline should look like this:
[{
"_id": {
"$oid": "63f06283b80a395adf27780d"
},
"suppliers": [
{
"name": "S1",
"duesPaid": true
},
{
"name": "S2",
"duesPaid": true
}
],
"suppliersPaid": true,
},{
"_id": {
"$oid": "63f06283b80a395adf27780e"
},
"suppliers": [
{
"name": "S1",
"duesPaid": true
},
{
"name": "S2",
"duesPaid": false
}
],
"suppliersPaid": false,
}]
I have tried the following pipeline:
[{$addFields: {
suppliersPaid: {
$and: [
{ $gte: [{ $size: "$suppliers" }, 1] },
{
suppliers: {
$not: {
$elemMatch: { duesPaid: false },
},
},
},
],
},
}}]
and I get the following error: Invalid $addFields :: caused by :: Unrecognized expression '$elemMatch'
I've tried to eliminate the reliance on $elemMatch per the docs https://www.mongodb.com/docs/manual/reference/operator/query/elemMatch/#single-query-condition as such:
[{$addFields: {
suppliersPaid: {
$and: [
{ $gte: [{ $size: "$suppliers" }, 1] },
{
suppliers: {
$not: {
duesPaid: false
},
},
},
],
},
}}]
But this yields the incorrect result of setting suppliersPaid to true for both documents, which is incorrect.
Note: I would like to avoid using any sort of JS in this code i.e. no $where operators.
For the second condition:
$eq - Compare the result from 1.1 to return an empty array.
1.1. $filter - Filter the documents from suppliers containing { duesPaid: false }.
db.collection.aggregate([
{
$addFields: {
suppliersPaid: {
$and: [
{
$gte: [
{
$size: "$suppliers"
},
1
]
},
{
$eq: [
{
$filter: {
input: "$suppliers",
cond: {
$eq: [
"$$this.duesPaid",
false
]
}
}
},
[]
]
}
]
}
}
}
])
Demo # Mongo Playground

MongoDB. Get every element from array in new field

I have a document with a nested array array_field:
{
"_id": {
"$oid": "1"
},
"id": "1",
"array_field": [
{
"data": [
{
"regions": [
{
"result": {
"item": [
"4",
"5",
"3"
]
}
},
{
"result": {
"item": [
"5"
]
}
},
{
"result": {
"item": [
"1"
]
}
}
]
}
]
}
]
}
I need add new field, new_added_field for example, with each array element from array_field.data.regions.result.item and remove array_field from document.
For example:
{
"_id": {
"$oid": "1"
},
"id": "1",
"new_added_field": [4,5,3,5,1]
}
I think i can do this with help of $unwind or $map but have difficulties and need dome hint, how i can do it with help op aggregation?
As you said,
db.collection.aggregate([
{
"$project": {
newField: {
"$map": {
"input": "$array_field",
"as": "m",
"in": "$$m.data.regions.result.item"
}
}
},
},
{ "$unwind": "$newField" },
{ "$unwind": "$newField" },
{ "$unwind": "$newField" },
{ "$unwind": "$newField" },
{
"$group": {
"_id": "$_id",
"newField": { "$push": "$newField" }
}
}
])
Working Mongo playground

MongoDb aggregation with arrays inside an array possible

I am struggling to find some examples of using the mongo aggregation framework to process documents which has an array of items where each item also has an array of other obejects (array containing an array)
In the example document below what I would really like is an example that sums the itemValue in the results array of all cases in the document and accross the collection where the result.decision was 'accepted'and group by the document locationCode
However, even an example that found all documents where the result.decision was 'accepted' to show or that summmed the itemValue for the same would help
Many thanks
{
"_id": "333212",
"data": {
"locationCode": "UK-555-5566",
"mode": "retail",
"caseHandler": "A N Other",
"cases": [{
"caseId": "CSE525666",
"items": [{
"id": "333212-CSE525666-1",
"type": "hardware",
"subType": "print cartridge",
"targetDate": "2020-06-15",
"itemDetail": {
"description": "acme print cartridge",
"quantity": 2,
"weight": "1.5"
},
"result": {
"decision": "rejected",
"decisionDate": "2019-02-02"
},
"isPriority": true
},
{
"id": "333212-CSE525666-2",
"type": "Stationery",
"subType": "other",
"targetDate": "2020-06-15",
"itemDetail": {
"description": "staples box",
"quantity": 3,
"weight": "1.66"
},
"result": {
"decision": "accepted",
"decisionDate": "2020-03-03",
"itemValue": "23.01"
},
"isPriority": true
}
]
},
{
"caseId": "CSE885655",
"items": [{
"id": "333212-CSE885655-1",
"type": "marine goods",
"subType": "fish food",
"targetDate": "2020-06-04",
"itemDetail": {
"description": "fish bait",
"quantity": 5,
"weight": "0.65"
},
"result": {
"decision": "accepted",
"decisionDate": "2020-03-02"
},
"isPriority": false
},
{
"id": "333212-CSE885655-4",
"type": "tobacco products",
"subType": "cigarettes",
"deadlineDate": "2020-06-15",
"itemDetail": {
"description": "rolling tobbaco",
"quantity": 42,
"weight": "2.25"
},
"result": {
"decision": "accepted",
"decisionDate": "2020-02-02",
"itemValue": "48.15"
},
"isPriority": true
}
]
}
]
},
"state": "open"
}
You're probably looking for $unwind. It takes an array within a document and creates a separate document for each array member.
{ foos: [1, 2] } -> { foos: 1 }, { foos: 2}
With that you can create a flat document structure and match & group as normal.
db.collection.aggregate([
{
$unwind: "$data.cases"
},
{
$unwind: "$data.cases.items"
},
{
$match: {
"data.cases.items.result.decision": "accepted"
}
},
{
$group: {
_id: "$data.locationCode",
value: {
$sum: {
$toDecimal: "$data.cases.items.result.itemValue"
}
}
}
},
{
$project: {
_id: 0,
locationCode: "$_id",
value: "$value"
}
}
])
https://mongoplayground.net/p/Xr2WfFyPZS3
Alternative solution...
We group by data.locationCode and sum all items with this condition:
cases[*].items[*].result.decision" == "accepted"
db.collection.aggregate([
{
$group: {
_id: "$data.locationCode",
itemValue: {
$sum: {
$reduce: {
input: "$data.cases",
initialValue: 0,
in: {
$sum: {
$concatArrays: [
[ "$$value" ],
{
$map: {
input: {
$filter: {
input: "$$this.items",
as: "f",
cond: {
$eq: [ "$$f.result.decision", "accepted" ]
}
}
},
as: "item",
in: {
$toDouble: {
$ifNull: [ "$$item.result.itemValue", 0 ]
}
}
}
}
]
}
}
}
}
}
}
}
])
MongoPlayground

MongoDB multiple counts, single document, arrays

I have been searching on stackoverflow and cannot find exactly what I am looking for and hope someone can help. I want to submit a single query, get multiple counts back, for a single document, based on array of that document.
My data:
db.myCollection.InsertOne({
"_id": "1",
"age": 30,
"items": [
{
"id": "1",
"isSuccessful": true,
"name": null
},{
"id": "2",
"isSuccessful": true,
"name": null
},{
"id": "3",
"isSuccessful": true,
"name": "Bob"
},{
"id": "4",
"isSuccessful": null,
"name": "Todd"
}
]
});
db.myCollection.InsertOne({
"_id": "2",
"age": 22,
"items": [
{
"id": "6",
"isSuccessful": true,
"name": "Jeff"
}
]
});
What I need back is the document and the counts associated to the items array for said document. In this example where the document _id = "1":
{
"_id": "1",
"age": 30,
{
"totalIsSuccessful" : 2,
"totalNotIsSuccessful": 1,
"totalSuccessfulNull": 1,
"totalNameNull": 2
}
}
I have found that I can get this in 4 queries using something like this below, but I would really like it to be one query.
db.test1.aggregate([
{ $match : { _id : "1" } },
{ "$project": {
"total": {
"$size": {
"$filter": {
"input": "$items",
"cond": { "$eq": [ "$$this.isSuccessful", true ] }
}
}
}
}}
])
Thanks in advance.
I am assuming your expected result is invalid since you have an object literal in the middle of another object and also you have totalIsSuccessful for id:1 as 2 where it seems they should be 3. With that said ...
you can get similar output via $unwind and then grouping with $sum and $cond:
db.collection.aggregate([
{ $match: { _id: "1" } },
{ $unwind: "$items" },
{ $group: {
_id: "_id",
age: { $first: "$age" },
totalIsSuccessful: { $sum: { $cond: [{ "$eq": [ "$items.isSuccessful", true ] }, 1, 0 ] } },
totalNotIsSuccessful: { $sum: { $cond: [{ "$ne": [ "$items.isSuccessful", true ] }, 1, 0 ] } },
totalSuccessfulNull: { $sum: { $cond: [{ "$eq": [ "$items.isSuccessful", null ] }, 1, 0 ] } },
totalNameNull: { $sum: { $cond: [ { "$eq": [ "$items.name", null ]}, 1, 0] } } }
}
])
The output would be this:
[
{
"_id": "_id",
"age": 30,
"totalIsSuccessful": 3,
"totalNameNull": 2,
"totalNotIsSuccessful": 1,
"totalSuccessfulNull": 1
}
]
You can see it working here

Use $Redact with a regular expression

In an aggregation pipeline, I am trying to filter some elements of an array of objects, based on the value of a field in this object.
Let's say that I have this entry:
{
"_id": "5b8911d346d19645f8a66bf4",
"title": "test task",
"creation_date": "2018-08-31T10:00:51.598Z",
"logs": [
{
"_id": "5b89126c46d19645f8a66bfb",
"content": "Running"
},
{
"_id": "5b89128646d19645f8a66bfd",
"content": "Stopping"
},
{
"_id": "5b89128646d19645f8a66bfd",
"content": "Stopped"
}
]
}
My objectif is to filter only the logs containing the stop word in their content:
{
"_id": "5b8911d346d19645f8a66bf4",
"title": "test task",
"creation_date": "2018-08-31T10:00:51.598Z",
"logs": [
{
"_id": "5b89128646d19645f8a66bfd",
"content": "Stopping"
},
{
"_id": "5b89128646d19645f8a66bfd",
"content": "Stopped"
}
]
}
I tried to use $redact to eliminate all the logs that does not contain the stop word:
$redact: {
$cond: {
if: { $match: { "logs.content": { $regex: "stop", $options: 'i' }}},
then: "$$KEEP",
else: "$$PRUNE"
}
}
but I keep getting the error:
Unrecognized expression '$match'
You can try below aggregation
db.collection.aggregate([
{ "$addFields": {
"logs": {
"$filter": {
"input": "$logs",
"cond": {
"$ne": [
{ "$indexOfBytes": [
{ "$toUpper": "$$this.content" },
{ "$toUpper": "stop" }
]},
-1
]
}
}
}
}}
])
Output
[
{
"_id": "5b8911d346d19645f8a66bf4",
"creation_date": "2018-08-31T10:00:51.598Z",
"logs": [
{
"_id": "5b89128646d19645f8a66bfd",
"content": "Stopping"
},
{
"_id": "5b89128646d19645f8a66bfd",
"content": "Stopped"
}
],
"title": "test task"
}
]
As per your requirement below query is working and it is properly tested
db.users.aggregate(
// Pipeline
[
// Stage 1
{
$unwind: {
path : "$logs",
preserveNullAndEmptyArrays : true // optional
}
},
// Stage 2
{
$group: {
_id: "$_id",
"title" :{$last:"$title"} ,
"creation_date" :{$last:"$creation_date"},
logs: {
$push: {
$cond: [ {$or:[{"$eq":[{ "$substr": [ "$logs.content", 0, 4 ] }, "Stop"]},{"$eq":[{ "$substr": [ "$logs.content", 0, 4 ] }, "stop"]}]},{"_id":"$logs._id","content":"$logs.content"},null]
}
}
}
},
// Stage 3
{
$project: {
logs: {
$filter: {
input: "$logs",
as: "log",
cond: { $ne: [ "$$log", null ] }
}
}
}
},
]
// Created with Studio 3T, the IDE for MongoDB - https://studio3t.com/
);