0

I am trying to retrieve elements in an array in mongo db. I would like to retrieve the 15 first elements which do not match a pattern

So let's imagine I have

{
"_id" : ObjectId("s4dcsd5s4d6c54s6d"),
"items" : [
    {
        type : "TYPE_1",
        text : "blablabla"
    },
    {
        type : "TYPE_2",
        text : "blablabla"
    },
    {
        type : "TYPE_3",
        text : "blablabla"
    },
    {
        type : "TYPE_1",
        text : "blablabla"
    },
    {
        type : "TYPE_2",
        text : "blablabla"
    },
    {
        type : "TYPE_1",
        text : "blablabla"
    }
]
}

So currently I have more element to match compared to the element to not match that's why I use nin. but it is to simplifiy

If I use

db.history.find({ "_id" : ObjectId("s4dcsd5s4d6c54s6d")}, { "items" : { "$elemMatch" : { "type" : { "$nin" : [ "TYPE_2" , "TYPE_3"]}}}, "items" : { $slice : [0, 2]}}).pretty()

It seems that the element match is not taken into account (inverse as well if i put element match after slice)

Then if I do:

db.history.find({ "_id" : ObjectId("s4dcsd5s4d6c54s6d")}, { "items" : { "$elemMatch" : { "type" : { "$nin" : [ "TYPE_2" , "TYPE_3"]}}, $slice : [0, 2]}}).pretty()

An error is thrown by mongo

Do you know how I can do?

Thanks a lot

Geoffrey
  • 1,151
  • 3
  • 13
  • 26

2 Answers2

2

You can't use $elemMatch for your case since it will only return the first element. From documentation :

$elemMatch The $elemMatch operator limits the contents of an field from the query results to contain only the first element matching the $elemMatch condition.

You can do an aggregation query which will do the following:

  • match your _id
  • unwind your items array to have one record per items in the array
  • match the types $nin your array [ "TYPE_2" , "TYPE_3"]
  • limit the number of result

The query is :

db.history.aggregate([{
        $match: {
            _id: ObjectId("s4dcsd5s4d6c54s6d")
        }
    }, {
        $unwind: '$items'
    }, {
        $match: {
            'items.type': { '$nin': ["TYPE_2", "TYPE_3"] }
        }
    },
    { $limit: 2 }
])

It gives :

{ "_id" : "s4dcsd5s4d6c54s6d", "items" : { "type" : "TYPE_1", "text" : "blablabla" } }
{ "_id" : "s4dcsd5s4d6c54s6d", "items" : { "type" : "TYPE_1", "text" : "blablabla" } }
Bertrand Martel
  • 42,756
  • 16
  • 135
  • 159
  • Thanks you betrand. – Geoffrey Dec 28 '16 at 09:49
  • Quick question do you use springdata mongodb? :) In fact I am trying to use what you said with it ` Aggregation aggregation = newAggregation(match(Criteria.where('id').is(profileID)), unwind('items'), match(Criteria.where('items.type').nin(ignoreditemstype)), limit(3), skip(1)); ` And I am getting: `{ "aggregate" : "__collection__" , "pipeline" : [ { "$match" : { "id" : "5856dcf347816870aab76137"}} , { "$unwind" : "$items"} , { "$match" : { "items.type" : { "$nin" : [ "TYPE_2" , "TYPE_3" , "TYPE_4" , "TYPE_5" , "TYPE_6"]}}} , { "$limit" : 3} , { "$skip" : 0}]}` Do you have an idea? – Geoffrey Dec 28 '16 at 10:29
  • If you use objectId type for _id and not String this might be the problem http://stackoverflow.com/questions/14346028/spring-data-mongodb-query-converts-string-to-objectid-automatically – Bertrand Martel Dec 28 '16 at 10:43
  • From what I can see in this discussion this is not related to my problem. Because the operation they are trying to do is to compare a string (reference to a user id for example) to an objectID in the effetcive user. My profileID field is an ID in object: `@Id private String id;` So I do not understand :/ – Geoffrey Dec 28 '16 at 11:09
  • You could check step by step the results to see whats wrong, first the match only, then match + unwind etc.. – Bertrand Martel Dec 28 '16 at 11:35
  • I will do and i will keep you posted. Thanks again for your help – Geoffrey Dec 28 '16 at 11:38
2

You will need to use aggregation for restricting the array in the form you have. Use $filter to apply the condition and $slice to limit the array elements.

db.history.aggregate([{
    $match: {
        _id: ObjectId("586309d6772c68234445f2a5")
    }
}, {
    "$project": {
        "items": {
            "$slice": [{
                    "$filter": {
                        "input": "$items",
                        "as": "item",
                        "cond": {
                            "$and": [{
                                $ne: ["$$item.type", "TYPE_2"]
                            }, {
                                $ne: ["$$item.type", "TYPE_3"]
                            }]
                        }
                    }
                },
                2
            ]
        }
    }
}])

Sample Output:

{ "_id" : ObjectId("586309d6772c68234445f2a5"), "items" : [ { "type" : "TYPE_1", "text" : "blablabla" }, { "type" : "TYPE_1", "text" : "blablabla" } ] }
s7vr
  • 73,656
  • 11
  • 106
  • 127