Automattic / mongoose

MongoDB object modeling designed to work in an asynchronous environment.
https://mongoosejs.com
MIT License
26.88k stars 3.83k forks source link

Subdocument search returns parent with all subdocs or null #2897

Closed IvanMMM closed 9 years ago

IvanMMM commented 9 years ago

Hello. I'm using Win7x64, nodejs 12.2, mongo 3.0.2, mongoose 4.0.1.

Here's my request:

    var dateStart=new Date('2015-04-17T10:06:00.000Z').toISOString();
    var dateEnd=new Date('2015-04-17T10:10:00.000Z').toISOString();

    var searchQueryTerminal = TickModel
    .findOne({
        symbol:symbol,
        'tickData.timestamp':{
            $gte:dateStart,
            $lte:dateEnd
        },
        broker:{$in: broker},
        MTVersion:MTVersion
    });

    searchQueryTerminal.exec(function(err,result){
        console.log("LEN: "+result.tickData.length);
    });

This request always returns full list of subdocuments or null.

Using elemMatch also does noting. Parent will full subdoc list or null.

        .findOne({
            symbol:symbol,
            'tickData.timestamp':{
                $gte:dateStart,
                $lte:dateEnd
            },
            broker:{$in: broker},
            MTVersion:MTVersion
        })
        .elemMatch("tickData", { "timestamp": {"$gte": dateStart, "$lte": dateEnd } });

Request from mongo shell returns parent and one subdocument, as expected.

db.ticks.findOne({
    "symbol": 'EURUSD',
    "tickData.timestamp": {
        "$gte": ISODate("2015-04-17T10:06:00.000Z"),
        "$lte": ISODate("2015-04-17T10:10:00.000Z")
    },    
    "MTVersion": 4
}, {
    "symbol": 1,
    "MTVersion": 1,
    "tickData": {
        "$elemMatch": {
            "timestamp": {
                "$gte": ISODate("2015-04-17T10:06:00.000Z"),
                "$lte": ISODate("2015-04-17T10:10:00.000Z")
            }
        }
    }
});

Here's discussion at stackoverflow

vkarpov15 commented 9 years ago

Try using the explicit projection form in mongoose:

TickModel.findOne({
    "symbol": 'EURUSD',
    "tickData.timestamp": {
        "$gte": ISODate("2015-04-17T10:06:00.000Z"),
        "$lte": ISODate("2015-04-17T10:10:00.000Z")
    },    
    "MTVersion": 4
}, {
    "symbol": 1,
    "MTVersion": 1,
    "tickData": {
        "$elemMatch": {
            "timestamp": {
                "$gte": ISODate("2015-04-17T10:06:00.000Z"),
                "$lte": ISODate("2015-04-17T10:10:00.000Z")
            }
        }
    }
}, callback);

Does that work for you?

IvanMMM commented 9 years ago

Yes, it worked as expected. One parent doc and one subdoc.

vkarpov15 commented 9 years ago

This is because mongoose's .elemMatch() function is a helper for MongoDB's $elemMatch query operator rather than the $elemMatch projection operator. See the mongoose docs on elemMatch. Admittedly the distinction between the two is tricky.

You can either use the explicit form I gave you or the .select() helper:

TickModel.
  findOne({
    "symbol": 'EURUSD',
    "tickData.timestamp": {
        "$gte": ISODate("2015-04-17T10:06:00.000Z"),
        "$lte": ISODate("2015-04-17T10:10:00.000Z")
    },    
    "MTVersion": 4
  }).
  select(
    "tickData": {
        "$elemMatch": {
            "timestamp": {
                "$gte": ISODate("2015-04-17T10:06:00.000Z"),
                "$lte": ISODate("2015-04-17T10:10:00.000Z")
            }
        }
    }
  }).exec(callback);