Filtering dollar values with mongodb - mongodb

I'm using mongodb and I want to select objects with AppraisedValueDisplay between 2 dollar values. I have tried filtering by :
{AppraisedValueDisplay: {$gt: 5000,$lt: 50000}}
This gives no results
:
I then realized that (screenshot), The value is saved in a string format like:
$35,000
How do I filter by dollar values in mongodb?

I would also like to preface this by saying that storing numeric values in your database formatted for presentation as strings is a bad idea, which you no doubt already know.
With that out of the way, here is the aggreagation you're looking for:
db.collection.aggregate([
{
"$project": {
"AppraisedValueDisplay": {
$replaceAll: {
input: "$AppraisedValueDisplay",
find: {
$literal: "$"
},
replacement: ""
}
}
}
},
{
"$project": {
"AppraisedValueDisplay": {
"$toInt": {
$replaceAll: {
input: "$AppraisedValueDisplay",
find: ",",
replacement: ""
}
}
}
}
},
{
$match: {
AppraisedValueDisplay: {
$gt: 30000,
$lt: 40000
}
}
}
])
The idea is to replace the $ and , with empty strings and then cast the resulting strings to integers. From that point, it's just a simple matter of matching the numeric values.
Playground: https://mongoplayground.net/p/YU65M-q1QCM

You should consider saving data in a format that you usually need to retrive as other users said in the comments.
But for this specific usecase you can use aggregate query to filter the results.
First convert the value into double and then match, I tested this query with mongodb version 4.0.23
db.tempCol.aggregate([
{
'$addFields':{
'signRemoved': {
'$reduce': {
'input': { '$split': [ '$AppraisedValueDisplay', { $literal: '$' } ] },
'initialValue': '',
'in': {
'$concat': [
'$$value',
{'$cond': [{'$eq': ['$$value', '']}, '', ', ']},
'$$this'
]
}
}
},
}
},
{
'$addFields':{
'commaRemoved': {
'$reduce': {
'input': { '$split': [ '$signRemoved', ',' ] },
'initialValue': '',
'in': {
'$concat': [
'$$value',
{'$cond': [{'$eq': ['$$value', '']}, '', '']},
'$$this'
]
}
}
},
}
},
{
'$addFields':{
matchPrice: { '$toDouble': '$commaRemoved' }
}
},
{
'$match':{
matchPrice: { '$gt': 5000, '$lt': 50000}
}
},
])

Related

MongoDB search by first attr with value

Is it possible do same filtering as in js
const list = [
{
a: 1,
"mostImportant": "qwer",
"lessImportant": "rty"
},
{
a: 2,
"lessImportant": "weRt",
"notImportant": "asd",
},
{
a: 3,
"mostImportant": "qwe2",
"notImportant": "asd",
}
];
list.filter((data) => {
data.attrToSearch = data.mostImportant || data.lessImportant || data.notImportant;
return data.attrToSearch.match(/wer/i);
});
in MongoDB?
Loot at example:
https://mongoplayground.net/p/VQdfoQ-HQV4
So I want to attrToSearch contain value of first not blank attr with next order mostImportant, lessImportant, notImportant
and then match by regex.
Expected result is receive first two documents
Appreciate your help
Approach 1: With $ifNull
Updated
$ifNull only checks whether the value is null but does not cover checking for the empty string.
Hence, according to the attached JS function which skips for null, undefined, empty string value and takes the following value, you need to set the field value as null if it is found out with an empty string via $cond.
db.collection.aggregate([
{
$addFields: {
mostImportant: {
$cond: {
if: {
$eq: [
"$mostImportant",
""
]
},
then: null,
else: "$mostImportant"
}
},
lessImportant: {
$cond: {
if: {
$eq: [
"$lessImportant",
""
]
},
then: null,
else: "$lessImportant"
}
},
notImportant: {
$cond: {
if: {
$eq: [
"$notImportant",
""
]
},
then: null,
else: "$notImportant"
}
}
}
},
{
"$addFields": {
"attrToSearch": {
$ifNull: [
"$mostImportant",
"$lessImportant",
"$notImportant"
]
}
}
},
{
"$match": {
attrToSearch: {
$regex: "wer",
$options: "i"
}
}
}
])
Demo Approach 1 # Mongo Playground
Approach 2: With $function
Via $function, it allows you to write a user-defined function (UDF) with JavaScript support.
db.collection.aggregate([
{
"$addFields": {
"attrToSearch": {
$function: {
body: "function(mostImportant, lessImportant, notImportant) { return mostImportant || lessImportant || notImportant; }",
args: [
"$mostImportant",
"$lessImportant",
"$notImportant"
],
lang: "js"
}
}
}
},
{
"$match": {
attrToSearch: {
$regex: "wer",
$options: "i"
}
}
}
])
Demo Approach 2 # Mongo Playground

