Custom functions calculated columns mongodb projection

10,564

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.

Share:
10,564
Manar Husrieh
Author by

Manar Husrieh

Application Developer in a wide variety of business applications. Particularly interested in building .Net applications that helps in automating the business process. In addition, building business intelligence models for enterprises using Oracle BI. Looking for an opportunity to enter the game development market and achieve the main goal of my study in this field which is to become a great game developer and designer. Specialties:Desktop Applications (C# 4, VB.NET 4, WPF, Windows Forms), Web Based Applications (ASP.NET, ASP.NET MVC4 , PHP), Database (Microsoft SQL Server 2005-2008-2012), Network Application Development (Sockets with C#, C++, C), Web Services

Updated on June 18, 2022

Comments

  • Manar Husrieh
    Manar Husrieh almost 2 years

    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.