Match within a Range of two fields from an Array of Values - javascript

I have an array of numbers [111, 444, 777]
I want to look inside these 2 documents
{
_id: 1,
lookHere: {rangeLow: 333, rangeHigh: 555}
},
{
_id: 2,
lookHere: {rangeLow: 222, rangeHigh: 333}
}
I want to write a query that returns only the 1st document since my array of numbers includes 444 which is between 333 and 555.
Is there a query that can achieve this result in mongo/mongoose ?

You want an $or query. You can .map() the source array into the arguments for $or:
var inputs = [111, 444, 777];
collection.find({
"$or": inputs.map( n => ({
"lookHere.rangeLow": { "$lt": n },
"lookHere.rangeHigh": { "$gt": n }
}) )
})
That is basically looking to see if the Low value is less than and the High is greater than each of the current elements, and returns as true when any match both those conditions.
Note that all MongoDB query arguments are implicitly AND conditions unless stated otherwise.

Related

using R.groupWith (ramda)

I'm trying to group the following array acc to dateValue, but can't seem to get it to work. What am I missing here?
const dates = [
{"id":83,"dateValue":"2017-05-24"},
{"id":89,"dateValue":"2017-05-28"},
{"id":91,"dateValue":"2017-05-25"},
{"id":78,"dateValue":"2017-05-24"},
{"id":84,"dateValue":"2017-05-25"}
]
const groups = R.groupWith(R.eqProps('dateValue'),dates)
console.log(groups)
<script src="//cdn.jsdelivr.net/ramda/latest/ramda.min.js"></script>
I've included a link to ramda repl with this code loaded
expected result:
[ [ { dateValue: "2017-05-24", id: 83 },
{ dateValue: "2017-05-24", id: 78 } ],
[ { dateValue: "2017-05-28", id: 89 } ],
[ { dateValue: "2017-05-25", id: 91 } ],
[ { dateValue: "2017-05-25", id: 84 } ] ]
There are several differences between groupBy and groupWith.
groupBy accepts a function which yields a grouping key for a single item. groupWith accepts a binary predicate that says whether two items match.
groupBy collects all items into single collections. groupWith only collects matching itemss which are consecutive.
groupBy returns an object that maps those keys to the list of matching items. groupWith returns a list of lists of (again consecutive) matching items.
It's that second point which is tripping you up. You probably want groupBy:
R.groupBy(R.prop('dateValue'))(dates)
// or
R.compose(R.values, R.groupBy(R.prop('dateValue')))(dates);
You can see this in action on the Ramda REPL.

MongoDB - How to use a field value in a $slice projection?