Merge arrays by matching similar values in mongodb

This is an extension of the below question.
Filter arrays in mongodb
I have a collection where each document contains 2 arrays as below.
{
users:[
{
id:1,
name:"A"
},
{
id:2,
name:"B"
},
{
id:3,
name:"C"
}
]
priv_users:[
{
name:"X12/A",
priv:"foobar"
},
{
name:"Y34.B",
priv:"foo"
}
]
}
From the linked question, I learnt to use $map to merge 2 document arrays. But I can't figure out to match users.name to priv_users.name to get below output.
{
users:[
{
id:1,
name:"A",
priv:"foobar"
},
{
id:2,
name:"B",
priv:"foo"
},
{
id:3,
name:"C"
}
]
}
users.name and priv_users.name don't have a consistent pattern, but users.name exists within priv_users.name.
MongoDB version is 4.0
This may not be as generic but will push you in the right direction. Consider using the operators $mergeObjects to merge the filtered document from the priv_users array with the document in users.
Filtering takes the $substr of the priv_users name field and compares it with the users name field. The resulting pipeline will be as follows
db.collection.aggregate([
{ '$addFields': {
'users': {
'$map': {
'input': '$users',
'in': {
'$mergeObjects': [
{
'$arrayElemAt': [
{
'$filter': {
'input': '$priv_users',
'as': 'usr',
'cond': {
'$eq': [
'$$this.name',
{ '$substr': [
'$$usr.name', 4, -1
] }
]
}
}
},
0
]
},
'$$this'
]
}
}
}
} }
])
If using MongoDB 4.2 and newer versions, consider using $regexMatch operator for matching the priv_users name field with the users name field as the regex pattern. Your $cond operator now becomes:
'cond': {
'$regexMatch': {
'input': '$$usr.name',
'regex': '$$this.name',
'options': "i"
}
}

How to query MongoDB for complex data

I have a table structured as follows:
db.l2vpn_fdb_database.findOne()
{
_id: ObjectId("5f5257f11324c04122714445"),
hostname: "spine01-drt-red",
l2vpn_fdb_database: {
MAC: [
{
IfIndex: "1631",
MacAddr: "00-00-0C-07-AC-01",
SrvID: "1",
VsiName: "EVPN",
},
{
IfIndex: "0",
MacAddr: "00-00-0C-07-AC-02",
SrvID: "0",
VsiName: "EVPN",
},
{
IfIndex: "1631",
MacAddr: "00-00-0C-07-AC-0A",
SrvID: "1",
VsiName: "EVPN",
},
],
},
}
I'd like to search for "MacAddr" object, could you help me figure out based on above? So essentially I'd like to be able to parse database for a MacAddress assuming it's there and be able to get "IfIndex" for further processing.
Thank you.
You can use $filter to get matched objects
db.collection.aggregate([
{
$project: {
l2vpn_fdb_database: {
$filter: {
input: "$l2vpn_fdb_database.MAC",
cond: {
$eq: [
"$$this.IfIndex",
"1631"
]
}
}
}
}
}
])
Working Mongo playground
for Hostname with macAddr try like this,
db.collection.aggregate([
{
$project: {
l2vpn_fdb_database: {
$filter: {
input: "$l2vpn_fdb_database.MAC",
cond: {
$eq: [
"$$this.IfIndex",
"1631"
]
}
}
},
hostname:{
$eq:['$hostname','spine01-drt-red']
}
}
}
])
This query could help you.
b.l2vpn_fdb_database.findOne({
"l2vpn_fdb_database.MAC.MacAddr": "00-00-0C-07-AC-01",
},
{
"l2vpn_fdb_database.MAC.$": 1
})
The result is the same document just with 1 element in the array
Result:
{
"_id": ObjectId("5f5257f11324c04122714445"),
"l2vpn_fdb_database": {
"MAC": [
{
"IfIndex": "1631",
"MacAddr": "00-00-0C-07-AC-01",
"SrvID": "1",
"VsiName": "EVPN"
}
]
}
}

