Aggregate Unexpected token : - mongodb

Below aggregate mongodb query gives
Unexpected token : error
db.getCollection("products_data").aggregate(
{
"$unwind": {
"path": "$color",
"preserveNullAndEmptyArrays": true
}
},
{
"$match":{
"country":"UK",
"$or":[{
"$and":[
"$or":
[{
"$and":[
{"status":"drafted"},
{"color":{$in:["blue"]}}
]},
{"$and":[
{"status1":"complete"},
{"status2":{$nin:["n/a","drafted","complete"]}},
{"color":{$in:["green"]}}
]}
]
]
},{
"$and":[
"$or":
[
{ "$and":[
{"status":"drafted"},
{"color":{$in:["blue"]}}
]},
{"$and":[
{"status1":"complete"},
{"status2":{$nin:["n/a","drafted","complete"]}},
{"color":{$in:["green"]}}
]}
]
]
}
]
}
},
{
"$group":{
"_id":"$field",
"products":{$sum: 1},
"bid":{"$push":"$product_id"}
}
},
{
"$project":{
"field":"$_id",
"products":"$products",
"bid":1,
"_id":0
}
}
);
To fetch the aggregate count for the given specified condition.

Correct syntax to use aggregate and it's stages in pipeline
db.getCollection("products_data").aggregate([
{ "$unwind": { "path": "$color", "preserveNullAndEmptyArrays": true }},
{ "$match": {
"country": "UK",
"$or": [
{
"$and": [
{
"$or": [
{ "$and": [{ "status": "drafted" }, { "color": { "$in": ["blue"] }}] },
{ "$and": [{ "status1": "complete" }, { "status2": { "$nin": ["n/a", "drafted", "complete"] }}, { "color": { "$in": ["green"] }}]}
]
}
]
},
{
"$and": [
{
"$or": [
{ "$and": [{ "status": "drafted" }, { "color": { "$in": ["blue"] }}] },
{ "$and": [{ "status1": "complete" }, { "status2": { "$nin": ["n/a", "drafted", "complete"] }}, { "color": { "$in": ["green"] }}] }
]
}
]
}
]
}},
{ "$group": {
"_id": "$field",
"products": { "$sum": 1 },
"bid": { "$push": "$product_id" }
}},
{ "$project": { "field": "$_id", "products": "$products", "bid": 1, "_id": 0 }}
])

Related

How to add a summary header/footer of a dataset within the same query using Mongodb

Let the following dataset (_id ommited for clarity sakes)
{ "model":"Nissan", "regId": 1230, "status": "active", "regCost" :100},
{ "model":"Nissan", "regId": 1231, "status": "active", "regCost" :100 },
{ "model":"Nissan", "regId": 1232, "status": "inactive", "regCost" :0},
{ "model":"Honda", "regId": 1233, "status": "active", "regCost" :90},
{ "model":"Honda", "regId": 1234, "status": "active", "regCost" :90},
{ "model":"Toyota", "regId": 1235, "status": "active", "regCost" :80}
Running the following query in Mongo
[
{
"$group": {
"_id": "$model",
"TotalActive": {
"$sum": {
"$cond": {
"if": {
"$eq": ["$status", "active"]
},
"then": 1,
"else": 0
}
}
},
"TotalCost" : {"$sum" : "$regCost"}
}
}
]
will give this above result:
The question is how can I modify my query in order to add a summary row like:
You can use below aggregation
db.collection.aggregate([
{ "$group": {
"_id": "$model",
"TotalActive": {
"$sum": {
"$cond": {
"if": {
"$eq": ["$status", "active"]
},
"then": 1,
"else": 0
}
}
},
"TotalCost": { "$sum": "$regCost" }
}},
{ "$facet": {
"total": [
{ "$group": {
"_id": "Total",
"TotalActive": { "$sum": "$TotalActive" },
"TotalCost": { "$sum": "$TotalCost" }
}}
],
"data": [{ "$match": {} }]
}},
{ "$project": {
"data": {
"$concatArrays": ["$data", "$total"]
}
}},
{ "$unwind": "$data" },
{ "$replaceRoot": { "newRoot": "$data" } }
])
MongoPlayground

