Compare Properties from Array to Single Property in Document - mongodb

I have a mongoDB orders collection, the documents of which look as follows:
[{
"_id" : ObjectId("59537df80ab10c0001ba8767"),
"shipments" : {
"products" : [
{
"orderDetails" : {
"id" : ObjectId("59537df80ab10c0001ba8767")
}
},
{
"orderDetails" : {
"id" : ObjectId("59537df80ab10c0001ba8767")
}
}
]
},
}
{
"_id" : ObjectId("5953831367ae0c0001bc87e1"),
"shipments" : {
"products" : [
{
"orderDetails" : {
"id" : ObjectId("5953831367ae0c0001bc87e1")
}
}
]
},
}]
Now, from this collection, I want to filter out the elements in which, any of the values at shipments.products.orderDetails.id path is same as value at _id path.
I tried:
db.orders.aggregate([{
"$addFields": {
"same": {
"$eq": ["$shipments.products.orderDetails.id", "$_id"]
}
}
}])
to add a field same as a flag to decide whether the values are equal, but the value of same comes as false for all documents.
EDIT
What I want to do is compare the _id field the the documents with all shipments.products.orderDetails.id values in the array.
If even 1 of the shipments.products.orderDetails.ids match the value of the _id field, I want that document to be present in the final result.
PS I am using MongoDB 3.4, and have to use the aggregation pipeline.

Your current attempt fails because the notation returns an "array" in comparison with a "single value".
So instead either use $in where available, which can compare to see if one value is "in" an array:
db.orders.aggregate([
{ "$addFields": {
"same": {
"$in": [ "$_id", "$shipments.products.orderDetails.id" ]
}
}}
])
Or notate both as arrays using $setIsSubset
db.orders.aggregate([
{ "$addFields": {
"same": {
"$setIsSubset": [ "$shipments.products.orderDetails.id", ["$_id"] ]
}
}}
])
Where in that case it's doing a comparison to see if the "sets" have an "intersection" that makes _id the "subset" of the array of values.
Either case will return true when "any" of the id properties within the array entries at the specified path are a match for the _id property of the document.

Related

MongoDB - find entries where field with specific name is present, nested anywhere

