Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom functions calculated columns mongodb projection

Tags:

mongodb

I am trying to use projection to get a column calculated using a custom function on columns in collection but I couldn't't figure a way how to do it. What I could do is this:

db.collection.aggregate([$project:{column1:1, calculatedCol: {$literal:[ jaro_Winkler("how to access column name")]}] )

The code might have syntax error because I don't have the code with me right now.

like image 376
Manar Husrieh Avatar asked Nov 28 '22 07:11

Manar Husrieh


1 Answers

You seem to think it is possible to call a JavaScript function in the aggregation pipeline, but you cannot do this. You are mistaking what is actually "interpolation" of a variable from a function result for execution within the pipeline.

For instance If I do this:

var getNumbers = function() { return [ 1,2,3 ] };

Then I call this:

db.collection.aggregate([
    { "$project": {
        "mynums": getNumbers()
    }}  
])

Then what actually happens in the JavaScript shell the values are being "interpolated" and "before" the instruction is sent to the server, like this:

db.collection.aggregate([
    { "$project": {
        "mynums": [1,2,3]
    }}  
])

To further demonstrate that, store a function "only" on the server:

db.system.js.save({ "_id": "hello", "value": function() { return "hello" } })

Then try to run the aggregation statement:

db.collection.aggregate([
    { "$project": {
        "greeting": hello()
    }}  
])

And that will result in an exception:

E QUERY [main] ReferenceError: hello is not defined at (shell):1:69

Which is because the execution is happening on the "client" and not the "server" and the function does not exist on the client.

The aggregation framework cannot run JavaScript, as it has no provision to do so. All operations are performed in native code, with no JavaScript engine being invoked. Therefore you use the operators there instead:

db.collection.aggregate([
    { "$project": {
        "total": { "$add": [ 1, 2 ] },
        "field_total": { "$subtract": [ "$gross", "$tax" ] }
    }}  
])   

If you cannot use the operators to acheive the results then the only way you can run JavaScript code is to run mapReduce instead, which of course uses a JavaScript engine to interface with the data from the collection. And from there you can also referce a server side function inside your logic if you need to:

{ "key": 1, "value": 1 },
{ "key": 1, "value": 2 },
{ "key": 1, "value": 3 }

db.system.js.save({ "_id": "square", "value": function(num) { return num * num } })

db.collection.mapReduce(
    function() {
        emit(this.key,square(this.value))
    },
    function(key,values) {
        return Array.sum(values);
    },
    { "out": { "inline": 1 } }
)

Returns:

{
    "_id": 1,
    "value": 14
}

So this is not about "how to pass in a field value" but really about the fact that the aggregation framework does not support JavaScript in any way, and that what you thought was happening is not actually the case.

like image 125
Blakes Seven Avatar answered Jan 05 '23 16:01

Blakes Seven