Mongodb aggregate project string as number - string

I have a mongo script which retrieves a value from an array and creates a new document. However, the value which it retrieves is a string. I need the value to be added to the new document as a number instead of a string because it is read by a graphing engine which ignores the value if it is a string.
From the script below, it is "value": {$arrayElemAt: ["$accountBalances", 1]} which needs to be a number instead of a string. Thanks.
db.std_sourceBusinessData.aggregate(
{ $match : {objectType: "Account Balances"}},
{ $project: {_id: 1,entity_ID: 1,objectOrigin: 1,accountBalances: 1}},
{ $unwind: "$accountBalances" },
{ $match: {"accountBalances": "Sales"}}
,
{$project: {
_id: 1
, "value": {$arrayElemAt: ["$accountBalances", 1]}
,"key": {$literal: "sales"}
,"company": "$entity_ID"
,"objectOrigin" : "$objectOrigin"
}}
,{$out: "entity_datapoints"}
)
This is what I currently get:
{
"_id" : ObjectId("5670961f910e1f54662c1d9d"),
"objectOrigin" : "Xero",
"Value" : "500.00",
"key" : "grossprofit",
"company" : "e56e09ef-5c7c-423e-b699-21469bd2ea00"
}
what I want is:
{
"_id" : ObjectId("5670961f910e1f54662c1d9d"),
"objectOrigin" : "Xero",
"Value" : 500.0000000000000,
"key" : "grossprofit",
"company" : "e56e09ef-5c7c-423e-b699-21469bd2ea00"
}

Related

$add,$subtract aggregation-framework in mongodb

Hi i am mentioning the sample data
///collection - test////
{
"_id" : {
"date" : ISODate("2020-02-11T17:00:00Z"),
"userId" : ObjectId("5e43e5cdc11f750864f46820"),
"adminId" : ObjectId("5e43de778b57693cd46859eb")
},
"outstanding" : 212.39999999999998,
"totalBill" : 342.4,
"totalPayment" : 130
}
{
"_id" : {
"date" : ISODate("2020-02-11T17:00:00Z"),
"userId" : ObjectId("5e43e73169fe1e3fc07eb7c5"),
"adminId" : ObjectId("5e43de778b57693cd46859eb")
},
"outstanding" : 797.8399999999999,
"totalBill" : 797.8399999999999,
"totalPayment" : 0
}
I need to structure a query which does following things-
I need to calculate the actualOutstanding:[(totalBill+outstanding)-totalPayment],
I need to save this actualOutstanding in the same collection & in the same document according to {"_id" : {"date","userId", "adminId" }}
NOTE: userId is different in both the documents.
Introduced in Mongo version 4.2+ pipelined updates, meaning we can now use aggregate expressions to update documents.
db.collection.updateOne(
{
"adminId" : ObjectId("5e43de778b57693cd46859eb")
'_id."userId" : ObjectId("5e43e73169fe1e3fc07eb7c5"),
'_id.date': ISODate("2020-02-11T18:30:00Z"),
},
[
{ '$set': {
actualOutstanding: {
$subtract:[ {$add: ['$totalBill','$outstanding']},'$totalPayment']
}
} }
]);
For any other Mongo version you have to split it into 2 actions, first query and calculate then update the document with the calculation.

Could not find the matching Number in mongodb aggregation using Regex

