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] }
Related
I am facing a problem in MongoDB. Suppose, I have the following collection.
{ id: 1, issueDate: "07/05/2021", code: "31" },
{ id: 2, issueDate: "12/11/2020", code: "14" },
{ id: 3, issueDate: "02/11/2021", code: "98" },
{ id: 4, issueDate: "01/02/2021", code: "14" },
{ id: 5, issueDate: "06/23/2020", code: "14" },
{ id: 6, issueDate: "07/01/2020", code: "31" },
{ id: 7, issueDate: "07/05/2022", code: "14" },
{ id: 8, issueDate: "07/02/2022", code: "20" },
{ id: 9, issueDate: "07/02/2022", code: "14" }
The date field is in the format MM/DD/YYYY. My goal is to get the count of items with each season (spring (March-May), summer (June-August), autumn (September-November) and winter (December-February).
The result I'm expecting is:
count of fields for each season:
{ "_id" : "Summer", "count" : 6 }
{ "_id" : "Winter", "count" : 3 }
top 2 codes (first and second most recurring) per season:
{ "_id" : "Summer", "codes" : {14, 31} }
{ "_id" : "Winter", "codes" : {14, 98} }
How can this be done?
You should never store date/time values as string, store always proper Date objects.
You can use $setWindowFields opedrator for that:
db.collection.aggregate([
// Convert string into Date
{ $set: { issueDate: { $dateFromString: { dateString: "$issueDate", format: "%m/%d/%Y" } } } },
// Determine the season (0..3)
{
$set: {
season: { $mod: [{ $toInt: { $divide: [{ $add: [{ $subtract: [{ $month: "$issueDate" }, 1] }, 1] }, 3] } }, 4] }
}
},
// Count codes per season
{
$group: {
_id: { season: "$season", code: "$code" },
count: { $count: {} },
}
},
// Rank occurrence of codes per season
{
$setWindowFields: {
partitionBy: "$_id.season",
sortBy: { count: -1 },
output: {
rank: { $denseRank: {} },
count: { $sum: "$count" }
}
}
},
// Get only top 2 ranks
{ $match: { rank: { $lte: 2 } } },
// Final grouping
{
$group: {
_id: "$_id.season",
count: { $first: "$count" },
codes: { $push: "$_id.code" }
}
},
// Some cosmetic for output
{
$set: {
season: {
$switch: {
branches: [
{ case: { $eq: ["$_id", 0] }, then: 'Winter' },
{ case: { $eq: ["$_id", 1] }, then: 'Spring' },
{ case: { $eq: ["$_id", 2] }, then: 'Summer' },
{ case: { $eq: ["$_id", 3] }, then: 'Autumn' },
]
}
}
}
}
])
Mongo Playground
I will give you clues,
You need to use $group with _id as $month on issueDate, use accumulator $sum to get month wise count.
You can divide month by 3, to get modulo, using $toInt, $divide, then put them into category using $cond.
Another option:
db.collection.aggregate([
{
$addFields: {
"season": {
$switch: {
branches: [
{
case: {
$in: [
{
$substr: [
"$issueDate",
0,
2
]
},
[
"06",
"07",
"08"
]
]
},
then: "Summer"
},
{
case: {
$in: [
{
$substr: [
"$issueDate",
0,
2
]
},
[
"03",
"04",
"05"
]
]
},
then: "Spring"
},
{
case: {
$in: [
{
$substr: [
"$issueDate",
0,
2
]
},
[
"12",
"01",
"02"
]
]
},
then: "Winter"
}
],
default: "No date found."
}
}
}
},
{
$group: {
_id: {
s: "$season",
c: "$code"
},
cnt1: {
$sum: 1
}
}
},
{
$sort: {
cnt1: -1
}
},
{
$group: {
_id: "$_id.s",
codes: {
$push: "$_id.c"
},
cnt: {
$sum: "$cnt1"
}
}
},
{
$project: {
_id: 0,
season: "$_id",
count: "$cnt",
codes: {
"$slice": [
"$codes",
2
]
}
}
}
])
Explained:
Add one more field for season based on $switch per month(extracted from issueDate string)
Group to collect per season/code.
$sort per code DESCENDING
group per season to form an array with most recurring codes in descending order.
Project the fields to the desired output and $slice the codes to limit only to the fist two most recurring.
Comment:
Indeed keeping dates in string is not a good idea in general ...
Playground
Hi have a collection as below:
clientPref
{
clntId: 1234,
clntType: "internal",
status: "PROCESSED",
prefs: [
{
name: "AAA",
value: "value1"
},
{
name: "BBB",
value: "value2"
},
{
name: "CCC",
value: "value3"
}
]
}
If I find by prefs.name $in ("AAA", "CCC"), I'm getting all the subdocuments along with the parent.
I then tried using prefs.$: 1 in the fields parameter of #Query but then it's returning the first matching subdocument only.
Desired output
{
clntId: 1234,
clntType: "internal",
status: "PROCESSED",
prefs: [
{
name: "AAA",
value: "value1"
},
{
name: "CCC",
value: "value3"
}
]
}
Is there a way I can get AAA and CCC subdocument by using #Query annotation. If not then how to do it using aggregation? Still pretty new to mongo so not able to figure out a way to get desired result.
You need to project what is needed.
{
"prefs.$":1,
"clntType":1,
"status":1
}
play
Query:
db.collection.find({
"prefs.name": "AAA"
},
{
"prefs.$": 1,
"clntType": 1,
"status": 1
})
Output:
[
{
"_id": ObjectId("5a934e000102030405000000"),
"clntType": "internal",
"prefs": [
{
"name": "AAA",
"value": "value1"
}
],
"status": "PROCESSED"
}
]
To get all sub doc:
Play
db.collection.aggregate([
{
"$unwind": "$prefs"
},
{
"$match": {
"prefs.name": {
"$in": [
"AAA",
"CCC"
]
}
}
}
])
If you want to group all the data again, you can do play
db.collection.aggregate([
{
"$unwind": "$prefs"
},
{
"$match": {
"prefs.name": {
"$in": [
"AAA",
"CCC"
]
}
}
},
{
"$group": {
"_id": "$_id",
"data": {
"$push": "$$ROOT"
}
}
}
])
In the below collection, column "qty" holds the integer values but the datatype is string.
I want to compare the "qty" field with an integer in the aggregate and "warehouse" field with a string "A". ("qty" > 2 and "warehouse" = "A")
[Can't change the datatype in the collection to integer as huge dependency is present]
Edit : Need to retrieve all the columns and all the documents matching the criteria.
Query : getting improper results
db.runCommand(
{
aggregate: "products", pipeline: [
{
$match: {
instock: {
$elemMatch: {
warehouse: "A",
qty: { $gt: "2" }
}
}
}
},
{ $project: { _id: 0 } }],
cursor: { batchSize: 200 }
});
Result : not getting documents where item = journal though it satisfies the conditions
/* 1 */
{
"item" : "paper",
"instock" : [
{
"warehouse" : "A",
"qty" : "60"
},
{
"warehouse" : "B",
"qty" : "15"
}
]
},
/* 2 */
{
"item" : "planner",
"instock" : [
{
"warehouse" : "A",
"qty" : "22"
},
{
"warehouse" : "B",
"qty" : "5"
}
]
}
Products Collection
[
{
"item": "journal",
"instock": [
{
"warehouse": "A",
"qty": "11"
},
{
"warehouse": "C",
"qty": "15"
}
]
},
{
"item": "paper",
"instock": [
{
"warehouse": "A",
"qty": "60"
},
{
"warehouse": "B",
"qty": "15"
}
]
},
{
"item": "planner",
"instock": [
{
"warehouse": "A",
"qty": "22"
},
{
"warehouse": "B",
"qty": "5"
}
]
}
]
Getting improper results as greater than operator in this case is working lexicographically but it should work like integers. Though I tried converting that to double but I am getting no results.
Query with $convert to double : no result
db.runCommand(
{
aggregate: "products", pipeline: [
//{ $match: { "item": { $in: ["planner", "paper","journal"] } } },
{
$match: {
instock: {
$elemMatch: {
warehouse: "A",
qty: {
$gt: [
{$convert:{ input: "$qty", to: "double" }}, 5]
}
}
}
}
},
{ $project: { _id: 0 } }],
cursor: { batchSize: 200 }
});
Try this:
db.products.aggregate([
{
$unwind: "$instock"
},
{
$match: {
$expr: {
$and: [
{
$eq: [
"$instock.warehouse",
"A"
]
},
{
$gt: [
{
$toInt: "$instock.qty"
},
2
]
}
]
}
}
},
{
$group: {
_id: "$_id",
item: {
$first: "$item"
},
instock: {
$push: "$instock"
}
}
},
{
$project: {
_id: 0
}
}
])
MongoPlayground
Try this, it uses $filter to retain objects has criteria :
db.runCommand(
{
aggregate: "products", pipeline: [
{ $match: { 'instock.warehouse': 'A' } },
{
$addFields: {
instockCheck: {
$filter: {
input: '$instock', as: 'each', cond: {
$and: [{ $gt: [{ $toInt: '$$each.qty' }, 2] },
{ $eq: ['$$each.warehouse', 'A'] }]
}
}
}
}
}, { $match: { instockCheck: { $gt: [] } } }, { $project: { instockCheck: 0, _id: 0 } }],
cursor: { batchSize: 200 }
});
Test : MongoDB-Playground
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
]
}
}
}
])
These are my documents in Stat collection:
{placeid: 'plaza', guestid: "xxx", logtype: "purchase", value: 12}
{placeid: 'plaza', guestid: "xxx", logtype: "visit", value: 0}
{placeid: 'plaza', guestid: "xxx", logtype: "purchase", value: 17}
{placeid: 'plaza', guestid: "yyy", logtype: "visit", value: 0}
I want to aggregate these documents to get information (passed plaza as argument):
xxx visited: 1, purchases: 2, value of purchases is 29
yyy visited: 1, purchases: 0, value of purchases is 0
This is my approach:
Stat.aggregate(
[
{ $match: { placeid: "plaza" } },
{
$group: {
_id: "$guestid",
totallogs: { $sum: 1 },
totalvalue: { $sum: "$value" },
}
}
]
)
problem here is that this aggregation does not take logtype into consideration.
And I do not know how to improve it. Any help?
You need to use the aggregation framework to $group your documents by "guestid" and use the $sum accumulator operator to return the sum. Of course you also need the $cond operator evaluates the value of "logtype" and returns the value of the "true case".
db.Stat.aggregate([
{ "$match": { "placeid": "plaza" } },
{ "$group": {
"_id": "$guestid",
"visit": {
"$sum": {
"$cond": [
{ "$eq": [ "$logtype", "visit" ] },
1,
0
]
}
},
"purchases": {
"$sum": {
"$cond": [
{ "$eq": [ "$logtype", "purchase" ] },
1,
0
]
}
},
"value_purchase": {
"$sum": {
"$cond": [
{ "$eq": [ "$logtype", "purchase" ] },
"$value",
0
]
}
}
}}
])
which produces:
{ "_id" : "yyy", "visit" : 1, "purchases" : 0, "value_purchase" : 0 }
{ "_id" : "xxx", "visit" : 1, "purchases" : 2, "value_purchase" : 29 }