MongoDB Casbah query field not exist or specific value - scala

I would like to perform a query using casbah in order to find all objects that have a certain field not set (the field does not exist) or the field has a particular value.
I have tried using
val query = ("_id.serviceName" $in serviceNames) ++ ($or("element" $exists false), MongoDBObject("element" -> "value")))
but I obtain an error:
found com.mongodb.casbah.commons.Imports.DBObject
required (String, Any)
Is it possible to express such query?
Thanks

Looks like this may be a bug in the right-hand value filter for $or; it doesn't appear to be accepting a preconstructed DBObject from the $exists DSL statement. It definitely should --- I'm filing a bug internally to fix this; in the meantime you can construct this by doing the "$exists" statement by hand:
scala> val query = ("_id.serviceName" $in serviceNames) ++ ($or(("element" -> MongoDBObject("$exists" -> false)), ("element" -> "value")))
query: com.mongodb.casbah.commons.Imports.DBObject = { "$or" : [ { "element" : { "$exists" : false}} , { "element" : "value"}] , "_id.serviceName" : { "$in" : [ "foo" , "bar" , "baz" , "bah"]}}
Sorry for the trouble... I've created a bug entry for this to correct for the next release.

Related

Could there be a (unique) workaround for duplicate "$or" keys when working with boolean logic in MongoDB?

When I asked a question earlier about querying in MongoDB resolved the respective issue, but another question stemmed from the original idea.
Under similar conditions, suppose that I'm trying to query:
Example: {
"AttributeA": type,
"AttributeB": type,
"AttributeC": type,
"AttributeD": type
etc...
}
But I want to find all elements given the conditions where:
(Attribute A matches criteria1 or Attribute B matches criteria2)
and
(Attribute C matches criteria3 or Attribute D matches criteria4 or Attribute E matches criteria5)
The $in operator only tracks an $or conditional given that the attributes are the same (eg. referring to previous question of AttributeC matching criteria 3, 4, or 5). So the general layout in this new query would be more like:
db.Example.find({
$or:[ {AttrA : "criteria1"}, {AttrB : "criteria2"}],
$or:[ {AttrC : "criteria3"}, {AttrD : "criteria4"}, {AttrE : "criteria5"} ]
})
But under the conditions above it seems impossible without a duplicate "$or" operator unless I do some boolean algebra and separate it into:
((A+B)*(C+D+E) = AC + AD + AE + BC + BD + BE) aka
AttrA matches ... and AttrC matches ...
or
AttrA matches ... and AttrD matches ...
or
...
AttrB matches ... and AttrE matches ...
meaning that the format would look like
db.Example.find({
$or:[
$and:[{AttrA : "criteria1"}, {AttrC : "criteria3"}],
$and:[{AttrA : "criteria1"}, {AttrD : "criteria4"}],
...,
$and:[{AttrB : "criteria2"}, {AttrE : "criteria5"}
]
})
Though I'm not even sure if the mongoDB system allows for duplicate "$and"s either.
Could there be an easier way or am I overcomplicating the conditional queries?
There is no need to manually distribute the conditions here. You must use the $and explicitly rather than implying on the implicit one:
db.Example.find({
$and: [
{ $or:[ {AttrA : "criteria1"}, {AttrB : "criteria2"}] },
{ $or:[ {AttrC : "criteria3"}, {AttrD : "criteria4"}, {AttrE : "criteria5"} ] }
]
})
Playground example here. This is covered in the documentation here.
The general problem that is encountered, at least with Javascript that includes the shell, is that duplicate field names are not supported. Consider this example using Node directly:
$ node
Welcome to Node.js v16.17.0.
Type ".help" for more information.
> let x = { x: 123, y: 456, x: 789 }
undefined
> x
{ x: 789, y: 456 }
>

How to search and update values in a list of dictionaries using pymongo for a mongo entry?

I have mongo entry like this
{
"_id" : ObjectId("5fdc78778adbdedd17ce6ff3"),
"solution_id" : "5fd6e275a675f2000134b243",
"versions" : [
{
"local" : 2,
"s3" : null
}
]
}
I need to search and update the value of "s3" key to "abc". How to do that using pymongo?
You need a "route" to the specific key to be able to set it. You can use the dot notation to determine the element in the object structure that needs to be updated; in your case this is versions.0.s3. If you're happy to do this in python, and tweaking your code, it would look like:
for i in cursor1:
for index, j in enumerate(i.get('versions', [])):
if j.get('local') == 2:
db.mycollection.update_one({'_id': i.get('_id')}, {'$set': {f'versions.{index}.s3': 'abc'}})
You can do the whole thing in a single update operation using the $ positional operator. NB This only updates the first match on local == 2 found; the python example would update all matching items in the array.
db.mycollection.update_one({'_id': ObjectId(document_id), 'versions.local': 2}, {'$set': {'versions.$.s3': 'abc'}})
Option 1
item_id = ObjectId("5fdc78778adbdedd17ce6ff3")
db.collection.update({'_id': item_id}, {'$set': {'versions.$.s3': 'abc'}})
Option 2
query = {}
item_id = ObjectId("5fdc78778adbdedd17ce6ff3")
items = db.collection.find({'_id': item_id})
for i, _ in enumerate(items):
query[f'versions.{i}.s3'] = 'abc'
db.collection.update({'_id': item_id}, {'$set': query})

Mongo db : query on nested json

Sample json object :
{ "_id" : ObjectId( "55887982498e2bef5a5f96db" ),
"a" : "x",
"q" : "null",
"p" : "",
"s" : "{\"f\":{\"b\":[\"I\"]},\"time\":\"fs\"}" }
need all documents where time = fs
My query :
{"s":{"time" : "fs"}}
above returns zero products but that is not true.
There are two problems here. First of all s is clearly a string so your query cannot work. You can use $regex as below but it won't be very efficient:
{s: {$regex: '"time"\:"fs"'}}
I would suggest converting s fields to proper documents. You can use JSON.parse to do it. Documents can be updated based on a current value using db.foo.find().snapshot().forEach. See this answer for details.
Second problem is your query is simply wrong. To match time field you should use dot notation:
{"s.time" : "fs"})

