Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mongodb return array of strings instead array of objects

I use this code to query tokens from users in mongo db (nodejs with mongoose):

UserMdl.find().exists('token').select('token').exec(function onUsersFound(err, userMdls) {
  console.log(userMdls);
});

So I'm getting:

[ { 
  _id: 5447e36a60e5d15678de486c,
  token: '34f83483cc0ed82e17c162d'
}, ... ]

Is there any way to get an array of strings:

[ '34f83483cc0ed82e17c162d', ... ]

Right now what I'm doing is post-processing the response. I'm asking this question because I think that maybe there is a faster way to do it in mongoose/mongodb query.

EDIT

Post-processing I'm doing right now:

var results = [];
userMdls.forEach(function (userMdl) {
  results.push(userMdl.token);
});

EDIT

Thanks to saintedlama response, I've done some testing and those are the results:

Data: 14.976 documents
Tests: 100
Results:

.find().exists('token').exec(..): 1236.33 ms
.aggregate({..}): 136.07 ms

Test code:

  var start,
    end,
    time,
    firstTimes = [],
    secondTimes = [],
    test = 0,
    firstFinal,
    secondFinal,
    i,
    Q = require('q'),
    UserMdl = require('models/user'),
    u,
    tokens = [];


  function promiseWhile(condition, body) {
    var done = Q.defer();

    function repeatTest() {
      start = new Date().getTime();

      UserMdl.find().exists('token').exec(function onUserMdlFound(err, users) {
        for (u = 0; u < users.length; u += 1) {
          tokens.push(users[u].token);
        }

        end = new Date().getTime();
        time = end - start;
        firstTimes.push(time);
        start = new Date().getTime();
        tokens = [];

        UserMdl.aggregate({
          $match: {
            token: {
              $exists: true
            }
          }
        }, {
          $project: {
            _id: 0,
            token: 1
          }
        }, function onUserMdlFoundAggregate(err, users) {
          for (u = 0; u < users.length; u += 1) {
            tokens.push(users[u].token);
          }

          end = new Date().getTime();
          time = end - start;
          secondTimes.push(time);
          tokens = [];

          if (condition()) {
            Q.when(body(), repeatTest, done.reject);
          } else {
            return done.resolve();
          }
        });
      });
    }

    Q.nextTick(repeatTest);

    return done.promise;
  }


  function printResult() {
    firstFinal = 0;
    secondFinal = 0;
    for (i = 0; i < firstTimes.length; i += 1) {
      firstFinal += firstTimes[i];
      secondFinal += secondTimes[i];
    }
    console.log("First mean: " + firstFinal / i + " - Second mean: " + secondFinal / i);
  }

  test = 1;
  promiseWhile(function condition() {
    return test <= 300;
  }, function body() {
    console.log("running test: " + test);
    test++;
    return Q.delay(0); // arbitrary async

  }).then(function () {
    console.log("Finish testing");
    printResult();

  }).done();
like image 330
David Avatar asked Oct 23 '14 19:10

David


2 Answers

There is a way to select only tokens that require less processing using the mongodb aggregation framework

UserMdl.aggregate(
  { $match: { token : { $exists : true }}},
  { $project: { _id: 0, token: 1 }},
  function onUsersFound(err, tokens) {
    console.log(tokens);
  });
);

This constructs an aggregation pipeline that first matches all documents that have a token field and then selects the token field and suppresses _id selection by using _id : 0 in the $project pipeline step.

The post processing step would look like this:

function postProcess(tokenObjects) {
  if (!tokenObjects) {
    return [];
  }

  return tokenObjects.map(function(tokenObject) { return tokenObject.token; });  
}

See also the mongoose docs for more detail on the aggregation function.

like image 60
saintedlama Avatar answered Oct 21 '22 07:10

saintedlama


You don't need to do anything special. Just change one thing in your code:

Your code

UserMdl.find().exists('token').select('token').exec(function onUsersFound(err, userMdls) {
    console.log(userMdls);    // (*) Change this to...
});

Change

UserMdl.find().exec((err, users) => {
    console.log(users.map(user => user.token));    // ...(*) to this
});

No need to use select & exists functions


EDIT

The best option is Mongoose distinct function:

const tokens = await UserMdl.distinct('token');

If you want condition then:

const tokens = await UserMdl.find(/* condition */).distinct('token');

Thats it

like image 34
TalESid Avatar answered Oct 21 '22 06:10

TalESid