Given a simple document such as:
{
myArray : [12, 10, 40, -1, -1, ..., -1], //'-1' is a int placeholder
latestUpdatedIndex : 2
}
I know how many values I will put in myArray so I preallocate space with the -1 values to make updates more efficient. I keep track of the latest value I updated in latestUpdatedIndex
Can I use the value of latestUpdatedIndex in a $slice projection like the one below?
coll.find({}, {'myArray':{'$slice':[0, <value of latestUpdatedIndex>]}
You can't do that using a normal query but you can do it using aggregation (in MongoDB 3.2):
db.collection.aggregate([
{ $project: { mySlicedArray: { $slice: [ "$myArray", "$latestUpdatedIndex" ] } } }
])

JSON Path: Outputting value plus parent hierarchically

I'm new to JSON Path, so this may be simpler than I expect, if it's even possible.
I have a JSON block that consists of a set of groups, with each group having a set of fields. Each group also has a sequence, as does each field. The field sequence represents order within a group, and the group sequence represents the display order of the group itself.
I generated this programmatically in C# off of a flat list of items (groups and fields) with different sequence values, so I want to validate my output and, by extension, my grouping algorithm.
The JSON block looks (vastly simplified) like this:
{
"groups": [
{
"sequence": 0,
"fields": [
{
"sequence": 0
},
{
"sequence": 1
},
{
"sequence": 2
}
]
},
{
"sequence": 1,
"fields": [
{
"sequence": 0
},
{
"sequence": 1
}
]
},
{
"sequence": 2,
"fields": [
{
"sequence": 0
},
{
"sequence": 1
},
{
"sequence": 2
}
]
}
]
}
I'm trying to validate this with JSON Path. For this, I don't have a tool, so I'm using the jsonpath online evaluator.
What I'm aiming for is output along the lines of this:
'0'
'sequence' => '0'
'fields'...
'0'
'sequence' => '0'
'1'
'sequence' => '1'
'2'
'sequence' => '2'
// etc...
In other words, I'm looking for a JSON path query that can return the sequence of each group plus the sequence of each field in the group, in a hierarchy.
To get the sequence of the groups, I'm using the following. This works fine, and gives me useful output, since groups are the top-level item already:
$.groups[*].sequence
Output:
'0' => "0"
'1' => "1"
'2' => "2"
To get the sequence of the fields in the groups, I'm using the following. The output here is less useful, as it's a flat list that can get difficult to read if I have dozens or more fields spread out across several groups.
$.groups[*].fields[*].sequence
Output:
'0' => "0"
'1' => "1"
'2' => "2"
'3' => "0"
'4' => "1"
'5' => "0"
'6' => "1"
'7' => "2"
So ultimately, the question is this: is there a JSON path query that will let me get the information I need here hierarchically? Or am I using the wrong tool for the job? I suppose I could write a few lines of code in JavaScript that will let me do this if I have to, but it seems like JSON path might be a powerful tool to learn, so if I can do it here I've learned something.
You do not need a variable sequence. When you need sequence, move item from one group to another, sort you can use array in object, and object in array.
Array use to sort, move items, change sequence. Objects to grouping of attributes within a single item
var groups =
[
{"fields": [
{name: 'field1'},
{name: 'field2'},
{name: 'field3'}
],
},
{"fields": [
{name: 'field1'},
{name: 'field2'},
{name: 'field3'}
]
},
{"fields": [
{name: 'field1'},
{name: 'field2'},
{name: 'field3'}
]
}
];
console.log(groups[0].fields); // Array [ Object, Object, Object ]
console.log(groups[2].fields[0].name); // field1
Function splice is the best to change sequence. In same fields, and another fields
Example for change sequence in same fields
var oldIndex = 2, newIndex = 0, item = groups[0].fields.splice(oldIndex, 1)[0];
console.log(item);
groups[0].fields.splice(newIndex, 0, item);
console.log(groups);

Mongo $and selector

How does mongo $and selector work? I have trouble getting correct results back.
Let's say I have a collection something like this:
{ "_id" : "F7mdaZC2eBQDXA5wx", "quantity" : 5 }
{ "_id" : "F7mdaZC2eBQDXA5wx", "quantity" : 9 }
{ "_id" : "F7mdaZC2eBQDXA5wx", "quantity" : 34 }
{ "_id" : "F7mdaZC2eBQDXA5wx", "quantity" : 66 }
and I run query:
var selectorMin = 9;
var selectorMax = 42;
ScrapReport.find({ $and: [ { quantity: { $gte: selectorMin }, quantity: { $lte: selectorMax } } ] })
I would expect mongo to return me only 9 and 34. But for some reason it also returns 5 and 66.
What's wrong with my query?
Your query is returning all the documents in that sample because it is first looking for documents whose quantity >= 9 i.e. 9, 34 and 66 AND combines that query with documents whose quantity <= 42 i.e 34, 9 and 5. It's not looking for documents within a particular range but your query explicitly looks for all documents that satisify two ranges i.e.
Documents which satisfy "quantity >= 9"
+
Documents which satisfy "quantity <= 42"
not
Documents which satisfy "9 <= quantity <= 42"
Just simplify your query to
ScrapReport.find({ "quantity": { "$gte": selectorMin, "$lte": selectorMax } })
That way, you specify a range for MongoDB to filter your documents with i.e.
9 <= quantity <= 42
Specifying a comma separated list of expressions implies an implicit AND operation and use an explicit AND with the $and operator when when the same field or operator has to be specified in multiple expressions.
Using an implicit AND operation like the other answers suggested would work. But I would like to dig deeper into the specifics. Why is your query not working as you expected it to work?
Why? Why is this seemingly correct query of yours returning not so correct results? After all, whether you use implicit or explicit AND operation should be a matter of your choice and you should be able to achieve your goal irrespective of which form you use. How to make your query work with an explicit AND operation?
Let us look at the syntax of the AND operation.
{ $and: [ { <expression1> }, { <expression2> } , ... , { <expressionN> } ] }
The value of your AND operator should be an array containing expressions on which you would like to perform the AND operation.
After a first glance at your query, everything looks fine. But if you take a moment to look deeper, you would see that your query is not matching the AND syntax exactly. It is still syntactically correct. No doubt about that. But it is logically incorrect. I will explain how.
This is your $and operator value
{ $and: [ { quantity: { $gte: selectorMin }, quantity: { $lte: selectorMax } } ] }
You think you have an expression1 quantity: { $gte: selectorMin } and an expression2 quantity: { $lte: selectorMax }. An AND operation with these expressions should return the documents with quantity 9 and 34. But actually, all you have is one expression. Pay close attention to the braces. You have added both these expressions in a single {} block. Do you see it? So effectively, there is no 2nd expression for the AND operator to work with. But AND operator requires two or more expressions to function properly.
So your query is of the form
{ $and: [ { <expression1> } ] }
With an incorrect form, the results will also be incorrect. The correct query using an explicit AND operation would be
ScrapReport.find({ $and: [ { quantity: { $gte: selectorMin } }, { quantity: { $lte: selectorMax } } ] })
Do you see the difference? Try this query and you will get the results that you expected in the first place.
If you are not satisfied by just having the answer and are curious to know how Mongo interpreted your first query, read further.
Consider this query
ScrapReport.find({ quantity: 9 })
What would you expect the result to be? If you expected Mongo to return a single document whose value in the quantity field is 9, you are right. That is exactly what the result is. Now consider the same query with a small twist.
ScrapReport.find({ quantity: 9, quantity: 5 })
What would the result be now? Things are getting interesting now, huh? If you execute this query and have a look at the result, you will still see only a single document. But the value in the quantity field is 5. Now that is interesting!
ScrapReport.find({ quantity: 9, quantity: 5, quantity: 34 })
What about this? The result is still a single document with value in the quantity field being 34. You can try other combinations. What you will find out is this -
Within an expression, if you are referencing a field multiple times, the result will be determined by the last reference to that field in that expression.
Now apply this concept to your original query. It has already been pointed out that you have a single expression with two parts quantity: { $gte: selectorMin } and quantity: { $lte: selectorMax }. Since within an expression, you are referring to the same field twice, only the last one will be relevant. The selection criteria will be quantity: { $lte: selectorMax }. The result will be 3 documents with quantity values 5, 9 and 34.
If you swap the order i.e. write quantity: { $lte: selectorMax } first and then quantity: { $gte: selectorMin }, the selection criteria will now be determined by quantity: { $gte: selectorMin }. The result will be 3 documents with quantity values 9, 34 and 66.
Although it wasn't your intention, your original query is effectively
ScrapReport.find({ quantity: { $gte: selectorMin }, quantity: { $lte: selectorMax } })
When you miss braces or add them at the wrong position, it can completely change the meaning of your query.
Moral - Pay close attention to where you place your braces in complex queries.
Actually you have two problems in there:
Your query is equivalent to the following:
ScrapReport.find( { "$and": [{ "quantity": { "$lte": selectorMax } } ] } )
or even better:
ScrapReport.find( { "quantity": { "$lte": selectorMax } } )
The reason is because duplicate key are allowed in JSON document but the last value for a given key is maintained.
So this will only return all those documents where "quantity" is less than or equal selectorMax.
The second problem is already mentioned in #chridam's answer so the right query is:
ScrapReport.find({ "quantity": { "$gte": selectorMin, "$lte": selectorMax } })

How to make a MongoDB query sort on strings with -number postfix?

I have a query:
ownUnnamedPages = Entries.find( { author : this.userId, title : {$regex: /^unnamed-/ }}, {sort: { title: 1 }}).fetch()
That returns the following array sorted:
[ {
title: 'unnamed-1',
text: '<p>sdaasdasdasd</p>',
tags: [],
_id: 'Wkxxpapm8bbiq59ig',
author: 'AHSwfYgeGmur9oHzu',
visibility: 'public' },
{
title: 'unnamed-10',
text: '',
author: 'AHSwfYgeGmur9oHzu',
visibility: 'public',
_id: 'aDSN2XFjQPh9HPu4c' },
{
title: 'unnamed-2',
text: '<p>kkhjk</p>',
tags: [],
_id: 'iM9FMCsyzehQvYGKj',
author: 'AHSwfYgeGmur9oHzu',
visibility: 'public' },
{
title: 'unnamed-3',
text: '',
tags: [],
_id: 'zK2w9MEQGnwsm3Cqh',
author: 'AHSwfYgeGmur9oHzu',
visibility: 'public' }]
The problem is that it seems to sort on the first numeric character so it thinks the proper sequence is 1, 10, 2, 3, etc....
what I really want is for it to sort on both the whole numerical part so that 10 would be at the end.
I'd prefer not to do this by having additional numbers such as 01 or 001 for the numbers.
How would I do that?
You can use
db.collectionName.find().sort({title: 1}).collation({locale: "en_US", numericOrdering: true})
numericOrdering flag is boolean and is Optional. Flag that determines whether to compare numeric strings as numbers or as strings.
If true, compare as numbers; i.e. "10" is greater than "2".
If false, compare as strings; i.e. "10" is less than "2".
Default is false.
See mongo's collation documentation for an updated explanation of those fields.
MongoDB can't sort by numbers stored as strings. You either have to store the number as an integer in its own field, pad with leading zeroes, or sort the results after they've been returned from the database.
If you 0 pad the numbers you will be able to search as a string in the right order, so instead of 0,1,2,3,4,5,6,7,8,9,10,11...
use 01,02,03,04,05,06,07,08,09,10,11...
and a string search will return them in order.
The mongo documentation said you can use Collation for this goal
as #Eugene Kaurov said you can use
.collation({locale: "en_US", numericOrdering: true})
this is the official documentation:
mongo ref
and be aware that the accepted answer is not correct now
In mongo is not possible (sort strings in ascii) but you can sort with the below function after you get all documents from the collection
const sortString = (a, b) => {
const AA = a.title.split('-');
const BB = b.title.split('-');
if (parseInt(AA[1], 10) === parseInt(BB[1], 10)) {
return 0;
}
return (parseInt(AA[1], 10) < parseInt(BB[1], 10)) ? -1 : 1;
};
document.sort(sortString);
In my case we work with aggregations. The approach was to sort using the length of our string; only works when the text part is always the same (unnamed- in your case)
db.YourCollection.aggregate([
{
$addFields: {
"TitleSize": { $strLenCP: "$Title" }
}
},
{
$sort: {
"TitleIdSize": 1,
"Title": 1
}
}
]);
Now we sort using length, the second sort will use the content.
Example:
"unnamed-2", Titlesize: 9
"unnamed-7", Titlesize: 9
"unnamed-30", Titlesize: 10
"unnamed-1", Titlesize: 9
The first sort will put the ids in this order: 2, 7, 1, 30. Then the second sort will put the ids in the correct order: 1, 2, 7, 30.

Categories

Resources