I want to order an array. The JSONata expression below has an incoming array as follows.
[{"id":"Air-1a",
"Controller":"ESP62",
"Cntr-TaskNo":10,
"Cntr-GPIO":13,
"name":"Air",
"valueName":"Humidity",
"Sensor":"DHT22",
(and many other key pairs)},
{next object}, ...]
I then transform the array with the following JSONata expression:
payload.(
{ "Controller" : $.Controller,
"Cntr-TaskNo": $.CntrDef.TaskNo,
"Cntr-GPIO" : $.CntrDef.GPIO,
"name" : $.name,
"valueName" : $.valueName,
"Sensor" : $.Sensor,
"id" : $.id
}
)
But now I want to - in the same JSONata expression, sort on firstly the Controller, and then the GPIO. To tried with the Controller only first.
I tried:
payload.(
{ $sort("Controller",function($l, $r){$l.Controller > $r.Controller}) : $.Controller ,
"Cntr-TaskNo": $.CntrDef.TaskNo,
"Cntr-GPIO" : $.CntrDef.GPIO,
"name" : $.name,
"valueName" : $.valueName,
"Sensor" : $.Sensor,
"id" : $.id
}
)
As well as trying to add the sort function at the end with the ~> chaining command. I also tried the order-by operator.
Could anyone point me in the right direction?
//----------
The new flow with the changed 'ESP62' to '-' that does not work:
[{"id":"874b0c77.f87418","type":"inject","z":"6f27a311.d135bc","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":200,"y":180,"wires":[["8c196590.c20638"]]},{"id":"8c196590.c20638","type":"change","z":"6f27a311.d135bc","name":"Dataset","rules":[{"t":"set","p":"payload","pt":"msg","to":"[{\"id\":\"Air-1a\",\"Controller\":\"ESP62\",\"CntrTaskNo\":10,\"CntrGPIO\":13,\"name\":\"Air\",\"valueName\":\"Humidity\",\"Sensor\":\"DHT22\",\"aaa\":\"111\",\"bbb\":\"222\",\"ccc\":\"333\"},{\"id\":\"Air-2a\",\"Controller\":\"ESP72\",\"CntrTaskNo\":11,\"CntrGPIO\":14,\"name\":\"Air\",\"valueName\":\"Humidity\",\"Sensor\":\"DHT22\",\"aaa\":\"444\",\"bbb\":\"555\",\"ccc\":\"666\"},{\"id\":\"Air-1a\",\"Controller\":\"ESP62\",\"CntrTaskNo\":2,\"CntrGPIO\":9,\"name\":\"Air\",\"valueName\":\"Humidity\",\"Sensor\":\"DHT22\",\"aaa\":\"777\",\"bbb\":\"888\",\"ccc\":\"999\"},{\"id\":\"Air-1a\",\"Controller\":\"-\",\"CntrTaskNo\":10,\"CntrGPIO\":12,\"name\":\"Air\",\"valueName\":\"Humidity\",\"Sensor\":\"DHT22\",\"aaa\":\"777\",\"bbb\":\"888\",\"ccc\":\"999\"}]","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":360,"y":180,"wires":[["13981162.14e28f"]]},{"id":"c8a256a5.a170c8","type":"debug","z":"6f27a311.d135bc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":690,"y":180,"wires":[]},{"id":"13981162.14e28f","type":"change","z":"6f27a311.d135bc","name":"Jsonata $sort","rules":[{"t":"set","p":"payload","pt":"msg","to":"($sort(payload,function($l , $r){$l.Controller > $r.Controller}) ; \t$sort(payload,function($l , $r){$l.CntrGPIO > $r.CntrGPIO}))","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":520,"y":180,"wires":[["c8a256a5.a170c8"]]}]
I suggest first sorting the dataset and afterward transform the already sorted array of objects. The transformation is trivial and you want to know how to sort, so I show below one possible solution. It uses an expression with two concatenated $sort functions.
Edited after a better understanding of the requirement.
I tested successfully a Node-RED flow using this expression in a change node:
($a := $sort(payload,function($l , $r){$l.Controller > $r.Controller}) ; $sort($a,function($l , $r){(($l.Controller = $r.Controller) and ($l.CntrGPIO > $r.CntrGPIO))}))
Flow (contain dataset set hardcoded):
[{"id":"a7814b7e.3adeb8","type":"tab","label":"Flow 4","disabled":false,"info":""},{"id":"8bf10833.c71748","type":"inject","z":"a7814b7e.3adeb8","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":140,"y":140,"wires":[["9e365564.edca08"]]},{"id":"9e365564.edca08","type":"change","z":"a7814b7e.3adeb8","name":"Dataset","rules":[{"t":"set","p":"payload","pt":"msg","to":"[{\"id\":\"Air-1a\",\"Controller\":\"ESP62\",\"CntrTaskNo\":10,\"CntrGPIO\":13,\"name\":\"Air\",\"valueName\":\"Humidity\",\"Sensor\":\"DHT22\",\"aaa\":\"111\",\"bbb\":\"222\",\"ccc\":\"333\"},{\"id\":\"Air-2a\",\"Controller\":\"ESP72\",\"CntrTaskNo\":11,\"CntrGPIO\":14,\"name\":\"Air\",\"valueName\":\"Humidity\",\"Sensor\":\"DHT22\",\"aaa\":\"444\",\"bbb\":\"555\",\"ccc\":\"666\"},{\"id\":\"Air-1a\",\"Controller\":\"ESP62\",\"CntrTaskNo\":2,\"CntrGPIO\":9,\"name\":\"Air\",\"valueName\":\"Humidity\",\"Sensor\":\"DHT22\",\"aaa\":\"777\",\"bbb\":\"888\",\"ccc\":\"999\"},{\"id\":\"Air-1a\",\"Controller\":\"-\",\"CntrTaskNo\":10,\"CntrGPIO\":12,\"name\":\"Air\",\"valueName\":\"Humidity\",\"Sensor\":\"DHT22\",\"aaa\":\"777\",\"bbb\":\"888\",\"ccc\":\"999\"}]","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":300,"y":140,"wires":[["762f6421.074fec"]]},{"id":"f827bddb.c9acd","type":"debug","z":"a7814b7e.3adeb8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":630,"y":140,"wires":[]},{"id":"762f6421.074fec","type":"change","z":"a7814b7e.3adeb8","name":"Jsonata $sort","rules":[{"t":"set","p":"payload","pt":"msg","to":"($a := $sort(payload,function($l , $r){$l.Controller > $r.Controller}) ; $sort($a,function($l , $r){(($l.Controller = $r.Controller) and ($l.CntrGPIO > $r.CntrGPIO))}))","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":460,"y":140,"wires":[["f827bddb.c9acd"]]}]
also tested in Jsonata exerciser: http://try.jsonata.org/S1IlT3y-E
You can sort the array using the following expression:
payload^(Controller, CntrDef.GPIO)
The order-by operator ^ will sort the array, first by increasing value of Controller, then by increasing value of CntrGPIO. You can then transform each object within that array
payload^(Controller, CntrDef.GPIO).{
"Controller" : Controller,
"Cntr-TaskNo": CntrDef.TaskNo,
"Cntr-GPIO" : CntrDef.GPIO,
"name" : name,
"valueName" : valueName,
"Sensor" : Sensor,
"id" : id
}
Related
Im a student just starting out on NoSQL and its just not clicking with me. im a little confused on a few points.
Any help would be greatly appreciated
1.Can documents belong to multiple collections?
2.Have I the correct syntax here for creating the Collection?
The pic is the collection er and a is just a snippet of the full er.
db.Animal.insert ( {
“animal_ID” : “XXXXXXX “,
“common_name” : “Red Squirrel”,
“IUCN” : “Least Concern (declining)”,
“photo” : “qs451xkx6qf4j”,
“extinct” : {
“when” : “null “,
“reason” : “null”
},
“invasive” : {
“threat_level” : “null”,
“threat” : “null”,
“how_to_help” : “null”
},
“native” : {
“endangerment” : “population declining“,
“how_to_help” : “providing a little extra food, planting some red squirrel-friendly shrubs and reporting any red or grey squirrel activity”
},
“Fact_sheet” : “{
“fact_id” : “ “,
“animal_id” : “ XXXXXXX “,
“order” : “ Rodentia “,
“family” : “Sciuridae “ ,
“species” : “Sciurus vulgaris “ ,
“size” : “body length 19 to 23 cm, tail length 15 to 20 cm “ ,
“weight” : “250 to 340 g “ ,
“lifespan” : “3 years , 7 to 10 in captivity “ ,
“extra” : “In Norse mythology, Ratatoskr is a red squirrel who runs up and down with messages in the world tree, Yggdrasil, and spreads gossip “ ,
“habitat” : { [
“name” : “woodland “,
“description” : “a low-density forest forming open habitats with plenty of sunlight and limited shade “
]
});
Can documents belong to multiple collections?
In MongoDB, no. In other databases, I don't know.
2.Have I the correct syntax here for creating the Collection?
To create a collection you would use https://docs.mongodb.com/manual/reference/method/db.createCollection/. This call also permits you to pass various collection options.
You are inserting a document. In MongoDB when a document is inserted, if the destination collection doesn't exist, it is created automatically by the server.
I am in need of assistance reading this confusing derived column expression which contains multiple Boolean expressions.
I've tried reading it multiple ways, but not sure which one is correct.
ISNULL(ContractNumber) ? (ISNULL(PaidLossAmount)
&& ISNULL(CaseReserveAmount)) ? NULL(DT_CY) :
(ISNULL(PaidLossAmount) ? 0 : PaidLossAmount) + (ISNULL(CaseReserveAmount)
? 0 : CaseReserveAmount) : PaidLossAmount
Could someone please advise on how this expression should be read? Thanks for your advice!
This is a nested if then else in the format of [Logical test] ? [Do this if true] : [Do this if false]
This is the formatted version.
ISNULL(ContractNumber)
?(ISNULL(PaidLossAmount) && ISNULL(CaseReserveAmount))
?NULL(DT_CY)
: (ISNULL(PaidLossAmount)
? 0
: PaidLossAmount)
+ (ISNULL(CaseReserveAmount)
? 0
: CaseReserveAmount)
: PaidLossAmount
Here's a decision tree:
"functions": [{
"script_score": {
"script": "(doc['content.acknowledgement'].value) ? ((doc['content.acknowledgementUsers'].values.contains(24)) ? 0 : 1 ) : 0"
}
}
],
"score_mode": "sum"
This is the syntax i have so far. I'm trying to give a score based on these conditions.
If the users is in acknowledgementUsers give a score of 0 else give a score of 1.
For some reason everytime the script is giving me score of 0. I'm using elastic search 5.2
Can some one please help me with this?
This question is closely related to this one and I will consider the advice given with respect to schema design in a NoSQL context, yet I'm curious to understand this:
Actual questions
Suppose you have the following document:
_id : 2 abcd
name : 2 unittest.com
paths : 4
0 : 3
path : 2 home
queries : 4
0 : 3
name : 2 query1
url : 2 www.unittest.com/home?query1
requests: 4
1 : 3
name : 2 query2
url : 2 www.unittest.com/home?query2
requests: 4
Basically, I'd like to know
if it is possible to use MongoDB's positional $ operator (details) multiple times, or put differently, in update scenarios that involve array/document structures with a "degree of nestedness" greater than 1:
{ <update operator>: { "paths.$.queries.$.requests" : value } } (doesn't work)
instead of "only" be able to use $ once for a top-level array and being bound to use explicit indexes for arrays on "higher levels":
{ <update operator>: { "paths.$.queries.0.requests" : value } }) (works)
if possible at all, how the corresponding R syntax would look like.
Below you'll find a reproducible example. I tried to be as concise as possible.
Code example
Database connection
require("rmongodb")
db <- "__unittest"
ns <- paste(db, "hosts", sep=".")
# CONNCETION OBJECT
con <- mongo.create(db=db)
# ENSURE EMPTY DB
mongo.remove(mongo=con, ns=ns)
Example document
q <- list("_id"="abcd")
b <- list("_id"="abcd", name="unittest.com")
mongo.insert(mongo=con, ns=ns, b=b)
q <- list("_id"="abcd")
b <- list("$push"=list(paths=list(path="home")))
mongo.update(mongo=con, ns, criteria=q, objNew=b)
q <- list("_id"="abcd", paths.path="home")
b <- list("$push"=list("paths.$.queries"=list(
name="query1", url="www.unittest.com/home?query1")))
mongo.update(mongo=con, ns, criteria=q, objNew=b)
b <- list("$push"=list("paths.$.queries"=list(
name="query2", url="www.unittest.com/home?query2")))
mongo.update(mongo=con, ns, criteria=q, objNew=b)
Update of nested arrays with explicit position index (works)
This works, but it involves an explicit index for the second-level array queries (nested in a subdoc element of array paths):
q <- list("_id"="abcd", paths.path="home", paths.queries.name="query1")
b <- list("$push"=list("paths.$.queries.0.requests"=list(time="2013-02-13")))
> mongo.bson.from.list(b)
$push : 3
paths.$.queries.0.requests : 3
time : 2 2013-02-13
mongo.update(mongo=con, ns, criteria=q, objNew=b)
res <- mongo.find.one(mongo=con, ns=ns, query=q)
> res
_id : 2 abcd
name : 2 unittest.com
paths : 4
0 : 3
path : 2 home
queries : 4
0 : 3
name : 2 query1
requests : 4
0 : 3
time : 2 2013-02-13
url : 2 www.unittest.com/home?query1
1 : 3
name : 2 query2
url : 2 www.unittest.com/home?query2
Update of nested arrays with positional $ indexes (doesn't work)
Now, I'd like to substitute the explicit 0 with the positional $ operator just like I did in order to have the server find the desired subdoc element of array paths (paths.$.queries).
AFAIU the documentation, this should work as the crucial thing is to specify a "correct" query selector:
The positional $ operator, when used with the update() method and acts as a placeholder for the first match of the update query selector:
I think I specified a query selector that does find the correct nested element (due to the paths.queries.name="query1" part):
q <- list("_id"="abcd", paths.path="home", paths.queries.name="query1")
I guess translated to "plain MongoDB" syntax, the query selector looks somewhat like this
{ _id: abcd, paths.path: home, paths.queries.name: query1 }
which seems like a valid query selector to me. In fact it does match the desired element/doc:
> !is.null(mongo.find.one(mongo=con, ns=ns, query=q))
[1] TRUE
My thought was that if it works on the top-level, why shouldn't it work for higher levels as well (as long as the query selector points to the right nested components)?
However, the server doesn't seem to like a nested or multiple use of $:
b <- list("$push"=list("paths.$.queries.$.requests"=list(time="2013-02-14")))
> mongo.bson.from.list(b)
$push : 3
paths.$.queries.$.requests : 3
time : 2 2013-02-14
> mongo.update(mongo=con, ns, criteria=q, objNew=b)
[1] FALSE
I'm not sure if it doesn't work because MongoDB doesn't support this or if I didn't get the R syntax right.
The positional operator only supports one level deep and only the first matching element.
There is a JIRA trackable for the sort of behaviour you want here: https://jira.mongodb.org/browse/SERVER-831
I am unsure if it will allow for more than one match but I believe it will due to the dynamics of how it will need to work.
In case you can execute your query from the MongoDB shell you can bypass this limitation by taking advantage of MongoDB cursor's forEach function (http://docs.mongodb.org/manual/reference/method/cursor.forEach/)
Here is an example with 3 nested arrays:
var collectionNameCursor = db.collection_name.find({...});
collectionNameCursor.forEach(function(collectionDocument) {
var firstArray = collectionDocument.firstArray;
for(var i = 0; i < firstArray.length; i++) {
var secondArray = firstArray[i].secondArray;
for(var j = 0; j < secondArray.length; j++) {
var thirdArray = secondArray[j].thirdArray;
for(var k = 0; k < thirdArray.length; k++) {
//... do some logic here with thirdArray's elements
db.collection_name.save(collectionDocument);
}
}
}
});
Note that this is more of a one time solution then a production code but it's going to do the job if you have to write a fix-up script.
As #FooBar mentioned in the comments of the accepted answer, this feature was implemented in 2017 with MongoDB 3.6.
To do so, you must to use positional filters with arrayFilters conditions.
Applied to your example:
updateOne(
{ "paths.home": "home" },
{ $push : {
"paths.$.queries.$[q].requests": { time: "2022-11-15" }
}
},
{ arrayFilters: [{ "q.name": "name" }] }
)
The postional operator $ refers to the filter { "paths.home": "home" }. Then, the positional filter $[q] refers to the arrayFilter { "q.name": "name" }.
Using this method, you can add as many positional filters as needed, as long as you put the condition in arrayFilters.
However, looking through the documentation of rmongodb, using arrayFilters is not possible at the moment. Alternatively, you could use another R package that has this feature implemented, such as Mongolite.
I would like to create a function which will retrieve the result from following list
var Fedsy = [
{min : 0, max : 2300 , must:0, percent:0 , excess:0},
{min : 2300, max : 11525 , must:0, percent:10 , excess:2300},
{min : 11525, max : 39750 , must:922.50, percent:15 , excess:11525},
{min : 39750, max : 93050 , must:5156.25, percent:25 , excess:39750},
{min : 93050, max : 191600 , must:18481.25, percent:28 , excess:93050},
{min : 191600, max : 413800 , must:46075.25, percent:33 , excess:191600},
{min : 413800, max : 415500 , must:119401.25, percent:35 , excess:413800},
{min : 415500, max : 1000000000 , must:119996.25, percent:39.6 , excess:415500}
];
Now I want to pull the row from above list if "inputNum" is between "min" & "max"
var result= function(inputNum){
///..???
}
Ex:if "inputNum"= 40000 the function should return
{min : 39750, max : 93050 , must:5156.25, percent:25 , excess:39750}
Since you tagged underscore.js, I understand that you want to solve the problem using that library.
As such, you can use the function _.find to find the first value that matches a condition. Think of it like a filter but just looking into the first one:
function findRange(inputNum) {
return _.find(Fedsy, function(f) {
return f.min <= inputNum && f.max >= inputNum;
});
}
The first item from Fedsy (f) to return true in the iterator function will be the value that underscore returns.
You may see it more in detail and with more reference functions at underscore.js site: http://underscorejs.org/
What I did:
I created the function you mentioned, it uses pure javascript without any libraries. It loops through each item in the Fedsy array, and looks to see if the value is in the range. If so it returns the row in question.
It also returns as soon as it finds the result not continuing to search the object.
Data
var Fedsy = [
{min : 0, max : 2300 , must:0, percent:0 , excess:0},
{min : 2300, max : 11525 , must:0, percent:10 , excess:2300},
{min : 11525, max : 39750 , must:922.50, percent:15 , excess:11525},
{min : 39750, max : 93050 , must:5156.25, percent:25 , excess:39750},
{min : 93050, max : 191600 , must:18481.25, percent:28 , excess:93050},
{min : 191600, max : 413800 , must:46075.25, percent:33 , excess:191600},
{min : 413800, max : 415500 , must:119401.25, percent:35 , excess:413800},
{min : 415500, max : 1000000000 , must:119996.25, percent:39.6 , excess:415500}
];
Function
function filterArray(inputNum){
var row;
// Foreach item in the array
for(rownum in Fedsy){
var row = Fedsy[rownum];
// If "inputNum" is greater than or equal to "row.min" & less than "row.max"
if(inputNum >= row.min && inputNum < row.max){
return row;
}
}
}
Usage
var result = filterArray(3); // returns first row
console.log(result.must); //0
If you just want the first matching row, then and application of underscore/lodash's _.find https://lodash.com/docs#find might be what you want:
var result = (function(inputNum){
return _.find(Fedsy, function(item){
return inputNum>item.min && inputNum<item.max;
});
})();
That will immediately run an anonymous function that accepts the inputNum and uses it in another anonymous function that _.find uses to check each item. The advantage of _.find is that it will stop looking as soon as it hits a result.
Ideally though, you'd write things in a more generalized way, so that you could specify your filter and what you're filtering separately, so maybe by doing something like this:
var minmaxFilterFunc = function(inputNum){
return function(item){
return inputNum>item.min && inputNum<item.max;
};
};
Now, using minmaxFilterFunc, you can create a function that will return true for a particular inputNum. Now you can use find directly, creating the proper filter function in one line. Easier to read, and also easier to reuse for filtering other collections on different numbers later.
var result = _.find(Fedsy, minMaxFilterFunc(inputNum));