can't convert from BSON type missing to Date

I have a mongoDB aggregation which was working great when I tried it on MongoDB 4.0 but now I need to use it on MongoDB 3.4 and it's not working, I can't find why. All I suppose it that the bug occurs in the $project stage.
Here's the aggregation query :
{
"aggregate": true,
"pipeline": [
{
"$match": {
"field": {
"$exists": true
},
"field.objects": {
"$exists": true,
"$ne": []
},
"created_at": {
"$gte": {
"sec": 1551398400,
"usec": 0
},
"$lte": {
"sec": 1554076799,
"usec": 0
}
}
}
},
{
"$unwind": "$field.objects"
},
{
"$lookup": {
"from": "Object",
"localField": "field.objects.id",
"foreignField": "_id",
"as": "objects"
}
},
{
"$match": { // Some match clauses here
}
},
{
"$group": {
"_id": "$_id"
}
},
{
"$project": {
"year": {
"$year": "$created_at"
},
"month": {
"$month": "$created_at"
}
}
},
{
"$group": {
"_id": {
"date": {
"$concat": [
{
"$substr": [
"$year",
0,
4
]
},
"-",
{
"$cond": [
{
"$lte": [
"$month",
9
]
},
{
"$concat": [
"0",
{
"$substr": [
"$month",
0,
2
]
}
]
},
{
"$substr": [
"$month",
0,
2
]
}
]
},
"-01"
]
}
},
"total": {
"$sum": 1
}
}
}
],
"options": {
"cursor": true
},
"db": "db",
"collection": "Collection"
}
So, with MongoDB 4.0, I get the right result, but MongoDB 3.4 throws the following : can't convert from BSON type missing to Date. I looked a bit at changelogs but I didn't find anything.

MongoDB: Get all $matched elements individually from an array

I'm trying to get all matched elements individually, here is the sample data and the query.
// json
[
{
"name": "Mr Cool",
"ican": [
{
"subcategory": [
{
"id": "5bffdba824488b182ec86f8d", "name": "Cricket"
},
{
"id": "5bffdba824488b182ec86f8c", "name": "Footbal"
}
],
"category": "5bffdba824488b182ec86f88",
"name": "Sports"
}
]
}
]
// query
db.collection.aggregate([
{
"$match": {
"ican.subcategory.name": { $in: ["Cricket","Football"] }
}
},
{
"$project": { "_id": 1, "name": 1, }
}
])
I'm getting the combined result, I need the individual match record. I tried $all and $elementMatch but getting the same response. how can I get the results as below. I'm using $aggregate because I will be using $geoNear pipeline for getting the nearby users.
// current result
[
{
"_id": ObjectId("5a934e000102030405000000"),
"name": "Mr Cool"
}
]
// expected result
[
{
"_id": ObjectId("5a934e000102030405000000"),
"name": "Mr Cool",
"subcategory: "Cricket"
},
{
"_id": ObjectId("5a934e000102030405000000"),
"name": "Mr Cool",
"subcategory: "Footbal"
}
]
Thank you
Try this Mongo Playground
db.col.aggregate([
{"$unwind" : "$ican"},
{"$unwind" : "$ican.subcategory"},
{"$match" : {"ican.subcategory.name": { "$in": ["Cricket","Football"] }}},
{"$group" : {"_id" : null,"data" : {"$push" : {"_id" : "$_id","name" : "$name","subcategory" : "$ican.subcategory.name"}}}},
{"$unwind" : "$data"},
{"$replaceRoot" : {"newRoot" : "$data"}}
])
You can use below aggregation without the $unwind and for better performance
db.collection.aggregate([
{ "$match": { "ican.subcategory.name": { "$in": ["Cricket","Football"] }}},
{ "$project": {
"ican": {
"$reduce": {
"input": "$ican",
"initialValue": [],
"in": {
"$concatArrays": [
{ "$filter": {
"input": {
"$map": {
"input": "$$this.subcategory",
"as": "s",
"in": { "name": "$name", "subcategory": "$$s.name" }
}
},
"as": "fil",
"cond": { "$in": ["$$fil.subcategory", ["Football"]] }
}},
"$$value"
]
}
}
}
}},
{ "$unwind": "$ican" },
{ "$replaceRoot": { "newRoot": "$ican" }}
])

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/
);