I have a mongoDB database, where some entries have a field of a name "rx" + value of another field in this entry (let's call it rxid), so the final name of this field could look like "rx1234" or "rx2836". Inside this field there is a list of dictionaries which MAY contain fields with names "A" and "B". My task is to find how many entries in this database have at least one non-empty field named A or B, no matter where it is nested. I tried to search for nested queries, however it always requires to specify the "parent" field, which in my case is a combination of rx and a value of other field, so it is not a constant name.
My database schema look something like:
{
"_id" : 1,
"rxid" 1234,
"rx1234" : [
{
"A" : "somevalue",
"B" : "someothervalue",
},
{
"A" : "somevalue2",
}
]
},
{
"_id" : 2,
"rxid" 2345
}
In this case I expect to count how many objects are structured like _id = 1 and how many like _id = 2 (there may be other fields inside but they are irrelevant)
Refer this
It checks for rxCustomNumber field and then checks for A/B inside that field. You can simply add a count pipeline to get the counts.
db.collection.aggregate([
{
"$project": {
"concatValue": {//Form the dynamic field dynamically
"$concat": [
"rx",
{
"$toString": "$rxid"
}
]
},
"data": {
"$objectToArray": "$$ROOT"
}
}
},
{//Restructure the field
$unwind: "$data"
},
{
$match: {
$and: [//Check for the match
{
$expr: {
"$eq": [
"$concatValue",
"$data.k"
]
}
}
]
}
},
{//Print the existence
"$project": {
"data.v.A": 1,
"data.v.B": 1
}
}
])

How do I match an array of sub-documents in MongoDB?

Match documents if a value in an array of sub-documents is greater than some value only if the same document contains a field that is equal to some value
I have a collection that contains documents with an array of sub-documents. This array of sub-documents contains a field that dictates whether or not I can filter the documents in the collection based on another field in the sub-document. This'll make more sense when you see an example of the document.
{
"_id":"ObjectId('XXX')",
"Data":{
"A":"",
"B":"-25.78562 ; 28.35629",
"C":"165"
},
"SubDocuments":[
{
"_id":"ObjectId('XXX')",
"Data":{
"Value":"XXX",
"DataFieldId":"B"
}
},
{
"_id":"ObjectId('XXX')",
"Data":{
"Value":"",
"DataFieldId":"A"
}
},
{
"_id":"ObjectId('XXX')",
"Data":{
"Value":"105",
"DataFieldId":"Z"
}
}
]
}
I only want to match documents that contain sub-documents with a DataFieldId that is equal to Z but also filter for Values that are greater than 105 only if Data Field Id is equal to Z.
Try as below:
db.collection.aggregate([
{
$project: {
_id:1,
Data:1,
filteredSubDocuments: {
$filter: {
input: "$SubDocuments",
as: "subDoc",
cond: {
$and: [
{ $eq: ["$$subDoc.Data.DataFieldId", "Z"] },
{ $gte: ["$$subDoc.Data.Value", 105] }
]
}
}
}
}
}
])
Resulted response will be:
{
"_id" : ObjectId("5cb09659952e3a179190d998"),
"Data" : {
"A" : "",
"B" : "-25.78562 ; 28.35629",
"C" : "165"
},
"filteredSubDocuments" : [
{
"_id" : "ObjectId('XXX')",
"Data" : {
"Value" : 105,
"DataFieldId" : "Z"
}
}
]
}
This can be done by using the $elemMatch operator on sub-documents, for details you can click on provided link. For your problem you can try below query by using $elemMatch which is match simpler than aggregation:
db.collectionName.find({
"SubDocuments": {
$elemMatch: {
"Data.DataFieldId": "Z" ,
"Data.Value" : {$gte: 105}
}
} })
Its working fine, I have verified it locally, one modification you required is that you have to put the value of SubDocuments.Data.Value as Number or Long as per your requirements.

Mongodb upsert field in document

I have a document in the following form:
{
"_id" : <id of document>,
"inventory" : [
{
"entity_id" : "<id>",
"ad_type" : "ADTYPE"
}
]
}
I'm not sure if this is possible in mongo but I am trying to construct an upsert query on the above document where I will insert an empty array if inventory doesn't exist or push to the array if inventory has one or more elements.
I tried using the findAndModify function but I don't think that will work.
db.collection.find({"_id":"value"}).forEach(
function(doc){
if(doc.hasOwnProperty('inventory'))
{
db.collection.update({"_id":doc._id},
{"$push":{"inventory": "new value"}});
}
else
{
db.collection.update({"_id":doc._id},
{"$set":{"inventory": new Array() }});
}
});
"collection" is array, "subcollection" is object.
The script adds new field "rank" to array element with index 1.
If the field doesn't exist the script creates "rank" and set value, If the field exists the script update value.
db.userCollection.update(
{ "_id": ObjectId("5a1e7aefd1928362e53c5c48"),
$and: [ { "collection.1.subcollection.id": "1234" },
{ "collection.1.rank": {"$ne": -1 }}
]
},
{ $set: { "collection.1.rank": "5" }
}
)

Query to retrieve multiple items in an array

If I have the following payload:
{
"objs": [
{ "_id": "1234566", "some":"data", "key": "one" },
{ "_id": "1234576", "some":"data", "key": "one" },
{ "_id": "2345666", "some":"otherdata", "key": "two" },
{ "_id": "4566666", "some":"yetotherdata", "key": "three" },
]
}
How can I return all objects (objs) with the following:
key: "one"
_id: [1234566, 1234576]
Thanks
The find() query returns all the objs that have the sub documents that match both these conditions.
var input = ["1234566","1234576"];
db.collection.find({$and:[{"objs._id":{$in:input}},{"objs.key":"one"}]})
If you want to get the redacted documents inside the objs array, You can achieve this using the aggregate pipeline operations.
Define a variable to hold the input values.
$unwind by objs elements, this gives you seperate documents for each
element in the objs array.
$match only those documents that match the selection criteria.
$group by "_id" of the document which is autogenerated by mongo.
$project the required fields.
The Code:
var input = ["1234566","1234576"];
db.collection.aggregate([
{$unwind:"$objs"},
{$match:{"objs._id":{$in:input},"objs.key":"one"}},
{$group:{"_id":"_id","objs":{$push:"$objs"}}},
{$project:{"_id":0,"objs":1}}
])
o/p:
{ "objs" :
[ { "_id" : "1234566", "some" : "data", "key" : "one" },
{ "_id" : "1234576", "some" : "data", "key" : "one" } ] }
You can't. MongoDB returns the document that matches the query conditions, not individual pieces that match the query conditions. You can suppress or include fields of the matching documents with projection, but you cannot (as of 2.6) return an array restricted just to contain elements that matched conditions on the array. You can return just the first such match with $
db.collection.find(
{ "objs" : { "$elemMatch" : { "_id" : { "$in" : [1234566, 1234576] }, "key" : "one" } } }, // query
{ "_id" : 0, "objs.$" : 1 } // projection
)
If you want to return all matching objs elements, most likely you should make each subdocument in the objs array into its own document in the collection.

how to match the last value of array in mongo db? [duplicate]

I have a sample document like shown below
{
"_id" : "docID",
"ARRAY" : [
{
"k" : "value",
"T" : "20:15:35",
"I" : "Hai"
},
{
"K" : "some value",
"T" : "20:16:35",
"I" : "Hello"
},
{
"K" : "some other value",
"T" : "20:15:35",
"I" : "Update"
}
]
}
I am trying to update the last element in the "ARRAY" based on field "ARRAY.T"(which is only field i know at the point of update), but what my problem is first element in the array matches the query and its ARRAY.I field is updated.
Query used to update:
db.collection.update( { _id: "docID","ARRAY.T" : "20:15:35"},
{ $set: { "ARRAY.$.I": "Updated value" }
})
Actually i don't know index of the array where to update so i have to use ARRAY.I in the query, is there any way to to tell Mongodb to update the first element matched the query from last of the array.
I understand what you are saying in that you want to match the last element in this case or in fact process the match in reverse order. There is no way to modify this and the index stored in the positional $ operator will always be the "first" match.
But you can change your approach to this, as the default behavior of $push is to "append" to the end of the array. But MongoDB 2.6 introduced a $position modifier so you can in fact always "pre-pend" to the array meaning your "oldest" item is at the end.
Take this for example:
db.artest.update(
{ "array": { "$in": [5] } },
{ "$push": { "array": { "$each": [5], "$position": 0 } }},
{ "upsert": true }
)
db.artest.update(
{ "array": { "$in": [5] } },
{ "$push": { "array": { "$each": [6], "$position": 0 } }},
{ "upsert": true }
)
This results in a document that is the "reverse" of the normal $push behavior:
{ "_id" : ObjectId("53eaf4517d0dc314962c93f4"), "array" : [ 6, 5 ] }
Alternately you could apply the $sort modifier when updating your documents in order to "order" the elements so they were reversed. But that may not be the best option if duplicate values are stored.
So look into storing your arrays in "reverse" if you intend to match the "newest" items "first". Currently that is your only way of getting your "match from last" behavior.