Using MongoDBObject Query Builder with Salat

I am attempting to provide an API to search a MongoDB collection on various criteria, including a full-text search. Since this is a Scala project (in Play FWIW), I am using Salat, an abstraction around Casbah.
The following code works fine:
MySalatDao
.find(MongoDBObject("$text" -> MongoDBObject("$search" -> "Vidya")), MongoDBObject("score" -> MongoDBObject("$meta" -> "textScore")))
.sort(orderBy = MongoDBObject("score" -> MongoDBObject("$meta" -> "textScore")))
However, I will eventually need to search on multiple criteria and sort the results by their full-text search score, so I explored Casbah's MongoDBObject query builder functionality (at bottom).
So I tried to replicate the above like this:
val builder = MongoDBObject.newBuilder
builder += "$text" -> MongoDBObject("$search" -> "Vidya")
builder += "score" -> MongoDBObject("$meta" -> "textScore")
MySalatDao
.find(a.result())
.sort(orderBy = MongoDBObject("score" -> MongoDBObject("$meta" -> "textScore")))
This gives the following exception:
com.mongodb.MongoException: Can't canonicalize query: BadValue must have $meta projection for all $meta sort keys
at com.mongodb.QueryResultIterator.throwOnQueryFailure(QueryResultIterator.java:214)
at com.mongodb.QueryResultIterator.init(QueryResultIterator.java:198)
at com.mongodb.QueryResultIterator.initFromQueryResponse(QueryResultIterator.java:176)
at com.mongodb.QueryResultIterator.<init>(QueryResultIterator.java:64)
at com.mongodb.DBCollectionImpl.find(DBCollectionImpl.java:86)
at com.mongodb.DBCollectionImpl.find(DBCollectionImpl.java:66)
.
.
I've seen this error before--when I didn't include the score component in the query. But once I did that, it worked (as seen in the first code snippet), and I thought the version with the query builder is equivalent.
For that matter, a call to builder.result().toString() produces this:
{ "$text" : { "$search" : "Vidya"} , "score" : { "$meta" : "textScore"}}
Any help with getting the query builder to work for me would be much appreciated.
In your working query you are passing one DBObject for the "query predicate" and a second DBObject for "fields" or "projection" - find takes a second optional argument indicating which fields to return and in case of $text search, there is a special projection field $meta which allows you to get back the score of the matched document so that you can sort on it.
In your builder attempt you are adding the projection DBObject to the query criteria, and that gives you the same error you saw before when omitting the score component as the second argument to find.
Add MongoDBObject("score" -> MongoDBObject("$meta" -> "textScore")) as the second argument to find like you were doing before, and use builder for combining multiple query criteria.
In simple JSON terms, you are calling find like this:
db.coll.find( { "$text" : { "$search" : "Vidya"} , "score" : { "$meta" : "textScore"}} )
when you really want to call it like this:
db.coll.find( { "$text" : { "$search" : "Vidya"} } , { "score" : { "$meta" : "textScore"}} )

MongoDB dot notation query

I have a BasicDBObject which while debugging seems to hold:
{ "_id" : { "p_key" : { "$date" : "2012-02-02T00:00:00Z"} , "d_key" : 222} , "t_key" : 10195 , "w_key" : 4 , "f_key" : { "$date" : "2012-02-02T00:00:00Z"}}
Now, when I try:
dbObject.get("_id.d_key"));
I get a null pointer exception.
Any thoughts on what I am doing wrong here?
I am trying to get the value of d_key inside _id.
DBObject and it's children do not support dot notation gets. You will have to do :
if(dbObject.containsField("_id"))
((DBOBject)dbObject.get("_id")).get("p_key")
That's an extremely large _id field by the way. It will result in huge indexes which is generally not a good idea.