Here's the query I'm trying to run:
{ ownerId: 14,
'$or':
[ { '$or':
[ { '$and':
[ { provider: 'INSTAGRAM' },
{ tags:
{ '$or':
[ { '$in': [ 'skyhotel' ] },
{ '$or': [ { '$all': ["skyhotel","excellent"] } ] } ] } } ] },
{ '$and':
[ { provider: 'VKONTAKTE' },
{ tags:
{ '$or':
[ { '$in': [ 'skyhotel' ] },
{ '$or': [ { '$all': ["skyhotel","excellent"] } ] } ] } } ] } ] },
{ '$or':
[ { '$and':
[ { provider: 'INSTAGRAM' },
{ authorLogin: { '$in': [ 'valera92', 'petyan' ] } } ] },
{ '$and':
[ { provider: 'VKONTAKTE' },
{ authorLogin: { '$in': [ 'valera92' ] } } ] } ] },
{ '$or':
[ { '$and':
[ { provider: 'INSTAGRAM' },
{ locationId: { '$in': [ '32454234' ] } } ] } ] },
{ '$or':
[ { '$and':
[ { provider: 'INSTAGRAM' },
{ location: { '$or': [ { '$geoWithin': { '$centerSphere': [ [ '56.829782', '60.593162' ], 0.000012629451881788331 ] } } ] } } ] } ] } ] }
It seems to be malformed according to mongo standards. The error I'm getting is:
Can't canonicalize query: BadValue unknown operator: $or
What would be the proper way in which I could reformat this query?
EDIT: Document example
{
"_id" : ObjectId("570362332ee1a7ab1ecb9899"),
"proextid" : "INSTAGRAM_fdfsfsdfsdfwefwef2r3232",
"updatedAt" : ISODate("2016-04-05T06:58:59.683Z"),
"createdAt" : ISODate("2016-04-05T06:58:59.683Z"),
"ownerId" : 7,
"authorId" : "390599885",
"authorName" : "name",
"authorLogin" : "login",
"authorDetail" : {
"authorPicture" : "url",
"authorLink" : "url"
},
"externalCreatedAt" : ISODate("2015-08-29T22:42:04.000Z"),
"externalId" : "fdsfsdfsdfsdfwer2342342423423r23r",
"detailType" : "PHOTO",
"location" : [
0,
0
],
"locationId" : "",
"locationTitle" : "",
"provider" : "INSTAGRAM",
"detail" : {},
"description" : "hello",
"tags" : [
"ленинградскийпроспект",
"москва",
"архитектура",
"историческоенаследие",
"петровскийдворец"
],
"commentsCount" : 1,
"likesCount" : 40,
"groupName" : "",
"__v" : 0
}
Your query could be simplified as something like:
{
ownerId:14,
provider: { $in: [ 'INSTAGRAM', 'VKONTAKTE' ] },
'$or': [
{
tags: { $in: [ 'skyhotel', 'excellent' ] }
},
{
authorLogin: { $in: [ 'valera92', 'petyan' ] },
},
{
locationId:{ '$in':[ '32454234' ] }
},
{
location:{
{
'$geoWithin':{
'$centerSphere':[ [ '56.829782', '60.593162' ], 0.000012629451881788331 ]
}
}
}
}
]
}
Now this may not be exactly what you're looking for. But from what you asked, and the information you provided, this is the best suggestion I can give.
Related
idk if this is possible but need some help with mongo, I have the following document, and I want to make it so I can use $addToSet to add a value to one of the items in votes, but remove that item from all the other items in votes but have no idea how
{
_id: '872952643117518909',
questions: [
{ question: 'a', number: 1, dropDownInfo: [Object] },
{ question: 'b', number: 2, dropDownInfo: [Object] },
{ question: 'c', number: 3, dropDownInfo: [Object] }
],
votes: {
'1': [ '619284841187246090', '662697094104219678' ],
'2': [ '619284841187246090', '662697094104219678' ],
'3': [ '662697094104219678', '619284841187246090' ]
},
question: 'abc',
timestamp: 1628198528903,
finished: false,
channel: '812038854302892064'
}
The bellow pipeline adds a vote('619284841187246090') to a specific field,here randomly "2" was chosen,and removes that vote from "1" and "3" array.
Solution is general,can work with any vote fields not just "1" "2" "3".
You can use this pipeline in aggregation or update with pipeline (Mongodb>=4.2)
$addToSet doesn't work in arrays, it works when grouping and
in some other places in MongoDB 5.
I think your schema has a problem, because you are saving data in the schema, and that makes querying harder and creating indexing harder etc.
But we can still do it converting the object to array and back to object.
I think its best to keep data in arrays,and fields to be the known schema.
You can run the bellow code here
Query
db.collection.aggregate( [ {
"$addFields" : {
"votes" : {
"$arrayToObject" : {
"$map" : {
"input" : {
"$map" : {
"input" : {
"$objectToArray" : "$votes"
},
"as" : "m",
"in" : [ "$$m.k", "$$m.v" ]
}
},
"as" : "vote",
"in" : {
"$cond" : [ {
"$eq" : [ {
"$arrayElemAt" : [ "$$vote", 0 ]
}, "2" ]
}, [ {
"$arrayElemAt" : [ "$$vote", 0 ]
}, {
"$cond" : [ {
"$in" : [ "619284841187246090", {
"$arrayElemAt" : [ "$$vote", 1 ]
} ]
}, {
"$arrayElemAt" : [ "$$vote", 1 ]
}, {
"$concatArrays" : [ {
"$arrayElemAt" : [ "$$vote", 1 ]
}, [ "619284841187246090" ] ]
} ]
} ], [ {
"$arrayElemAt" : [ "$$vote", 0 ]
}, {
"$filter" : {
"input" : {
"$arrayElemAt" : [ "$$vote", 1 ]
},
"as" : "v",
"cond" : {
"$not" : [ {
"$eq" : [ "$$v", "619284841187246090" ]
} ]
}
}
} ] ]
}
}
}
}
}
} ])
Results
[
{
"_id": "872952643117518909",
"channel": "812038854302892064",
"finished": false,
"question": "abc",
"questions": [
{
"dropDownInfo": "",
"number": 1,
"question": "a"
},
{
"dropDownInfo": "",
"number": 2,
"question": "b"
},
{
"dropDownInfo": "",
"number": 3,
"question": "c"
}
],
"timestamp": 1.628198528903e+12,
"votes": {
"1": [
"662697094104219678"
],
"2": [
"619284841187246090",
"662697094104219678"
],
"3": [
"662697094104219678"
]
}
}
]
I cannot figure to add a new field with multiple conditions in MongoDB. Here is how DB looks like,
const db = [
{
_id: 1,
isActived: false, // Drafted
isScheduled: false,
isExpired: false
},
{
_id: 2,
isActived: true,
isScheduled: true, // Scheduled
isExpired: false
},
{
_id: 3,
isActived: true, // Expired
isScheduled: false,
isExpired: true
},
{
_id: 4,
isActived: true, // Actived
isScheduled: false,
isExpired: false
},
]
Conditions are:
if (!isActived) status = "Draft"
if (isActived && isScheduled) status = "Scheduled"
if (isActived && isExpired) status = "Expired"
if (isActived && !isScheduled && !isExpired) status = "Actived"
I can figure out only one condition and cannot solve it any further. Here is what I did so far
{
$addFields: {
status: {
$cond: [
{
$and: [
{ $eq: ["$isActived", true] },
{ $eq: ["$isScheduled", true] },
],
},
"Actived",
"Scheduled",
],
$cond: [
{
$and: [
{ $eq: ["$isActived", true] },
{ $eq: ["$isExpired", true] },
],
},
"Actived",
"Expired",
],
},
},
}
The result is not what I expected.
Any suggestion. Thank You.
You can concatenate if/else in this way:
Pseudocode:
if !isActived:
return "Draft"
else
if !isScheduled && !isExpired:
return "Actived"
else
if isScheduled:
return "Scheduled"
else
return "Expired" //<-- Since one of "isScheduled" and "isExpired" should be true
Take care on last line where may you can have isScheduled and isExpired to true, so you logic can change.
db.collection.aggregate([
{
"$set": {
"status": {
"$cond": {
"if": {
"$eq": [
"$isActived",
false
]
},
"then": "Draft",
"else": {
"$cond": {
"if": {
"$and": [
{
"$eq": [
"$isExpired",
false
]
},
{
"$eq": [
"$isScheduled",
false
]
}
]
},
"then": "Actived",
"else": {
"$cond": {
"if": {
"$eq": [
"$isScheduled",
true
]
},
"then": "Scheduled",
"else": "Expired"
}
}
}
}
}
}
}
}
])
Example here
Simple nested conditions, working solution
db.collection.aggregate([
{
$addFields: {
status: {
$cond: [
{
$and: [
{ $eq: [ "$isActived", true ] },
{ $eq: [ "$isScheduled", false ] },
{ $eq: [ "$isExpired", false ] },
],
},
"Actived",
{
$cond: [
{
$and: [
{ $eq: [ "$isActived", true ] },
{ $eq: [ "$isExpired", true ] },
],
},
"Expired",
{
$cond: [
{
$and: [
{ $eq: [ "$isActived", true ] },
{ $eq: [ "$isScheduled", true ] },
],
},
"Scheduled",
{
$cond: [
{
$and: [
{ $eq: [ "$isActived", false ] },
],
},
"Draft",
null,
],
},
],
},
],
},
],
},
},
},
])
I have an aggregate function that works well by grouping data from 2 collections by Month.
var pipeline = [
{
"$match": {
"merchant": new ObjectID("5f2a4e4efb740d9c6e810d67")
}
},
{
"$group": {
"_id": {
"$dateToString": {
"format": "%Y-%m",
"date": "$purchaseDate"
}
},
"totalWarrantyWholesalePrice": {
"$sum": "$warrantyWholesalePrice"
},
"totalWarrantyPrice": {
"$sum": "$warrantyPrice"
},
"totalSaleAmount": {
"$sum": "$purchasePrice"
},
"totalCoverAmount": {
"$sum": "$suggestedRetailPrice"
},
"totalContracts": {
"$sum": 1.0
}
}
},
{
"$lookup": {
"from": "transactions",
"let": {
"statementMonth": "$_id"
},
"pipeline": [
{
"$addFields": {
"transMonth": {
"$dateToString": {
"format": "%Y-%m",
"date": "$date"
}
}
}
},
{
"$match": {
"$expr": {
"$eq": [
"$$statementMonth",
"$transMonth"
]
}
}
}
],
"as": "transactions"
}
},
{
"$unwind": {
"path": "$transactions",
"preserveNullAndEmptyArrays": true
}
},
{
"$group": {
"_id": "$_id",
"totalContracts": {
"$first": "$totalContracts"
},
"totalWarrantyWholesalePrice": {
"$first": "$totalWarrantyWholesalePrice"
},
"totalWarrantyPrice": {
"$first": "$totalWarrantyPrice"
},
"totalSaleAmount": {
"$first": "$totalSaleAmount"
},
"totalCoverAmount": {
"$first": "$totalCoverAmount"
},
"totalAmountPaid": {
"$sum": "$transactions.amountPaid"
},
"totalAmountDue": {
"$sum": "$totalAmountDue"
}
}
},
{
"$addFields": {
"totalAmountDue": {
"$subtract": [
"$totalWarrantyPrice",
"$totalWarrantyWholesalePrice"
]
}
}
},
{
"$project": {
"_id": 1.0,
"totalContracts": 1.0,
"totalWarrantyWholesalePrice": 1.0,
"totalWarrantyPrice": 1.0,
"totalSaleAmount": 1.0,
"totalCoverAmount": 1.0,
"totalAmountPaid": 1.0,
"totalAmountDue": {
"$round": [
"$totalAmountDue",
2.0
]
},
"balance": {
"$round": [
{
"$subtract": [
"$totalAmountDue",
"$totalAmountPaid"
]
},
2.0
]
},
"status": {
"$switch": {
"branches": [
{
"case": {
"$eq": [
{
"$subtract": [
"$totalAmountDue",
"$totalAmountPaid"
]
},
0.0
]
},
"then": "PAID"
},
{
"case": {
"$and": [
{
"$gt": [
{
"$subtract": [
"$totalAmountDue",
"$totalAmountPaid"
]
},
0.0
]
},
{
"$gt": [
"$totalAmountPaid",
0.0
]
}
]
},
"then": "PARTIAL"
}
],
"default": "DUE"
}
}
}
},
{
"$sort": {
"_id": -1.0
}
}
];
This produces the following output:
{
"_id" : "2020-08",
"totalContracts" : 10.0,
"totalWarrantyWholesalePrice" : NumberInt(109),
"totalWarrantyPrice" : 163.55,
"totalSaleAmount" : NumberInt(9000),
"totalCoverAmount" : NumberInt(10000),
"totalAmountPaid" : 47.0,
"totalAmountDue" : 54.55,
"balance" : 7.55,
"status" : "PARTIAL"
}
{
"_id" : "2020-07",
"totalContracts" : 1.0,
"totalWarrantyWholesalePrice" : NumberInt(23),
"totalWarrantyPrice" : NumberInt(40),
"totalSaleAmount" : NumberInt(900),
"totalCoverAmount" : NumberInt(1000),
"totalAmountPaid" : NumberInt(0),
"totalAmountDue" : NumberInt(17),
"balance" : NumberInt(17),
"status" : "DUE"
}
{
"_id" : "2020-06",
"totalContracts" : 1.0,
"totalWarrantyWholesalePrice" : NumberInt(0),
"totalWarrantyPrice" : NumberInt(0),
"totalSaleAmount" : NumberInt(900),
"totalCoverAmount" : NumberInt(1000),
"totalAmountPaid" : NumberInt(0),
"totalAmountDue" : NumberInt(0),
"balance" : NumberInt(0),
"status" : "PAID"
}
However, I would like to summarise the monthly groups, while also displaying the monthly summaries. I can work out how to complete either with two aggregators, however I would like to consolidate this into one. How can I achieve the following?
{
lifetimeTotals: {
"totalContracts" : 12.0,
"totalWarrantyWholesalePrice" : NumberInt(132),
"totalWarrantyPrice" : 203.55,
"totalSaleAmount" : NumberInt(9900),
"totalCoverAmount" : NumberInt(11000),
"totalAmountPaid" : 47.0,
"totalAmountDue" : 71.55,
"balance" : 24.55,
},
monthTotals: {
"2020-08": {
"totalContracts" : 10.0,
"totalWarrantyWholesalePrice" : NumberInt(109),
"totalWarrantyPrice" : 163.55,
"totalSaleAmount" : NumberInt(9000),
"totalCoverAmount" : NumberInt(10000),
"totalAmountPaid" : 47.0,
"totalAmountDue" : 54.55,
"balance" : 7.55,
"status" : "PARTIAL"
},
"2020-07": {
"totalContracts" : 1.0,
"totalWarrantyWholesalePrice" : NumberInt(23),
"totalWarrantyPrice" : NumberInt(40),
"totalSaleAmount" : NumberInt(900),
"totalCoverAmount" : NumberInt(1000),
"totalAmountPaid" : NumberInt(0),
"totalAmountDue" : NumberInt(17),
"balance" : NumberInt(17),
"status" : "DUE"
}
}
},
You can acheive this with $facet. I wrote the a mongo query based on your last output.
$facet helps you to categorize the incoming fields. So by using $group, we get all sum inside lifetimeTotals[] as one field
$map helps to run whole / modify the current objects.
$arrayToObject is alwasy looking for key value pair. (k:v). We already got it with $map. key (k) as "_id" and value (v) as "{REST OF OTHER FIELDS}".
Here is the code
[
{
$facet: {
lifetimeTotals: [
{
$match: {
_id: {
$exists: true
}
}
},
{
$group: {
_id: null,
totalContracts: {
$sum: "$totalContracts"
},
totalWarrantyWholesalePrice: {
$sum: "$totalWarrantyWholesalePrice"
},
totalWarrantyPrice: {
$sum: "$totalWarrantyPrice"
},
/** Rest of other fields*/
}
}
],
months: [
{
$match: {
_id: {
$exists: true
}
}
}
]
}
},
{
$project: {
lifetimeTotals: 1,
monthTotals: {
$arrayToObject: {
$map: {
input: "$months",
in: {
k: "$$this._id",
v: {
totalContracts: "$$this.totalContracts",
totalWarrantyWholesalePrice: "$$this.totalWarrantyWholesalePrice",
totalWarrantyPrice: "$$this.totalWarrantyPrice"
/** Rest of other fields*/
}
}
}
}
}
}
}
]
Working Mongo playground
Assume I have the following document:
[
{
"callId": "17dac51e-125e-499e-9064-f20bd3b1a9d8",
"caller": {
"firstName": "Test",
"lastName": "Testing",
"phoneNumber": "1231231234"
},
"inquiries": [
{
"inquiryId": "b0d14381-ce75-49aa-a66a-c36ae20b72a8",
"routeHistory": [
{
"assignedUserId": "cfa0ffe9-c77d-4eec-87d7-4430f7772e81",
"routeDate": "2020-01-01T06:00:00.000Z",
"status": "routed"
},
{
"assignedUserId": "cfa0ffe9-c77d-4eec-87d7-4430f7772e81",
"routeDate": "2020-01-03T06:00:00.000Z",
"status": "ended"
}
]
},
{
"inquiryId": "9d743be9-7613-46d7-8f9b-a04b4b899b56",
"routeHistory": [
{
"assignedUserId": "cfa0ffe9-c77d-4eec-87d7-4430f7772e81",
"routeDate": "2020-01-01T06:00:00.000Z",
"status": "routed"
},
{
"assignedUserId": "cfa0ffe9-c77d-4eec-87d7-4430f7772e81",
"routeDate": "2020-01-03T06:00:00.000Z",
"status": "ended"
}
]
}
]
}
]
I want to get results where inquiries.routeHistory.routeDate is equal to the $max routeDate value in routeHistory. I would expect my results to look like the following:
[
{
"callId": "17dac51e-125e-499e-9064-f20bd3b1a9d8",
"caller": {
"firstName": "Test",
"lastName": "Testing",
"phoneNumber": "1231231234"
},
"inquiries": [
{
"inquiryId": "b0d14381-ce75-49aa-a66a-c36ae20b72a8",
"routeHistory": [
{
"assignedUserId": "cfa0ffe9-c77d-4eec-87d7-4430f7772e81",
"routeDate": "2020-01-03T06:00:00.000Z",
"status": "ended"
}
]
},
{
"inquiryId": "9d743be9-7613-46d7-8f9b-a04b4b899b56",
"routeHistory": [
{
"assignedUserId": "cfa0ffe9-c77d-4eec-87d7-4430f7772e81",
"routeDate": "2020-01-03T06:00:00.000Z",
"status": "ended"
}
]
}
]
}
]
Is there a clean way to do this in a single aggregate, so that additional $match criteria can be applied? One caveat is that I can only use operators supported by DocumentDB: https://docs.aws.amazon.com/documentdb/latest/developerguide/mongo-apis.html
I have tried the following code, but to no avail:
{
$addFields: {
maxDate: {
$max: '$inquiries.routeHistory.routeDate',
},
},
},
{
$addFields: {
routeHistory: [
{
$arrayElemAt: [
{
$filter: {
input: '$inquiries.routeHistory',
cond: {
$eq: ['$maxDate', '$$this.routeDate'
],
},
},
},
0,
],
},
],
},
}
You have to use $map to scan outer array and to $filter to compare inner array's elements against $max date:
db.collection.aggregate([
{
$addFields: {
inquiries: {
$map: {
input: "$inquiries",
as: "inquiry",
in: {
inquiryId: "$$inquiry.inquiryId",
routeHistory: {
$filter: {
input: "$$inquiry.routeHistory",
cond: {
$eq: [ { $max: "$$inquiry.routeHistory.routeDate" }, "$$this.routeDate" ]
}
}
}
}
}
}
}
}
])
Mongo Playground
EDIT: by looking at your link I've noticed that $map is not supported, you can use below combination as a workaround:
db.collection.aggregate([
{
$unwind: "$inquiries"
},
{
$addFields: {
"inquiries.routeHistory": {
$filter: {
input: "$inquiries.routeHistory",
cond: {
$eq: [ { $max: "$inquiries.routeHistory.routeDate" }, "$$this.routeDate" ]
}
}
}
}
},
{
$group: {
_id: "$_id",
callId: { $first: "$callId" },
caller: { $first: "$caller" },
inquiries: { $push: "$inquiries" }
}
}
])
Mongo Playground (2)
So for my example database set up:
db.lists.insertMany([
{ _id: "1", name: "list1", included_lists: ["2"], items: ["i1"] },
{ _id: "2", name: "list2", included_lists: [], items: ["i2", "i3"] }
])
db.items.insertMany([
{ _id: "i1", name: "item1", details: [{}, {}, {}] },
{ _id: "i2", name: "item2", details: [{}, {}, {}] },
{ _id: "i3", name: "item3", details: [{}, {}, {}] }
])
I'm currently getting my items data via:
db.lists.aggregate([
{ "$match": { "_id": { "$in": ["1", "2"] } } },
{
"$lookup": {
"from": "items",
"localField": "items",
"foreignField": "_id",
"as": "item"
}
},
{ "$unwind": "$item" },
{
"$facet": {
"results": [
{ "$skip": 0 },
{ "$limit": 10 },
{
"$project": {
name: 1,
item: 1
}
}
],
"total": [
{ "$count": "total" },
]
}
}
]).pretty()
which returns:
{
"results" : [
{
"_id" : "1",
"name" : "list1",
"item" : {
"_id" : "i1",
"name" : "item1",
"details" : [
{
},
{
},
{
}
]
}
},
{
"_id" : "2",
"name" : "list2",
"item" : {
"_id" : "i2",
"name" : "item2",
"details" : [
{
},
{
},
{
}
]
}
},
{
"_id" : "2",
"name" : "list2",
"item" : {
"_id" : "i3",
"name" : "item3",
"details" : [
{
},
{
},
{
}
]
}
}
],
"total" : [
{
"total" : 3
}
]
}
What I'm trying to do, is remove the { "$match": { "_id": { "$in": ["1", "2"] } } }, as I want to remove the query needed to get the array of ids, and instead just get all the ids from list _id and its included_lists ids. Then have return all the items return like my result.
This question is similar to: mongodb - unwinding nested subdocuments but I've reasked due to ambiguity and lack of db documents.
you can do it with graph lookup and then group
db.lists.aggregate([
{ "$match": { "_id": { "$in": ["1"] } } },
{
$graphLookup: {
from: "lists",
startWith: "$_id" ,
connectFromField: "included_lists",
connectToField: "_id",
as: "connected",
}
},
{$unwind:"$connected"},
{ $group:{_id:"$connected._id",items:{$first:'$connected.items'},name:{$first:'$connected.name'}}},
{
"$lookup": {
"from": "items",
"localField": "items",
"foreignField": "_id",
"as": "item"
}
},
{ "$unwind": "$item" },
{
"$facet": {
"results": [
{ "$skip": 0 },
{ "$limit": 10 },
{
"$project": {
name: 1,
item: 1
}
}
],
"total": [
{ "$count": "total" },
]
}
}
]).pretty()