Question

I'm trying to find all of the documents in my db where the the size of my "states" list only contains one state but I'm struggling with the syntax of the java code.

My db looks like this:

{ "_id" : 13218 , "country" : { "MY" : 11 , "US" : 4} , "state" : { "WA" : 4 }}
{ "_id" : 95529 , "country" : { "US" : 6 } , "state" : { "MI" : 6 }}
{ "_id" : 22897 , "country" : { "US" : 4 } , "state" : { "CA" : 2 , "TX" : 1 , "WY" : 1 }}

What I want to do is print out every "_id" found from the US that only has a single state. So, the only "_id" that'd be returned here is 95529.

here is the relevant portion of code:

DBObject query = new BasicDBObject("country.US", new BasicDBObject("$gt", 4));
//query.put("state.2", new BasicDBObject("$exists", true)); 
//This is my attempt at checking the list length but it doesn't work
DBCursor dbCursor = dBcollection.find(query);
while (dbCursor.hasNext()){
    DBObject record = dbCursor.next();
    Object _id= record.get("_id");
    Object state= record.get("state");      
    System.out.println(_id + "," + state);
}

current output looks like this:

95529, { "MI" : 6 }
22897, { "CA" : 2 , "TX" : 1 , "WY" : 1 }
Était-ce utile?

La solution

The essential problem you have here is that your data is not in fact a "list". As a "hash" or "map" which is what it really is there is no concept of "length" in a MongoDB sense.

You would be better off changing your data to use actual "arrays" which is what a list actually is:

{
    "_id" : 13218 , 
    "country" : [
        { "code": "MY", "value" : 11 },
        { "code": "US", "value" : 4 },
    ],
    "state" : [{ "code": "WA", "value" : 4 }]
},
{ 
    "_id" : 95529 , 
    "country" : [{ "code": "US", "value" : 6 }], 
    "state" : [{ "code": "MI", "value" : 6 }]
},
{ 
    "_id" : 22897 ,
    "country" : [{ "code": "US", "value" : 4 }],
    "state" : [
        { "code": "CA", "value" : 2 }, 
        { "code": "TX", "value" : 1 }, 
        { "code": "WY", "value" : 1 }
    ]
}

Then getting those documents that only have a single state is a simple matter of using the $size operator.

DObject query = new BasicDBObject("country", 
    new BasicDBObject( "$elemMatch", new BasicDBObject(
        "code", "US").put( "value", new BasicDBObject( "$gt", 4 ) 
    )
);

query.put( "state": new BasicDBObject( "$size", 1 ) );

This ultimately gives you a lot more flexibilty in issuing queries as you don't need to specify the explicit "key" in each query. Also as noted, there is a concept of length here that does not otherwise exist.

If you keep your data in it's current form then the only way to do this is with the JavaScript evaluation of the $where operator. That is not very efficient as the interpreted code needs to be run for each document in order to determine if the condition matches:

DBObject query = new BasicDBObject("country.US", new BasicDBObject("$gt", 4));
query.put("state", new BasicDBObject( "$type", 3 ));
query.put("$where","return Object.keys( this.state ).length === 1");

Also using the $type operator in order to make sure that "state" is actually present and an "Object" that is expected. So possible, but not a really great idea do to performance.

Try to change your document structure as it will make other sorts of queries possible without using JavaScript evaluation.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top