Could not find the matching Number in mongodb aggregation using Regex - node.js

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.

Related

Sort JSON document by values embedded in an array of objects

I have a document in the below format. The goal is to group the document by student name and sort it by rank in the ascending order. Once that is done, iterate through the rank(within a student) and if each subsequent rank is greater than the previous one, the version field needs to be incremented. As part of a pipeline, student_name will be passed to me so matching by student name should be good instead of grouping.
NOTE: Tried it with python and works to some extent. A python solution would also be great!
{
"_id" : ObjectId("5d389c7907bf860f5cd11220"),
"class" : "I",
"students" : [
{
"student_name" : "AAA",
"Version" : 2,
"scores" : [
{
"value" : "50",
"rank" : 2
},
{
"value" : "70",
"rank" : 1
}
]
},
{
"student_name" : "BBB",
"Version" : 5,
"scores" : [
{
"value" : 80,
"rank" : 2
},
{
"value" : 100,
"rank" : 1
},
{
"value" : 100,
"rank" : 1
}
]
}
]
}
I tried this piece of code to sort
def version(student_name):
db.column.aggregate(
[
{"$unwind": "$students"},
{"$unwind": "$students.scores"},
{"$sort" : {"students.scores.rank" : 1}},
{"$group" : {"students.student_name}
]
)
for i in range(0,(len(students.scores)-1)):
if students.scores[i].rank < students.scores[i+1].rank:
tag.update_many(
{"$inc" : {"students.Version":1}}
)
The expected output for student AAA should be
{
"_id" : ObjectId("5d389c7907bf860f5cd11220"),
"class" : "I",
"students" : [
{
"student_name" : "AAA",
"Version" : 3, #version incremented
"scores" : [
{
"value" : "70",
"rank" : 1
},
{
"value" : "50",
"rank" : 2
}
]
}
I was able to sort the document.
pipeline = [
{"$unwind": "$properties"},
{"$unwind": "$properties.values"},
{"$sort" : {"$properties.values.rank" : -1}},
{"$group": {"_id" : "$properties.property_name", "values" : {"$push" : "$properties.values"}}}
]
import pprint
pprint.pprint(list(db.column.aggregate(pipeline)))

All fields search [duplicate]

This question already has answers here:
MongoDB Query Help - query on values of any key in a sub-object
(3 answers)
Closed 6 years ago.
This is my data set, which is part of a bigger json code. I want to write a query, which will match all fields inside the value chain.
Dataset:
"value_chain" : {
"category" : "Source, Make & Deliver",
"hpe_level0" : "gift Chain Planning",
"hpe_level1" : "nodemand to Plan",
"hpe_level2" : "nodemand Planning",
"hpe_level3" : "nodemand Sensing"
},
Example:
If someone searches for "gift", the query should scan through all fields, and if there is a match, return the document.
This is something I tried, but didnt work
db.sw_api.find({
value_chain: { $elemMatch: { "Source, Make & Deliver" } }
})
Sounds like you need to create $text index on all the text fields first since it performs a text search on the content of the fields indexed with a text index:
db.sw_api.createIndex({
"value_chain.category" : "text",
"value_chain.hpe_level0" : "text",
"value_chain.hpe_level1" : "text",
"value_chain.hpe_level2" : "text",
"value_chain.hpe_level3" : "text"
}, { "name": "value_chain_text_idx"});
The index you create is a composite index consisting of 5 columns, and mongo will automatically create the text namespace for you by default if you don't override it. With the above, if you don't specify the index name as
db.sw_api.createIndex({
"value_chain.category" : "text",
"value_chain.hpe_level0" : "text",
"value_chain.hpe_level1" : "text",
"value_chain.hpe_level2" : "text",
"value_chain.hpe_level3" : "text"
});
there is a potential error "ns name is too long (127 byte max)" since the text index will look like this:
"you_db_name.sw_api.$value_chain.category_text_value_chain.hpe_level0_text_value_chain.hpe_level1_text_value_chain.hpe_level2_text_value_chain.hpe_level3_text"
Hence the need to give it a name which is not too long if autogenerated by mongo.
Once the index is created, a db.sw_api.getIndexes() query will show you the indexes present:
/* 1 */
[
{
"v" : 1,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "dbname.sw_api"
},
{
"v" : 1,
"key" : {
"_fts" : "text",
"_ftsx" : 1
},
"name" : "value_chain_text_idx",
"ns" : "dbname.sw_api",
"weights" : {
"value_chain.category" : 1,
"value_chain.hpe_level0" : 1,
"value_chain.hpe_level1" : 1,
"value_chain.hpe_level2" : 1,
"value_chain.hpe_level3" : 1
},
"default_language" : "english",
"language_override" : "language",
"textIndexVersion" : 3
}
]
Once you create the index, you can then do a $text search:
db.sw_api.find({ "$text": { "$search": "gift" } })

Mongodb aggregate project string as number

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"
}

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.

Mongo: Group, push and sort

I have a mongodb collection data as per below;I want to group by EmployeedID( i.e 0001) and then sort(by age)
{
"_id" : ObjectId("54d0512191a4da7736e9db43"),
"EmployeeID" : "0001",
"Speciality" : "xxx",
"Code" : "P",
"Age" : 8
}
/* 1 */
{
"_id" : ObjectId("54d0512191a4da7736e9db44"),
"EmployeeID" : "0002",
"Speciality" : "yyyyy",
"Code" : "P",
"Age" : 6
}
/* 2 */
{
"_id" : ObjectId("54d0512191a4da7736e9db45"),
"EmployeeID" : "0001",
"Speciality" : "zzz",
"Code" : "P",
"Age" : 5
}
I know I can group using the following way.
collection.aggregate([
{$match:{"EmployeeId":0001}},
{$group:{"_id":"$EmployeeID",
"speciality":{$push:"$Speciality"},
"Code":{$push:"$Code"},
"Age":{$push:"$Age"}}}
])
But how can I using $sort here? SO my result can be something like below;
[{ "EmployeeID" : "0001",
"speciality" : [ "zzz","xxx"],
"Code" :[ "P","P"],
"Age" : [5,8]
}]
You can sort the document prior to the grouping stage:
collection.aggregate([
{$sort: {_id: -1}},
{$match:{"EmployeeId":0001}},
{$group:{"_id":"$EmployeeID",
"speciality":{$push:"$Speciality"},
"Code":{$push:"$Code"},
"Age":{$push:"$Age"}}}
])
Sorting prior to grouping may exceed mongo's memory when dealing with large collections. Fortunately, you can set allowDiskUse to true to allow mongo to write temporary files.

Resources