Aggregate and $ne Not Working as Expected - mongodb

I'm trying the aggregation below, but do not appear to be getting the expected result using $ne and null.
I have tried other solutions like using a combination of $cond, $not, and $eq to no avail. Using $gt:[ "$unloadeddate", null] seems to give some results, but that does not seem to be proper syntax and I am concerned it's not trustworthy to run properly over entire dataset.
Also, querying as follows:
db.getCollection('esInvoices').find({"esBlendTickets.loadeddate":{$ne:null}})
... returns results so not sure why the same query in the aggregate function not working.
Any help is appreciated!!
The first part works...
"unloadeddate": { "$switch": {
branches:[ {
case: {
"$ne":[ "$esBlendTickets.ticketdate", null]
},
then: "$esBlendTickets.ticketdate"
}, {
case: {
"$ne":[ "$esDeliveryTickets.ticketdate", null]
},
then: "$esDeliveryTickets.ticketdate"
}],
default: null
}
},
"loadeddate": { "$switch": {
branches:[ {
case: {
"$ne":[ "$esBlendTickets.loadeddate", null]
},
then: "$esBlendTickets.loadeddate"
}, {
case: {
"$ne":[ "$esDeliveryTickets.loadeddate", null]
},
then: "$esDeliveryTickets.loadeddate"
}],
default: null
}
},
... but this second part (essentially the same logic except for the resulting value) does not work as expected...
"stagename": { "$switch": {
branches:[ {
case: {
"$ne":[ "$esDeliveryTickets.ticketdate", null]
},
then: "Invoiced"
}, {
case: {
"$ne":[ "$esBlendTickets.ticketdate", null]
},
then: "Invoiced"
}],
default: "Invoiced-Only"
}
}
Complete Aggregation:
db.esInvoices.aggregate([ {
$addFields: {
// A single invoice will not have both a blend ticket and delivery ticket associated so looping tough each case should work.
"unloadeddate": { "$switch": {
branches:[ {
case: {
"$ne":[ "$esBlendTickets.ticketdate", null]
},
then: "$esBlendTickets.ticketdate"
}, {
case: {
"$ne":[ "$esDeliveryTickets.ticketdate", null]
},
then: "$esDeliveryTickets.ticketdate"
}],
default: null
}
},
"loadeddate": { "$switch": {
branches:[ {
case: {
"$ne":[ "$esBlendTickets.loadeddate", null]
},
then: "$esBlendTickets.loadeddate"
}, {
case: {
"$ne":[ "$esDeliveryTickets.loadeddate", null]
},
then: "$esDeliveryTickets.loadeddate"
}],
default: null
}
},
"stagedate": "$InvoiceHeader.InvDate",
"stagename": { "$switch": {
branches:[ {
case: {
"$ne":[ "$esDeliveryTickets.ticketdate", null]
},
then: "Invoiced"
}, {
case: {
"$ne":[ "$esBlendTickets.ticketdate", null]
},
then: "Invoiced"
}],
default: "Invoiced-Only"
}
}
}}])

