Reputation: 1704
I have a Mongo DB with the following object:
[
{
"link" : "xxxxx.jpg"
"_id" : ObjectId("5501b1648ef0b4eccc41814e"),
"processed" : {
"320" : true,
"480" : true,
"540" : true,
"720" : true,
"800" : true,
"1080" : true,
"original" : false,
"iPhone" : true
}
}
]
I am trying to query where any of the processed values is false, but I cannot seem to figure out how to query where I do not know which key matches. Is this possible without looping through all documents?
Upvotes: 4
Views: 7999
Reputation: 103475
With MongoDB 3.4.4, use the aggregation framework to query the document. This is made possible with the $objectToArray
operator which allows you to map the keys in the processed field to an array of key/value pairs. The list will be easy to filter and get the key(s) that matches whatever condition you have.
In the following example, the aggregation pipeline consists of an extra field which holds the key(s) that match the above condition of having a false value, so ideally it will be an array:
db.collection.aggregate([
{ "$addFields": {
"notProcessed": {
"$map" : {
"input": {
"$filter": {
"input": { "$objectToArray": "$processed" },
"as": "el",
"cond": { "$not": "$$el.v" }
}
},
"in": "$$this.k"
}
}
} }
])
which yields
{
"_id" : ObjectId("5501b1648ef0b4eccc41814e"),
"link" : "xxxxx.jpg",
"processed" : {
"320" : true,
"480" : true,
"540" : true,
"720" : true,
"800" : true,
"1080" : true,
"original" : false,
"iPhone" : true
},
"notProcessed" : [
"original"
]
}
Starting with the nested expression
{
"$filter": {
"input": { "$objectToArray": "$processed" },
"as": "el",
"cond": { "$not": "$$el.v" }
}
}
the input to $filter
operator { "$objectToArray": "$processed" }
will convert the keys in the processed
key to this array:
[
{
"k" : "320",
"v" : true
},
{
"k" : "480",
"v" : true
},
{
"k" : "540",
"v" : true
},
{
"k" : "720",
"v" : true
},
{
"k" : "800",
"v" : true
},
{
"k" : "1080",
"v" : true
},
{
"k" : "original",
"v" : false
},
{
"k" : "iPhone",
"v" : true
}
]
and $filter
will filter the above array to have only the object elements whose v
property is NOT
true
:
[
{
"k" : "original",
"v" : false
}
]
$map
will then return a mapped array with just the values
[ { "k" : "original", "v" : false } ] => [ "original" ]
so you end up with just
[ "original" ]
as a result.
With older MongoDB versions, it's going to be pretty difficult to issue queries against dynamic keys. Consider modifying your schema to follow this document model which is easier to query:
// this operation changes the schema
var processed = [];
db.collection.find().forEach( function(doc) {
for(key in doc.processed) {
if(doc.processed.hasOwnProperty(key)) {
var item = { key: key, value: doc.processed[key] }
processed.push(item);
}
}
doc.processed = processed;
db.collection.save(doc);
});
// modified schema
{
"link": "xxxxx.jpg"
"_id": ObjectId("5501b1648ef0b4eccc41814e"),
"processed": [
{ "key": "320", "value": true },
{ "key": "480", "value": true },
{ "key": "540", "value": true },
{ "key": "720", "value": true },
{ "key": "800", "value": true },
{ "key": "1080", "value": true },
{ "key": "original", "value": false },
{ "key": "iPhone", "value": true }
]
}
Your find query will be simply
db.collection.find({"processed.value": false});
or use $map
and $filter
to return the keys with false
values as
db.collection.aggregate([
{ "$project": {
"link": 1,
"notProcessed": {
"$map" : {
"input": {
"$filter": {
"input": "$processed",
"as": "el",
"cond": { "$not": "$$el.v" }
}
},
"in": "$$this.k"
}
}
} }
])
Upvotes: 3
Reputation: 11375
With a document schema like you've put in the original post, we can use Javascript to;
processed
objectfalse
, add the _id
to an array_id
's that have a false
value in processed
objectQuery to run
var arrDoc = [];
db.test.find().forEach(
function anyFalse(doc) {
for(key in doc.processed) {
if(doc.processed.hasOwnProperty(key) && doc.processed[key] === false) {
arrDoc.push(doc._id);
break;
}
}
});
print( arrDoc.join("\r\n") );
Example document
{
"_id" : ObjectId("5107de525ed6103609000016"),
"link" : "xxxxx.jpg",
"processed" : {
"320" : true,
"480" : true,
"540" : true,
"720" : true,
"800" : true,
"1080" : true,
"original" : false,
"iPhone" : true
}
}
Example output
ObjectId("5107de525ed6103609000016")
Further notes
You can store this javascript function anyFalse
into Mongo and call it when you need it. See store javascript function on server
As noted in the comments, you have an images
array. This function will loop through all the images
array to check child processed
for false.
Query to run
var arrDoc = [];
db.test.find().forEach(
function anyFalse(doc) {
var len = doc.images.length;
for( var i = 0; i < len; i++ ) {
for(key in doc.images[i].processed) {
if(doc.images[i].processed.hasOwnProperty(key) && doc.images[i].processed[key] === false) {
arrDoc.push(doc.images[i]._id);
break;
}
}
}
});
print( arrDoc.join("\r\n") );
Example document
{
"_id" : ObjectId("5534fe2f3614af9afd23310a"),
"images" : [
{
"_id" : ObjectId("5107de525ed6103609000016"),
"link" : "xxxxx.jpg",
"processed" : {
"320" : true,
"480" : true,
"540" : true,
"720" : true,
"800" : true,
"1080" : true,
"original" : true,
"iPhone" : true
}
},
{
"_id" : ObjectId("5107de525ed6103609000017"),
"link" : "xxxxx.jpg",
"processed" : {
"320" : true,
"480" : true,
"540" : true,
"720" : true,
"800" : true,
"1080" : true,
"original" : false,
"iPhone" : true
}
}
]
}
Upvotes: 0