How to get count of multiple fields based on value in mongodb?

Collection exists as below:
[
{"currentLocation": "Chennai", "baseLocation": "Bengaluru"},
{"currentLocation": "Chennai", "baseLocation": "Bengaluru"},
{"currentLocation": "Delhi", "baseLocation": "Bengaluru"},
{"currentLocation": "Chennai", "baseLocation": "Chennai"}
]
Expected Output:
[
{"city": "Chennai", "currentLocationCount": 3, "baseLocationCount": 1},
{"city": "Bengaluru", "currentLocationCount": 0, "baseLocationCount": 3},
{"city": "Delhi", "currentLocationCount": 1, "baseLocationCount": 0}
]
What I have tried is:
db.getCollection('users').aggregate([{
$group: {
"_id": "$baselocation",
baseLocationCount: {
$sum: 1
}
},
}, {
$project: {
"_id": 0,
"city": "$_id",
"baseLocationCount": 1
}
}])
Got result as:
[
{"city": "Chennai", "baseLocationCount": 1},
{"city": "Bengaluru", "baseLocationCount": "3"}
]
I'm not familiar with mongo, so any help?
MongoDB Version - 3.4
Neil Lunn and myself had a lovely argument over this topic the other day which you can read all about here: Group by day with Multiple Date Fields.
Here are two solutions to your precise problem.
The first one uses the $facet stage. Bear in mind, though, that it may not be suitable for large collections because $facet produces a single (potentially huge) document that might be bigger than the current MongoDB document size limit of 16MB (which only applies to the result document and wouldn't be a problem during pipeline processing anyway):
collection.aggregate(
{
$facet:
{
"current":
[
{
$group:
{
"_id": "$currentLocation",
"currentLocationCount": { $sum: 1 }
}
}
],
"base":
[
{
$group:
{
"_id": "$baseLocation",
"baseLocationCount": { $sum: 1 }
}
}
]
}
},
{ $project: { "result": { $setUnion: [ "$current", "$base" ] } } }, // merge results into new array
{ $unwind: "$result" }, // unwind array into individual documents
{ $replaceRoot: { newRoot: "$result" } }, // get rid of the additional field level
{ $group: { "_id": "$_id", "currentLocationCount": { $sum: "$currentLocationCount" }, "baseLocationCount": { $sum: "$baseLocationCount" } } }, // group into final result)
{ $project: { "_id": 0, "city": "$_id", "currentLocationCount": 1, "baseLocationCount": 1 } } // group into final result
)
The second one works based on the $map stage instead:
collection.aggregate(
{
"$project": {
"city": {
"$map": {
"input": [ "current", "base" ],
"as": "type",
"in": {
"type": "$$type",
"name": {
"$cond": {
"if": { "$eq": [ "$$type", "current" ] },
"then": "$currentLocation",
"else": "$baseLocation"
}
}
}
}
}
}
},
{ "$unwind": "$city" },
{
"$group": {
"_id": "$city.name",
"currentLocationCount": {
"$sum": {
"$cond": {
"if": { "$eq": [ "$city.type", "current" ] },
"then": 1,
"else": 0
}
}
},
"baseLocationCount": {
"$sum": {
"$cond": {
"if": { "$eq": [ "$city.type", "base" ] },
"then": 1,
"else": 0
}
}
}
}
}
)