Think I just ran into the same problem you were having. Using Mongo 3.4 at the moment. From what I can tell the query $ne behaves differently from the aggregate $ne when you are comparing it to null. Threw me off for a little bit.
Specifically, a { $ne: [ '$field', null ] } predicate in the aggregate pipeline will return true when $field is undefined.
However, when using queries (not $aggregate), the { field: { $ne: null } predicate will return false for those same documents when $field is undefined.
My work-around was to use a $project with { field: { $ifNull: [ '$field': null ] } } in a prior step to turn undefined instances of that field into explicit nulls, which will then make the aggregate $ne work as I needed. For whatever reason, $ifNull works with null, undefined, and missing fields. Not sure why $ne is different.
Here's an example to reproduce.
db.test.insertMany([
{ a: 1, b: 'string' },
{ a: 2, b: null },
{ a: 3 },
])
db.test.find({ b: { $ne: null }}, { _id: 0 })
/*
returns:
{
"a" : 1.0,
"b" : "string"
}
*/
db.test.aggregate([
{ $project: {
_id: 0,
a: 1,
b: 1,
switched: { $switch: {
branches: [
{ case: { $ne: [ '$b', null ] }, then: 'cased' },
],
default: 'default',
}}
}}
])
/*
returns:
{
"a" : 1.0,
"b" : "string",
"switched" : "cased"
},
{
"a" : 2.0,
"b" : null,
"switched" : "default"
},
{
"a" : 3.0,
"switched" : "cased" <--
}
*/

The problem is missing fields are undefined while null fields are, well, nulls. When you write "$ne":[ "$esDeliveryTickets.ticketdate", null] you don't filter the former but only the latter.
But undefined is "smaller" than null. In order to filter them both you just need to make lte/gt instead of eq/ne. So this query returns all existing values:
See example:
db.test.insertOne(
{
"a" : 10,
"b" : null
})
...
db.getCollection('mytest').find( {},
{
"a" : { $lte : ["$a", null] },
"b" : { $lte : ["$b", null] },
"c" : { $lte : ["$c", null] },
})
// {
// "_id" : ObjectId("606724f38ec4d26b981b5a1c"),
// "a" : false,
// "b" : true,
// "c" : true
// }
In your case:
"stagename": { "$switch": {
branches:[ {
case: {
"$gt":[ "$esDeliveryTickets.ticketdate", null]
},
then: "Invoiced"
}, {
case: {
"$gt":[ "$esBlendTickets.ticketdate", null]
},
then: "Invoiced"
}],
default: "Invoiced-Only"
}
}
and this one returns only missing (or null) values:
"stagename": { "$switch": {
branches:[ {
case: {
"$lte":[ "$esDeliveryTickets.ticketdate", null]
},
then: "Invoiced"
}, {
case: {
"$lte":[ "$esBlendTickets.ticketdate", null]
},
then: "Invoiced"
}],
default: "Invoiced-Only"
}
}

Related

Or with If and In mongodb

{
$cond: {
if: { $in: [`$$${field.fieldName}`, ['', null]] },
then: [],
else: `$$${field.fieldName}`,
},
},
In this condition, I also want to check the field.fieldName exits.
$exists: true,
then it will something like
if ( !filed.fieldName || field.fieldName === null || field.fieldName === '') then []
I am not finding any way to add $exits: true or false in this.
Please help if can add something to this. Or any alternate way to achieve ?
You can use $or
$ifNull
Evaluates an expression and returns the value of the expression if the expression evaluates to a non-null value. If the expression evaluates to a null value, including instances of undefined values or missing fields, returns the value of the replacement expression.
{ $ifNull: [ <expression>, <replacement-expression-if-null> ] }
{
$cond: {
if: {
$or : [
{ $in: [`$$${field.fieldName}`, ['', null]] },
{ $ifNull: [`$$${field.fieldName}`, ""] }
]},
then: [],
else: `$$${field.fieldName}`,
}
},
You just need to put $ifNull in else part, if it's not exists it will return [],
{
$cond: {
if: {
$in: [`$$${field.fieldName}`, ["", null]]
},
then: [],
else: { $ifNull: [`$$${field.fieldName}`, []] }
}
}
Playground
Input:
[
{ "key": null },
{ "key": "" },
{ "A": "a" }
]
Result:
[
{
"key": null,
"status": []
},
{
"key": "",
"status": []
},
{
"status": []
}
]
Second approach, if you want to add an existing condition in if part you can try condition with $or,
$type will return the field's data type the missing field type is "missing"
{
$cond: {
if: {
$or: [
{ $eq: [{ $type: `$$${field.fieldName}` }, "missing"] },
{ $in: [`$$${field.fieldName}`, ["", null]] }
]
},
then: [],
else: `$$${field.fieldName}`
}
}
Playground
Bear in mind, "field.fieldName exists" might be different to "field.fieldName is not null".
Consider special values like this:
db.collection.insertMany([
{ _id: 1, a: 1 },
{ _id: 2, a: '' },
{ _id: 3, a: undefined },
{ _id: 4, a: null },
{ _id: 5 }
])
db.collection.aggregate([
{
$set: {
type: { $type: "$a" },
ifNull: { $ifNull: ["$a", true] },
defined: { $ne: ["$a", undefined] },
existing: { $ne: [{ $type: "$a" }, "missing"] }
}
}
])
{ _id: 1, a: 1, type: double, ifNull: 1, defined: true, existing: true }
{ _id: 2, a: "", type: string, ifNull: "", defined: true, existing: true }
{ _id: 3, a: undefined, type: undefined, ifNull: true, defined: false, existing: true }
{ _id: 4, a: null, type: null, ifNull: true, defined: true, existing: true }
{ _id: 5, type: missing, ifNull: true, defined: false, existing: false }
So, condition could be this one, depending on your requirements:
{
$cond: {
if: { $ne: [{ $type: "$field.fieldName" }, "missing"] },
then: [],
else: "$field.fieldName",
}
}
For sake of completeness: With db.collection.find():
db.collection.find({ a: { $exists: false } })
{ _id: 5 }
db.collection.find({ a: { $exists: true} })
{ _id: 1, a: 1 },
{ _id: 2, a: '' },
{ _id: 3, a: undefined },
{ _id: 4, a: null }
db.collection.find({ a: null })
{ _id: 3, a: undefined },
{ _id: 4, a: null },
{ _id: 5 }
db.collection.find({ a: {$ne: null} })
{ _id: 1, a: 1 },
{ _id: 2, a: '' },
db.collection.find({ a: {$type: "null"} })
{ _id: 4, a: null }
Try this:
{
$cond: {
if: { $ne : [`$$${field.fieldName}`, undefined] },
then: [],
else: `$$${field.fieldName}`,
},
}

Mongo how to write a CASE WHEN THEN ELSE

I'm trying to translate to mongo the following sql:
SELECT CASE WHEN COL_A IS NOT NULL THEN 'aaa' ELSE 'bbb' END AS FLAG, COL_B AS VALUE
FROM MY_TABLE
--- or, at least
SELECT coalesce(COL_A,'bbb') AS FLAG, COL_B AS VALUE
FROM MY_TABLE
/*sample json data*/
[
{ COL_A: "abc",
COL_B: "123"
},{
COL_B: "654"
},
{
COL_A: "zyx",
COL_B: "987"
}
]
/*expected_output*/
{
FLAG: "aaa", /*OR , at least, "abc"*/
VALUE: "123"
},{
FLAG: "bbb",
VALUE: "654"
},{
FLAG: "aaa", /*or, at least, "zyx"*/
VALUE: "987"
}
In mongo I'm Here:
db.getCollection('MyTable').aggregate([
{
$project:
{
"_id": 0,
"FLAG" : {
$switch :{
branches: [
{case: { "$COL_A": { $exists: true } then: "aaa" } }
],
default: "bbb"
}
},
"VALUE" : "$COL_B"
}
},
{
$limit:50
}
])
But it tells me unexpected identifier
EDIT:
Tested with $ifNull: I get an empty array as output instead of expected "bbb"
db.getCollection('MyTable').aggregate([
{
$project:
{
"_id": 0,
"FLAG" : {$ifNull: ["$COL_A", "bbb"] },
"VALUE" : "$COL_B"
}
},
{
$limit:50
}
])
db.collection.aggregate([
{
$project: {
"_id": 0,
"FLAG": {
$cond: [
{
$lte: [
"$COL_A",
null
]
},
"bbb",
"aaa"
]
},
"VALUE": "$COL_B"
}
},
{
$limit: 50
}
])
To check if the value doesn't exist or is null use { $lte: ["$field", null] }

mongodb aggregation $in with $type

I have a collection:
{
values: [null, null, 1, 2, 3, 4.6],
}
I want to receive a property which tells me if any of those values is a number.
I've tried:
{
$project: {
hasNumber: {
$in: [{ $eq: [{ $type: '$$CURRENT' }, 'number'] }, '$values'],
},
},
}
but it doesn't work, is something like this possible with aggregations?
Please try this :
db.yourCollectionName.aggregate([{
$project: {
values: 1, hasNumber: {
$gt: [{
$size: {
$filter:
{
input: "$values",
as: "value",
cond: { $eq: [{ $type: '$$value' }, 'int'] }
// To check & include double as well, replace above cond with this :
//cond: { $or :[ {$eq: [{ $type: '$$value' }, 'int']} , {$eq: [{ $type: '$$value' }, 'double']}] }
}
}
}, 0]
}
}
}])
Collection Data :
/* 1 */
{
"_id" : ObjectId("5e14d9dd627ef78236ea77e3"),
"values" : [
null,
null,
1,
2,
3,
4.6
]
}
/* 2 */
{
"_id" : ObjectId("5e14d9e4627ef78236ea785f"),
"values" : [
null,
null
]
}
/* 3 */
{
"_id" : ObjectId("5e14decc627ef78236eb12d3"),
"values" : [
"1",
4.6
]
}
Result :
/* 1 */
{
"_id" : ObjectId("5e14d9dd627ef78236ea77e3"),
"values" : [
null,
null,
1,
2,
3,
4.6
],
"hasNumber" : true
}
/* 2 */
{
"_id" : ObjectId("5e14d9e4627ef78236ea785f"),
"values" : [
null,
null
],
"hasNumber" : false
}
/* 3 */ // If we're checking for double this hasNumber will be true
{
"_id" : ObjectId("5e14decc627ef78236eb12d3"),
"values" : [
"1",
4.6
],
"hasNumber" : false
}
Debugging your code...
$in: [{ $eq: [{ $type: '$$CURRENT' }, 'number'] }, '$values'],
You are checking if false is inside $values.
Explanation:
'$$CURRENT' returns raw document
{ $type: '$$CURRENT' } returns 'object'
$eq:['object', 'number'] will always return false
$in:[ 'false', '$values'] will be false
I've solved with $convert operator
db.collection.aggregate([
{
$project: {
hasNumber: {
$cond: [
{
$eq: [
{
$map: {
input: "$values",
in: {
$convert: {
input: "$$this",
to: "int",
onError: -999,
onNull: -999
}
}
}
},
"$values"
]
},
true,
false
]
}
}
}
])

aggregate operation coming as null in mongodb

Inventors
.aggregate([{
$match: filter
},
{
$group: {
"_id": {
"store_id": "$store_id"
},
stockAmount: {
$sum: {
$multiply: ["$intProductQty", "$dblMRP"]
}
},
storeValue: {
$sum: "$intProductQty"
},
}
},
])
.exec(function(err, stock) {
return res.send(stock);
});
schema
{
"store_id" : "BST000433",
"strProductCode" : "9000000064775",
"dblMRP" : 25,
"intProductQty" : 1,
}
I initailized these fields(intProductQty, dblMRP, strPurchasePrice) as integer. But when I execute above command, I'm getting that three values(stockAmount, purchaseAmount, storeValue) as null.
If it is still possible that some of those values are not set, you could check if they are null with $ifNull and set them to 0 for those calculations in a $project step after the $match:
$project: {
intProductQty: { $ifNull: [ "$intProductQty", 0 ] },
dblMRP: { $ifNull: [ "$dblMRP", 0 ] },
strPurchasePrice: { $ifNull: [ "$strPurchasePrice", 0 ] }
},
Also, I guess it's not your case, but you could filter out those that are not numeric with $type:
$match: {
intProductQty: { $type: "number" },
dblMRP: { $type: "number" },
strPurchasePrice: { $type: "number" }
},

Nested conditions in $cond aggregate

I'm trying to create a computed status field in my Mongo query (statuses: created, payment received, shipped, received, finished).
db.orders.aggregate( [
{ $project: { status: {
$cond: { if: { $ne: ["$feedback", null] },
then: 'finished', else: {
$cond: { if: { $ne: ["$received", null] },
then: 'received', else: {
$cond: { if: { $ne: ["$shipped", null] },
then: 'shipped', else: {
$cond: { if: { $ne: ["$payment", null] },
then: 'payment received', else: 'created' }
} }
} }
} }
} } },
{ $match: { } }
] )
Example data:
{
"_id" : "xxxxxx0",
"payment" : ISODate("2016-02-03T10:45:00.011Z"),
"shipped" : ISODate("2016-02-03T11:55:00.011Z"),
"received" : ISODate("2016-02-03T12:45:00.011Z"),
"feedback" : ISODate("2016-02-03T14:34:00.011Z")
},
{
"_id" : "xxxxxx1",
"payment" : ISODate("2016-02-03T10:45:00.011Z"),
"shipped" : ISODate("2016-02-03T11:55:00.011Z"),
"received" : ISODate("2016-02-03T12:45:00.011Z")
},
{
"_id" : "xxxxxx2",
"payment" : ISODate("2016-02-03T10:45:00.011Z"),
"shipped" : ISODate("2016-02-03T11:55:00.011Z")
},
{
"_id" : "xxxxxx3",
"payment" : ISODate("2016-02-03T10:45:00.011Z")
},
{
"_id" : "xxxxxx4"
}
For some reason all my results show up as 'finished', am I using $cond wrong? Does it support nested $cond ?
You can't use $eq null if you want to check if the field exists or not, it will always return true
There is a trick to do that with $gt. You can check full explanation here (https://docs.mongodb.com/manual/reference/bson-types/#bson-types-comparison-order)
db.orders.aggregate( [
{ $project: { status: {
$cond: { if: { $gt: ["$feedback", null] },
then: 'finished', else: {
$cond: { if: { $gt: ["$received", null] },
then: 'received', else: {
$cond: { if: { $gt: ["$shipped", null] },
then: 'shipped', else: {
$cond: { if: { $gt: ["$payment", null] },
then: 'payment received', else: 'created' }
} }
} }
} }
} } },
{ $match: { } }
] )