If condition in MongoDB for Nested JSON to retrieve a particular value

I've nested JSON like this. I want to retrieve the value of "_value" in second level. i,e. "Living Organisms" This is my JSON document.
{
"name": "Biology Book",
"data": {
"toc": {
"_version": "1",
"ge": [
{
"_name": "The Fundamental Unit of Life",
"_id": "5a",
"ge": [
{
"_value": "Living Organisms",
"_id": "5b"
}
]
}
]
}
}
}
This is what I've tried, using the "_id", I want to retrieve it's "_value"
db.products.aggregate([{"$match":{ "data.toc.ge.ge._id": "5b"}}])
This is the closest I could get to the output you mentioned in the comment above. Hope it helps.
db.collection.aggregate([
{
$match: {
"data.toc.ge.ge._id": "5b"
}
},
{
$unwind: "$data.toc.ge"
},
{
$unwind: "$data.toc.ge.ge"
},
{
$group: {
_id: null,
book: {
$push: "$data.toc.ge.ge._value"
}
}
},
{
$project: {
_id: 0,
first: {
$arrayElemAt: [
"$book",
0
]
},
}
}
])
Output:
[
{
"first": "Living Organisms"
}
]
You can check what I tried here
If you are using Mongoid:
(1..6).inject(Model.where('data.toc.ge.ge._id' => '5b').pluck('data.toc.ge.ge._value').first) { |v| v.values.first rescue v.first rescue v }
# => "Living Organisms"
6 is the number of containers to trim from the output (4 hashes and 2 arrays).
If I understand your question correctly, you only care about _value, so it sounds like you might want to use a projection:
db.products.aggregate([{"$match":{ "data.toc.ge.ge._id": "5b"}}, { "$project": {"data.toc.ge.ge._value": 1}}])

mongodb convert array elements from int to string

I have a mongodb full of records with mixed types and need to sanitize data to make some sense in back end application.
To change type(NumberLong to String) of a normal field is easy, just cast it to string in Javascript like:
db.foo.find({ field: { $type: 18 } }).forEach(function (x) {
x.field = new String(x.field); // convert field to string
db.foo.save(x);
});
But how do I change array elements from NumberLong to String?
For example I have field:
"elements" : { "top" : {"random" : [ NumberLong(12) , NumberLong(20), NumberLong(13)] } }
and I need all the elements of elements.top.random as strings.
Do I need to do "foreach" element of the array or is there any better way?
Any Javascript guru can help me out?
Before posting I just could not find an answer, and now I did find something similar and adopted...
so here it is:
db.foo.find( {"elements.top.random": {$type:18}} ).forEach( function (x) {
var arr = [];
x.elements.top.random.forEach( function (e) { arr.push("" + e); } );
x.elements.top.random = arr;
db.foo.save(x);
});
We came across this issue today with a more recent version of MongoDB (v4.0.0). Since the save() API is deprecated, we had to use updateOne(), resulting in the following code:
db.foo.find( {"elements.top.random": {$type:18}} ).forEach(function (x) {
var stringValues = [];
x.elements.top.random.forEach(function(e) {
stringValues.push("" + e);
});
db.foo.updateOne(
{ _id: x._id },
{ $set: { "elements.top.random" : stringValues } }
);
});
With MongoDB v4.2+, you can do an update with aggregation pipeline. Use $reduce and $toString to do the string conversion and string concat.
db.collection.update({
"elements.top.random": {
$type: 18
}
},
[
{
$set: {
"elements.top.random": {
"$reduce": {
"input": "$elements.top.random",
"initialValue": "",
"in": {
"$concat": [
"$$value",
{
$toString: "$$this"
}
]
}
}
}
}
}
],
{
multi: true
})
Here is the Mongo playground for your reference.
With a slightly older version of MongoDB (v4.0+), you can still have $toString available. You can use the old "aggregate-then-update" approach.
db.collection.aggregate([
{
$match: {
"elements.top.random": {
$type: 18
}
}
},
{
$set: {
"elements.top.random": {
"$reduce": {
"input": "$elements.top.random",
"initialValue": "",
"in": {
"$concat": [
"$$value",
{
$toString: "$$this"
}
]
}
}
}
}
}
]).forEach(agg => {
db.collection.update(
{ _id: agg._id },
{ $set: { "elements.top.random" : agg.elements.top.random } }
)
})