I have a collection in Mongo where the structure is known.
It consists of
{
"_id" : "123456__data",
"fields" : {
"field1" : {
"type" : "boolean",
"writeAccess" : "someWriteAccess",
},
"field2" : {
"type" : "integer",
"writeAccess" : "secondWriteAccess",
},
"field3" : {
"someConcretePermissionOperation" : "set",
"writeAccess" : "thirdWriteAccess",
}
}
}
Now I got to find all documents, and preferably, all concrete values of "someConcretePermissionOperation", while the "field1", 2, 3 can be up to the user. That is, in different documents they could have different names. The only thing I know is the constant depth - if someConcretePermissionOperation will appear it will be under fields.XXX.someConcretePermissionOperation, where XXX can be anything.
Anybody got any ideas?
Just found something close to what I am looking for:
var operationOptions = [ "push", "set", "pushUnique" ];
db.mytable.aggregate(
[
{ $redact:
{
$cond:
{
if: { $gt: [ { $size: { $setIntersection: [ "$someConcretePermissionOperation", operationOptions ] } }, 0 ] },
then: "$$DESCEND",
else: "$$PRUNE"
}
}
}
]
)
But receiving
uncaught exception: aggregate failed: {
"errmsg" : "exception: The argument to $size must be an Array, but was of type: NULL",
Don't exactly know yet how to write "if exists, otherwise disregard" in this aggregation query.
Use the $ifNull operator to check if the field is either not an array or not present:
var operationOptions = [ "push", "set", "pushUnique" ];
db.mytable.aggregate(
[
{ $redact:
{
$cond:
{
if: {
$gt: [
{
$size: {
"$ifNull": [
{ $setIntersection: ["$someConcretePermissionOperation", operationOptions] },
[]
]
}
},
0
]
},
then: "$$DESCEND",
else: "$$PRUNE"
}
}
}
]
)
Related
I am trying to use the MongoDB $lookup with the Uncorrelated Subqueries.
Using MongoDB 3.6.12 (support began on 3.6)
https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/#join-conditions-and-uncorrelated-sub-queries
The following pipeline step is working, however if I swap out the first "userB" with the second, no results are returned.
{
from: 'friendships',
let: { requestuser: ObjectId("5c0a9c37b2365a002367df79"), postuser: ObjectId("5c0820ea17a69b00231627be") },
pipeline: [
{
$match : {
$or : [
{
"userA" : ObjectId("5c0820ea17a69b00231627be"),
"userB" : ObjectId("5c0a9c37b2365a002367df79")
// "userB" : '$$requestuser'
},
{
"userA" : ObjectId("5c0a9c37b2365a002367df79"),
"userB" : ObjectId("5c0820ea17a69b00231627be")
}
],
"accepted" : true
}
},
{
$project: {v: 'true'}
}
],
as: "match"}
Results with hard coded ObjectId:
"match" : [
{
"_id" : ObjectId("5d6171dd319401001fd326bf"),
"v" : "true"
}
]
Results using variable:
"match" : [
]
I feel like ObjectIds need special treatment. All the examples I could find are using simple variables like strings.
To verify the '$$requestUser' contained a value, I tested it on the projection:
"match" : [
{
"_id" : ObjectId("5d6171dd319401001fd326bf"),
"v" : ObjectId("5c0a9c37b2365a002367df79")
}
]
When you use un co-related sub queires, you need to use $expr to pass a variable.
You can try something like following.
{
$match: {
$expr: {
$or: [
{
$and:[
{
$eq: [ "userA", ObjectId("5c0820ea17a69b00231627be") ]
},
{
$eq: [ "userB", ObjectId("5c0a9c37b2365a002367df79") ]
},
{
$eq: [ "userB", "$$requestuser" ]
}
]
},
{
$and:[
{
$eq: [ "userA", ObjectId("5c0a9c37b2365a002367df79") ]
},
{
$eq: [ "userB", ObjectId("5c0820ea17a69b00231627be") ]
}
]
}
]
},
"accepted": true,
}
}
I have created a sample demo to show how $expr works inside the lookup : Sample demo for Uncorrelated Subquery
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
I have the following document stored in mongo:
{
"_id" : ObjectId("5d1a08d2329a3c1374f176df"),
"associateID" : "1234567",
"associatePreferences" : [
{
"type" : "NOTIFICATION",
"serviceCode" : "service-code",
"eventCode" : "test-template",
"preferences" : [
"TEXT",
"EMAIL"
]
},
{
"type" : "URGENT_NOTIFICATION",
"serviceCode" : "service-code",
"eventCode" : "test-template",
"preferences" : [
"TEXT"
]
}
]
}
I am basically trying to query one of the elements of the associatePreferences array based off of its type, serviceCode, and eventCode and add a new value to the preferences array. However, if that combination of type, serviceCode, and eventCode is not present, I would like to add a new element to the associatePreferences array with those values. This is my current query:
db.user_communication_preferences.update(
{'associateID':'testassociate'},
{$addToSet:{'associatePreferences.$[element].preferences':"NEW_VALUE"}},
{arrayFilters:[{'element.serviceCode':'service-code-not-present', 'element.eventCode':'event-code-not-present','element.type':'URGENT_NOTIFICATION'}]}
)
This query works if all of the arrayFilters are present in the an element of associatePreferences, but it does not add a new element if it is not present. What am I missing?
You can use aggregation pipeline to check the existence of the element, then append the element to associatePreferences array conditionally. Finally, using the aggregation result to update back your document.
db.user_communication_preferences.aggregate([
{
"$match": {
"associateID": "testassociate"
}
},
{
"$addFields": {
"filteredArray": {
"$filter": {
"input": "$associatePreferences",
"as": "pref",
"cond": {
$and: [
{
$eq: [
"$$pref.type",
"URGENT_NOTIFICATION"
]
},
{
$eq: [
"$$pref.eventCode",
"event-code-not-exists"
]
},
{
$eq: [
"$$pref.serviceCode",
"service-code-not-exists"
]
}
]
}
}
}
}
},
{
$addFields: {
"needAddElement": {
$eq: [
{
"$size": "$filteredArray"
},
0
]
}
}
},
{
"$addFields": {
"associatePreferences": {
"$concatArrays": [
"$associatePreferences",
{
"$cond": {
"if": {
$eq: [
"$needAddElement",
true
]
},
"then": [
{
"type": "URGENT_NOTIFICATION",
"serviceCode": "service-code-not-exists",
"eventCode": "event-code-not-exists",
"preferences": [
"TEXT"
]
}
],
"else": []
}
}
]
}
}
}
]).forEach(result){
db.user_communication_preferences.update({
_id : result._id
}, {
$set: {
"associatePreferences" : result.associatePreferences
}
})
}
I am trying to update my mongo database which has following structure.
{
"_id" : ObjectId("5a64d076bfd103df081967ae"),
"values" : [
{
"date" : "2018-01-22",
"Price" : "1289.4075"
},
{
"date" : "2018-01-22",
"Price" : "1289.4075"
},
{
"date" : "2015-05-18",
"Price" : 1289.41
}
],
"Code" : 123456,
"schemeStatus" : "Inactive"
}
I want to compare first 2 array element's date value i.e values[0].date and values[1].date. If both matches then I want to delete values[0] so that there will be only 1 entry with that date.
You can use aggregation framework's pipeline with $out as a last stage to update your collection
db.collection.aggregate([
{
$addFields: {
sameDate: {
$let: {
vars: {
fst: { $arrayElemAt: [ "$values", 0 ] },
snd: { $arrayElemAt: [ "$values", 1 ] }
},
in: { $cond: { if: { $eq: [ "$$fst.date", "$$snd.date" ] }, then: 1, else: 0 } }
}
}
}
},
{
$project: {
_id: 1,
values : { $cond: { if: { $eq: [ "$sameDate", 0 ] }, then: "$values", else: { $slice: [ "$values", 1, { $size: "$values" } ] } } },
Code: 1,
schemeStatus: 1
}
},
{ $out: "collection" }
])
Some more important operators used here:
$cond to handle if-else logic
$let to define some helper variables
$arrayElemAt to get first and second element
$slice to pop first element
The following query is not valid. Can anyone please point me to the error?
I am trying to filter on all documents where the last item in the Progress array has a given state - for example "Done".
db.logdata.aggregate(
[
{ "$match": { "ProcessingInfo.Progress" : { "$exists": true } } },
{ "$redact":
{
"$cond": {
"if": { "$eq": [ { "$arrayElemAt": [ "$ProcessingInfo.Progress.State", -1 ], "Done" } ] },
"then": "$$KEEP",
"else": "$$PRUNE"
}
}
}
]
)
Sample document (which should be matched - because the last State in Progress array is "InProgress"):
{
"_id" : ObjectId("578fa85bb29339a1fa6d6109"),
"ProcessingInfo" : {
"DateStarted" : ISODate("2016-08-06T16:55:58.294+0000"),
"Attempt" : NumberInt(1),
"LastState" : "Failed",
"Progress" : [
{
"State" : "Failed",
"StateDescription" : ""
},
{
"State" : "Success",
"StateDescription" : ""
},
{
"State" : "Done",
"StateDescription" : ""
},
{
"State" : "InProgress",
"StateDescription" : ""
}
]
}
}
To kind of "circumvent" this problem I have an extra field in the document root "LastState" - is that maybe the way to go (complexity-wise)?
Your query has a little syntax error: your "Done" should be in the $eq array, not in the object containing $arrayElemAt definition. The following query (optimised to filter out documents without the expected State anywhere in the ProcessInfo.Progress array thanks to what Styvane suggested) should return your example document:
[
{ "$match": { "ProcessingInfo.Progress.State" : "InProgress" } },
{ "$redact":
{
"$cond": {
"if": { "$eq": [ { "$arrayElemAt": [ "$ProcessingInfo.Progress.State", -1 ] }, "InProgress" ] },
"then": "$$KEEP",
"else": "$$PRUNE"
}
}
}
]