I have a collection
//demo name : user_informations
{
"_id" : 3015,
"Phone Number" : "9159571195",
"First Name" : "logesh",
"Last Name" : "chandiran",
"Email ID" : "logu#gmail.com",
"Gender" : "male",
"customerID" : "CUST3015",
"createddate" : 1472482064363.0,
"modifieddate" : 1474384997049.0,
"transationHistory" : [
{
"transactionStatus" : "Success",
"transactionAmount" : 2500,
"paymentId" : "Q8urVX9Cpf",
"transactionDate" : 1472754600000.0,
"transactionId" : "7egsOpmqBR",
"comments" : "EMI",
"medium" : "Cash"]
}
}
i'm using the following query
db.user_informations.aggregate([
{
$match:{"Phone Number": '9159571195'}},
{$unwind:'$transationHistory'},
{"$match":{"$or":[
{"transationHistory.transactionAmount":{$regex:/2500/i}},
{"transationHistory.transactionId":{$regex:/2500/i}},
{"transationHistory.paymentId":{$regex:/2500/i}},
{"transationHistory.transactionStatus":{$regex:/2500/i}},
{"transationHistory.medium":{$regex:/2500/i}},
{"transationHistory.comments":{$regex:/2500/i}}
]}},
{"$group":{_id: null,"count":{$sum: 1 },"result":{$push:"$transationHistory"}}}
])
The above query works fine with values in string but not with numbers it is returning as null. How to match the value with numbers in collection fields.
I need the count and result of matching data from the collection.
db.user_informations.aggregate(
{$match:{"Phone Number": '9159571195'}},
{$unwind:'$transationHistory'},
{$project:{
"transactionAmount":{ "$toLower":"$transationHistory.transactionAmount"}
}},
{"$match":{
"$or":[
{"transactionAmount":{$regex:txt,"$options": "i"}}
]
},
{"$group":{
_id: null,
"count":{$sum: 1 }
}})
Regex is a tool for strings, not numbers. Use regular number comparison operators, like $eq, $gt, $lt, etc.
Basically, it'll be:
db.user_informations.aggregate(
{$match:{"Phone Number": '9159571195'}},
{$unwind:'$transationHistory'},
{"$match":{
"$or":[
{"transationHistory.transactionAmount":{$eq:2500}}
]
},
{"$group":{
_id: null,
"count":{$sum: 1 },
"result":{$push:"$transationHistory"}
}})
Hopefully, all brackets are in place.

How to do mongoose aggregation with nested array documents

I have a Mongodb collection, Polls with following schema
{
"options" : [
{
"_id" : Object Id,
"option" : String,
"votes" : [ Object Id ] // object ids of users who voted
},.....
]
}
Assume i have userId of the user in node js to whom I want to send this info.
My task is to
(1) include an extra field in the above json object (which i get using mongoose).
as
"myVote" : option._id
I need to find option._id for which
options[someIndex].votes contains userId
(2) change the existing "votes" field in each option to represent number of votes on a particular option as can be seen in example
Example:
{
"options" : [
{
"_id" : 1,
"option" : "A",
"votes" : [ 1,2,3 ]
},
{
"_id" : 2,
"option" : "B",
"votes" : [ 5 ]
},
{
"_id" : 3,
"option" : "C",
"votes" : [ ]
}
]
}
So if i user with user id = 5 wants to see the poll, then i need to send following info:
Expected Result :
{
"my_vote" : 2, // user with id 5 voted on option with id 2
"options" : [
{
"_id" : 1,
"option" : "A",
"votes" : 3 //num of votes on option "A"
},
{
"_id" : 2,
"option" : "B",
"votes" : 1 //num of votes on option "B"
},
{
"_id" : 3,
"option" : "C",
"votes" : 0 //num of votes on option "C"
}
]
}
Since it was the question that you actually asked that was neither really provided in the current acceptance answer, and also that it does some unnecessary things, there is another approach:
var userId = 5; // A variable to work into the submitted pipeline
db.sample.aggregate([
{ "$unwind": "$options" },
{ "$group": {
"_id": "$_id",
"my_vote": { "$min": {
"$cond": [
{ "$setIsSubset": [ [userId], "$options.votes" ] },
"$options._id",
false
]
}},
"options": { "$push": {
"_id": "$options._id",
"option": "$options.option",
"votes": { "$size": "$options.votes" }
}}
}}
])
Which of course will give you output per document like this:
{
"_id" : ObjectId("5573a0a8b67e246aba2b4b6e"),
"my_vote" : 2,
"options" : [
{
"_id" : 1,
"option" : "A",
"votes" : 3
},
{
"_id" : 2,
"option" : "B",
"votes" : 1
},
{
"_id" : 3,
"option" : "C",
"votes" : 0
}
]
}
So what you are doing here is using $unwind in order to break down the array for inspection first. The following $group stage ( and the only other stage you need ) makes use of the $min and $push operators for re-construction.
Inside each of those operations, the $cond operation tests the array content via $setIsSubset and either returns the matched _id value or false. When reconstructing the inner array element, specify all elements rather than just the top level document in arguments to $push and make use of the $size operator to count the elements in the array.
You also make mention with a link to another question about dealing with an empty array with $unwind. The $size operator here will do the right thing, so it is not required to $unwind and project a "dummy" value where the array is empty in this case.
Grand note, unless you are actually "aggregating" across documents it generally would be advised to do this operation in client code rather than the aggregation framework. Using $unwind effectively creates a new document in the aggregation pipeline for each element of the array contained in each document, which produces significant overhead.
For such an operation acting on distinct documents only, client code is more efficient to process each document individually.
If you really must persist that server processing is the way to do this, then this is probably most efficient using $map instead:
db.sample.aggregate([
{ "$project": {
"my_vote": {
"$setDifference": [
{ "$map": {
"input": "$options",
"as": "o",
"in": { "$cond": [
{ "$setIsSubset": [ [userId], "$$o.votes" ] },
"$$o._id",
false
]}
}},
[false]
]
},
"options": { "$map": {
"input": "$options",
"as": "o",
"in": {
"_id": "$$o._id",
"option": "$$o.option",
"votes": { "$size": "$$o.votes" }
}
}}
}}
])
So this just "projects" the re-worked results for each document. The my_vote is not the same though, since it is a single element array ( or possible multiple matches ) that the aggregation framework lacks the operators to reduce to a non array element without further overhead:
{
"_id" : ObjectId("5573a0a8b67e246aba2b4b6e"),
"options" : [
{
"_id" : 1,
"option" : "A",
"votes" : 3
},
{
"_id" : 2,
"option" : "B",
"votes" : 1
},
{
"_id" : 3,
"option" : "C",
"votes" : 0
}
],
"my_vote" : [
2
]
}
Check out this question.
It's not asking the same thing, but there's no way to do what you're asking without multiple queries anyway. I would modify the JSON you get back directly, as you're just displaying extra info that is already contained in the result of the query.
Save the userID you're querying for.
Take the results of your query (options array in an object), search through the votes of each element in the array.
When you've found the right vote, attach the _id (perhaps add 'n/a' if you don't find a vote).
Write a function that does 2 and 3, and you can just pass it a userID, and get back a new object with myVote attached.
I don't think doing it like this will be slower than doing another query in Mongoose.

MongoDB geospatial index, how to use it with array elements?

I would like to get Kevin pub spots near a given position. Here is the userSpots collection :
{ user:'Kevin',
spots:[
{ name:'a',
type:'pub',
location:[x,y]
},
{ name:'b',
type:'gym',
location:[v,w]
}
]
},
{ user:'Layla',
spots:[
...
]
}
Here is what I tried :
db.userSpots.findOne(
{ user: 'Kevin',
spots: {
$elemMatch: {
location:{ $nearSphere: [lng,lat], $maxDistance: d},
type: 'pub'
}
}
},
},
function(err){...}
)
I get a strange error. Mongo tells me there is no index :2d in the location field. But when I check with db.userSpots.getIndexes(), the 2d index is there. Why doesn't mongodb see the index ? Is there something I am doing wrong ?
MongoError: can't find special index: 2d for : { spots: { $elemMatch: { type:'pub',location:{ $nearSphere: [lng,lat], $maxDistance: d}}}, user:'Kevin'}
db.userSpots.getIndexes() output :
{
"0" : {
"v" : 1,
"key" : {
"_id" : 1
},
"ns" : "mydb.userSpots",
"name" : "_id_"
},
"1" : {
"v" : 1,
"key" : {
"spots.location" : "2d"
},
"ns" : "mydb.usersBoxes",
"name" : "spots.location_2d",
"background" : true,
"safe" : null
}
}
For a similar geospatial app, I transformed the location into GeoJSON:
{
"_id" : ObjectId("5252cbdd9520b8b18ee4b1c3"),
"name" : "Seattle - Downtown",
"location" : {
"type" : "Point",
"coordinates" : [
-122.33145,
47.60789
]
}
}
(the coordinates are in longitude / latitude format. Mongo's use of GeoJSON is described here.).
The index is created using:
db.userSpots.ensureIndex({"location": "2dsphere"})
In my aggregation pipeline, I find matches using:
{"$match":{"location":{"$geoWithin": {"$centerSphere":[[location.coordinates[0], location.coordinates[1]], radius/3959]}}}}
(where radius is measured in miles - the magic number is used to convert to radians).
To index documents containing array of geo data MongoDB uses multi-key index. Multi-key index unwinds document to some documents with single value instead of array before indexing. So the index consider that key field as single value field not array.
Try query it without $elemMatch operator.

Compare two date fields in MongoDB

in my collection each document has 2 dates, modified and sync. I would like to find those which modified > sync, or sync does not exist.
I tried
{'modified': { $gt : 'sync' }}
but it's not showing what I expected. Any ideas?
Thanks
You can not compare a field with the value of another field with the normal query matching. However, you can do this with the aggregation framework:
db.so.aggregate( [
{ $match: …your normal other query… },
{ $match: { $eq: [ '$modified', '$sync' ] } }
] );
I put …your normal other query… in there as you can make that bit use the index. So if you want to do this for only documents where the name field is charles you can do:
db.so.ensureIndex( { name: 1 } );
db.so.aggregate( [
{ $match: { name: 'charles' } },
{ $project: {
modified: 1,
sync: 1,
name: 1,
eq: { $cond: [ { $gt: [ '$modified', '$sync' ] }, 1, 0 ] }
} },
{ $match: { eq: 1 } }
] );
With the input:
{ "_id" : ObjectId("520276459bf0f0f3a6e4589c"), "modified" : 73845345, "sync" : 73234 }
{ "_id" : ObjectId("5202764f9bf0f0f3a6e4589d"), "modified" : 4, "sync" : 4 }
{ "_id" : ObjectId("5202765b9bf0f0f3a6e4589e"), "modified" : 4, "sync" : 4, "name" : "charles" }
{ "_id" : ObjectId("5202765e9bf0f0f3a6e4589f"), "modified" : 4, "sync" : 45, "name" : "charles" }
{ "_id" : ObjectId("520276949bf0f0f3a6e458a1"), "modified" : 46, "sync" : 45, "name" : "charles" }
This returns:
{
"result" : [
{
"_id" : ObjectId("520276949bf0f0f3a6e458a1"),
"modified" : 46,
"sync" : 45,
"name" : "charles",
"eq" : 1
}
],
"ok" : 1
}
If you want any more fields, you need to add them in the $project.
For MongoDB 3.6 and newer:
The $expr operator allows the use of aggregation expressions within the query language, thus you can do the following:
db.test.find({ "$expr": { "$gt": ["$modified", "$sync"] } })
or using aggregation framework with $match pipeline
db.test.aggregate([
{ "$match": { "$expr": { "$gt": ["$modified", "$sync"] } } }
])
For MongoDB 3.0+:
You can also use the aggregation framework with the $redact pipeline operator that allows you to process the logical condition with the $cond operator and uses the special operations $$KEEP to "keep" the document where the logical condition is true or $$PRUNE to "remove" the document where the condition was false.
Consider running the following aggregate operation which demonstrates the above concept:
db.test.aggregate([
{ "$redact": {
"$cond": [
{ "$gt": ["$modified", "$sync"] },
"$$KEEP",
"$$PRUNE"
]
} }
])
This operation is similar to having a $project pipeline that selects the fields in the collection and creates a new field that holds the result from the logical condition query and then a subsequent $match, except that $redact uses a single pipeline stage which is more efficient:
Simply
db.collection.find({$where:"this.modified>this.sync"})
Example
Kobkrits-MacBook-Pro-2:~ kobkrit$ mongo
MongoDB shell version: 3.2.3
connecting to: test
> db.time.insert({d1:new Date(), d2: new Date(new Date().getTime()+10000)})
WriteResult({ "nInserted" : 1 })
> db.time.find()
{ "_id" : ObjectId("577a619493653ac93093883f"), "d1" : ISODate("2016-07-04T13:16:04.167Z"), "d2" : ISODate("2016-07-04T13:16:14.167Z") }
> db.time.find({$where:"this.d1<this.d2"})
{ "_id" : ObjectId("577a619493653ac93093883f"), "d1" : ISODate("2016-07-04T13:16:04.167Z"), "d2" : ISODate("2016-07-04T13:16:14.167Z") }
> db.time.find({$where:"this.d1>this.d2"})
> db.time.find({$where:"this.d1==this.d2"})
>
Use Javascript, use foreach And convert Date To toDateString()
db.ledgers.find({}).forEach(function(item){
if(item.fromdate.toDateString() == item.todate.toDateString())
{
printjson(item)
}
})
Right now your query is trying to return all results such that the modified field is greater than the word 'sync'. Try getting rid of the quotes around sync and see if that fixes anything. Otherwise, I did a little research and found this question. What you're trying to do just might not be possible in a single query, but you should be able to manipulate your data once you pull everything from the database.
To fix this issue without aggregation change your query to this:
{'modified': { $gt : ISODate(this.sync) }}

Resources