1

So I have some query to database (mongodb) which will order results by value field.

all := EValues{}
err := con.Find(bson.M{"name": "somename}).Sort("-value").All(&all)

Json output for this looks like:

 "values": [
    {
      "user_name": "guest7485",
      "value": 8911,
      "value_date": "2016-03-09T14:40:34.512Z"
    },
    {
      "user_name": "guest7485",
      "value": 539,
      "value_date": "2016-03-07T14:11:05.217Z"
    },
    {
      "user_name": "guest7485",
      "value": 221,
      "value_date": "2016-03-07T14:11:08.853Z"
    },
    {
      "user_name": "guest7485",
      "value": 77,
      "value_date": "2016-03-07T14:11:12.377Z"
    }
  ]

In my json response I need to add parameter "position" which should be basically equal to 1 - first result, 2 - second result and so on, for all results. So my final output should be:

 "values": [
    {
      "position": 1,
      "user_name": "guest7485",
      "value": 8911,
      "value_date": "2016-03-09T14:40:34.512Z"
    },
    {
      "position": 2,
      "user_name": "guest7485",
      "value": 539,
      "value_date": "2016-03-07T14:11:05.217Z"
    },
    {
      "position": 3,
      "user_name": "guest7485",
      "value": 221,
      "value_date": "2016-03-07T14:11:08.853Z"
    },
    {
      "position": 4,
      "user_name": "guest7485",
      "value": 77,
      "value_date": "2016-03-07T14:11:12.377Z"
    }
  ]

I'm wondering how to solve this with mgo and go in general, and I would be really greatfull if someone can give me the most efficient way to solve this.

Update:

Definition of Evalues is bellow:

type EValue struct {
    ID bson.ObjectId `json:"-" bson:"_id,omitempty"`
    Name string             `json:"-" bson:"name"`
    UserId    bson.ObjectId `json:"-" bson:"userId"`
    UserName  string        `json:"user_name" bson:"userName"`
    Value     int64         `json:"value" bson:"value"`
    AddedTime time.Time     `json:"value_date" bson:"addedTime"`
}

type EValues []EValue
Splendid
  • 1,317
  • 4
  • 20
  • 41

2 Answers2

0

Add a position field to EValue:

type EValue struct {
    ... other fields here
    Position int `json:"position" bson:"-"`
}

Loop through db results and set the field:

for i := range all {
    all[i].Position = i + 1
}

Marshal the result as JSON.

Charlie Tumahai
  • 113,709
  • 12
  • 249
  • 242
  • Is this most efficient way, since I'm really not fan of idea to iterate again trough whole array ? – Splendid Mar 10 '16 at 15:19
  • It's probably the most efficient way. Even if it's not, the cost is negligible compared to the cost of unmarshalling the BSON value or marshalling the JSON value. – Charlie Tumahai Mar 10 '16 at 15:43
0

With MongDB 3.2 this can be done using the $unwind operator where you can pass an object with the field path and the field includeArrayIndex which will hold the array index:

pipeline = [
    { "$match": {"name": "somename"} },
    { "$unwind": { "path": "$values", "includeArrayIndex": "position" } },
    {
        "$project": {
            "name": 1,
            "newarray.position": "$position",
            "newarray.user_name": "$values.user_name",
            "newarray.value_date": "$values.value_date",
            "newarray.value": "$values.value",
        }
    },
    {
        "$group": {
            "_id": "$name",
            "values": { "$push": "$newarray" }
        }
    }    
]
db.test.aggregate(pipeline);

Output

> db.test.aggregate(pipeline).pretty();
{
        "_id" : "somename",
        "values" : [
                {
                        "position" : NumberLong(0),
                        "user_name" : "guest8911",
                        "value_date" : "2016-03-09T14:40:34.512Z",
                        "value" : 8911
                },
                {
                        "position" : NumberLong(1),
                        "user_name" : "guest7485",
                        "value_date" : "2016-03-07T14:11:05.217Z",
                        "value" : 539
                },
                {
                        "position" : NumberLong(2),
                        "user_name" : "guest7485",
                        "value_date" : "2016-03-07T14:11:08.853Z",
                        "value" : 221
                },
                {
                        "position" : NumberLong(3),
                        "user_name" : "guest7485",
                        "value_date" : "2016-03-07T14:11:12.377Z",
                        "value" : 77
                }
        ]
}
>

If this is not supported with the mgo driver, then a not so efficient approach would be to use Map-Reduce for this. The following mongo shell example demonstrates how you can run the operation:

Populate test collection:

db.test.insert({ 
    "name": "somename", 
    "values": [
        {
          "user_name": "guest8911",
          "value": 8911,
          "value_date": "2016-03-09T14:40:34.512Z"
        },
        {
          "user_name": "guest7485",
          "value": 539,
          "value_date": "2016-03-07T14:11:05.217Z"
        },
        {
          "user_name": "guest7485",
          "value": 221,
          "value_date": "2016-03-07T14:11:08.853Z"
        },
        {
          "user_name": "guest7485",
          "value": 77,
          "value_date": "2016-03-07T14:11:12.377Z"
        }
      ]
})

Run the following map-reduce operation:

> mr = db.runCommand({
    "mapreduce": "test",
    "map": function() {
        var arr = []
        for(var i=0; i < this.values.length; i++){
            var val = this.values[i];
            val["position"] = i+1;
            arr.push(val);
        }
        emit(this._id, arr);
    },
    "reduce" : function() {}, 
    "out": "test_keys"
})

Query resulting collection:

> db[mr.result].find().pretty()
{
        "_id" : ObjectId("56e18ab84b9018ec86d2a6bd"),
        "value" : [
                {
                        "user_name" : "guest8911",
                        "value" : 8911,
                        "value_date" : "2016-03-09T14:40:34.512Z",
                        "position" : 1
                },
                {
                        "user_name" : "guest7485",
                        "value" : 539,
                        "value_date" : "2016-03-07T14:11:05.217Z",
                        "position" : 2
                },
                {
                        "user_name" : "guest7485",
                        "value" : 221,
                        "value_date" : "2016-03-07T14:11:08.853Z",
                        "position" : 3
                },
                {
                        "user_name" : "guest7485",
                        "value" : 77,
                        "value_date" : "2016-03-07T14:11:12.377Z",
                        "position" : 4
                }
        ]
}
>

Now given the listing above, you can assemble your query in mgo using MapReduce

job := mgo.MapReduce{
      Map:    "function(){var arr=[];for(var i=0;i<this.values.length; i++){var val=this.values[i];val['position']=i+1;arr.push(val);};emit(this._id,arr);}",
      Reduce: "function() { }",
  }
  var result []struct { Id int "_id"; Value []EValue }
  _, err := collection.Find(nil).MapReduce(job, &result)
  if err != nil {
      panic(err)
  }
  for _, item := range result {
      fmt.Println(item.Value)
  }

For more details, check the documentation: https://godoc.org/labix.org/v1/mgo#MapReduce:

chridam
  • 100,957
  • 23
  • 236
  • 235