Unwind array for documents that have a specified field - mongodb

I have an application that simply takes and converts XML files to JSON and inserts them into a Mongo database. Inside each document of the collection I have an array VehicleEntry. Some of these VehicleEntries will have a tag called Pre-FlashDTC-IPCand I would like to pull all of those entries. Since I want the individual entries from the array, I used the unwind operator:
db.Vehicles.aggregate( [
{ $unwind : { path : "$VehicleEntry" } },
{ $match : { "$VehicleEntry.Pre-FlashDTC-IPC" : { $exists: true } } }
] );
I have tried this both with unwind first and match first, but neither work. I get an error:
{ "serverUsed": "localhost:27017", "ok": 0.0, "errmsg": "bad query: BadValue unknown top level operator: $VehicleEntry.Pre-FlashDTC-IPC", "code": 16810}
I thought the $exists operator would work to ensure I only returned elements that do have that value, but that doesn't appear to be the case. How can I correct this query?
Consider the following sample document:
{
"VehicleEntry" : [
{
"BatteryStatus" : "GOOD",
"Pre-FlashDTC-IPC" : "U100",
"VehicleStatus" : "PASSED"
},
{
"BatteryStatus" : "GOOD",
"VehicleStatus" : "PASSED"
}
],
"project_id" : "1234"
}
There is some consistent information in the array, such as Battery and Vehicle status, but some have extra information like the first array item. I want to get the individual array items (hence the need for unwind) where this value exists. Therefore my expected results are:
{
"BatteryStatus" : "GOOD",
"Pre-FlashDTC-IPC" : "U100",
"VehicleStatus" : "PASSED"
},

Omit the "$" in your match-clause
{ $match : { "VehicleEntry.Pre-FlashDTC-IPC" : { $exists: true } } }
The $ in front of a field-name is only needed when you want to access the value of a field in an aggregation operation.

Related

Filter sub document array

db.doc.find({},{"sections.rows.Desk":1}) returns desks list but also empty rows i.e. where desk attribute doesn't exist in rows array...
I would like to eliminate empty results. How to go about it?
Thanks!
My document "doc" has the following format.
"docs":
{
"sections" : [
{
"name" : "Request Details",
"rows" : [
{
"Desk" : "IT4"
}
]
},
{
"name" : "Approval",
"rows" : [
{
"Approval" : ""
}
]
}
]
}
You will have to use the aggregation framework, particularly the $unwind operator to be able to query against individual array elements and get the ones you are after. Currently with the way .find works you will get the entire document if it matches your query.
db.docs.aggregate({$unwind: "$sections"}, {$match: {"sections.rows.Desk": {$exists: 1}}});

mongodb update and/or change an array key without using the value

I'm having trouble removing/renaming an array object from my mongodb.
{
"_id" : ObjectId("556a7e1b7f0a6a8f27e01b8a"),
"accountid" : "AC654164545",
"sites" :[
{ "site_id" : "example1.com" },
{ "002" : "example2.com" },
{ "003" : "example3.com" },
{ "004" : "example4.com" },
{ "005" : "example5.com" },
{ "006" : "example6.com" }
]}
}
Please take notice of the array key "site_id", I want to change it to "001" by either removing and appending it, which I know how to do, or rename it.
I've tried:
db.accounts.update({'id':ObjectId("556a7e1b7f0a6a8f27e01b8a")}, {$unset: {sites.site_id}})
But that says "unexpected token".
So I tried:
db.accounts.update({'id':ObjectId("556a7e1b7f0a6a8f27e01b8a")}, {$unset: {sites:site_id}})
That says "site_id is not defined"
Then I tried:
db.accounts.update({'id':ObjectId("556a7e1b7f0a6a8f27e01b8a")}, {$unset: {sites:'site_id'}})
That says WriteResult({ "nMatched" : 0, "nUpserted" : 0, "nModified" : 0 })
I also tried a $rename command:
db.accounts.update( { _id:ObjectId("556a7e1b7f0a6a8f27e01b8a") }, { $rename: { "sites.site_id": "sites.001" } } )
But that gave me a "Cannot use part (sites of sites.site_id) to traverse the element"
One option would be to use .find(), iterate through and delete it. Save the undeleted ones into an object, and run an .insert() command, but I want to stay away from that if I have too.
This site talks about dynamic renaming: http://docs.mongodb.org/manual/reference/operator/update/positional/
Aka first you make a matching query and then you use the $ to match that to the index in the array.
Here's the query that'll accomplish what you want for the test data you provided:
db.accounts.update({'accountid':"AC654164545", "sites.site_id": "example1.com"}, {$set: {"sites.$": {'001': 'example1.com'}}})
It is not recommended to use dynamic values such as numbers as a key in document structure. This will be more difficult to query using such values.
You can use $set and $elemMatch to get result as following:
db.collection.update({
'_id': ObjectId("556a7e1b7f0a6a8f27e01b8a"),
"sites": {
$elemMatch: {
"site_id": "example1.com"
}
}
}, {
$set: {
"sites.$":{"001": "example1.com"}
}
})

Select the value from an array of hashes based on key from a single document in mongoid

I have a document in items collection like
// Document One
{
"_id" : ObjectId("556411af73616d0d822f0000"),
"visibility" : [
{
"user_id" : ObjectId("556412bb73616d0d82310000"),
"visible" : false
},
{
"user_id" : ObjectId("556412c973616d0d82320000"),
"visible" : true
}
]
},
// Some other documents
{...}
{...}
I want to get the value of visible only for "Document One" based on user_id I provide (eg. ObjectId("556412bb73616d0d82310000")).
How?
I am using mongoid 4.0.0.
Thanks.
You can do this in two ways :
1> Using $elemMatch and $ in projection as below
db.collectionName.find({"visibility":{"$elemMatch":{"user_id":ObjectId("556412bb73616d0d82310000")}}},
{"visibility.$visible":1,"_id":0})
it retunrns results as
"visibility" : [ { "user_id" : ObjectId("556412bb73616d0d82310000"), "visible" : false } ]
this return whole matching array in visibility
2> Using aggregation as below :
db.collectionName.aggregate({
"$unwind": "$visibility"
}, {
"$match": {
"visibility.user_id": ObjectId("556412bb73616d0d82310000")
}
}, {
"$project": {
"_id": 0,
"visible": "$visibility.visible"
}
})
return results as { "visible" : false }
Maybe you can try this:
db.one.find(
{"visibility.user_id": ObjectId("556412bb73616d0d82310000")},
{_id: 0, "visibility.$": 1})
In the query statementsdb.collection.find(query, projection), {"visibility.user_id": ObjectId("")} is used to select the required item, and {_id: 0, "visibility.$": 1} is used to show the specified field.
What's more, $ operator (projection) is used to limit the output to be the matched one of array.
Official Doc: http://docs.mongodb.org/manual/reference/operator/projection/positional/#projection
$
The positional $ operator limits the contents of an from the query results to contain only the first element matching the query document. To specify an array element to update, see the positional $ operator for updates.
Use $ in the projection document of the find() method or the findOne() method when you only need one particular array element in selected documents.

In a Mongo collection, how do you query for a specific object in an array?

I'm trying to retrieve an object from an array in mongodb. Below is my document:
{
"_id" : ObjectId("53e9b43968425b29ecc87ffd"),
"firstname" : "john",
"lastname" : "smith",
"trips" : [
{
"submitted" : 1407824585356,
"tripCategory" : "staff",
"tripID" : "1"
},
{
"tripID" : "2",
"tripCategory" : "volunteer"
},
{
"tripID" : "3",
"tripCategory" : "individual"
}
]
}
My ultimate goal is to update only when trips.submitted is absent so I thought I could query and determine what the mongo find behavior would look like
if I used the $and query operator. So I try this:
db.users.find({
$and: [
{ "trips.tripID": "1" },
{ "trips": { $elemMatch: { submitted: { $exists: true } } } }
]
},
{ "trips.$" : 1 } //projection limits to the FIRST matching element
)
and I get this back:
{
"_id" : ObjectId("53e9b43968425b29ecc87ffd"),
"trips" : [
{
"submitted" : 1407824585356,
"tripCategory" : "staff",
"tripID" : "1"
}
]
}
Great. This is what I want. However, when I run this query:
db.users.find({
$and: [
{ "trips.tripID": "2" },
{ "trips": { $elemMatch: { submitted: { $exists: true } } } }
]
},
{ "trips.$" : 1 } //projection limits to the FIRST matching element
)
I get the same result as the first! So I know there's something odd about my query that isn't correct. But I dont know what. The only thing I've changed between the queries is "trips.tripID" : "2", which in my head, should have prompted mongo to return no results. What is wrong with my query?
If you know the array is in a specific order you can refer to a specific index in the array like this:-
db.trips.find({"trips.0.submitted" : {$exists:true}})
Or you could simply element match on both values:
db.trips.find({"trips" : {$elemMatch : {"tripID" : "1",
"submitted" : {$exists:true}
}}})
Your query, by contrast, is looking for a document where both are true, not an element within the trips field that holds for both.
The output for your query is correct. Your query asks mongo to return a document which has the given tripId and the field submitted within its trips array. The document you have provided in your question satisfies both conditions for both tripIds. You are getting the first element in the array trips because of your projection.
I have assumed you will be filtering records by the person's name and then retrieving the elements inside trips based on the field-exists criteria. The output you are expecting can be obtained using the following:
db.users.aggregate(
[
{$match:
{
"firstname" : "john",
"lastname" : "smith"
}
},
{$unwind: "$trips"},
{$match:
{
"trips.tripID": "1" ,
"trips.submitted": { $exists: true }
}
}
]
)
The aggregation pipeline works as follows. The first $match operator filters one document (in this case the document for john smith) The $unwind operator in mongodb aggregation unwinds the specified array (trips in this case), in effect denormalizing the sub-records associated with the parent records. The second $match operator filters the denormalized/unwound documents further to obtain the one required as per your query.

mongodb upsert in updating an array element

Want to upsert in object properties in a array of a document
Consider a document in collection m
{ "_id" : ObjectId("524bfc39e6bed5cc5a9f3a33"),
"x" : [
{ "id":0.0, "name":"aaa"},{ "id":1.0, "name":"bbb"}
]
}
Want to add age:100 to { "id":0.0, "name":"aaa"} .
Not just age .. But but provision for upsert in the array element {}. So it can contain {age:100,"city":"amd"} (since i am getting this from the application service)
Was trying this... But did not worked as it replaced the entire array element
db.m.update({_id:ObjectId("524bfc39e6bed5cc5a9f3a33"),
"x" : {
"$elemMatch" : {
"id" : 0.0
}
}},
{
$set : {
"x.$" : {
"age": 100
}
}
},
{upsert: true}
)
Changed the document to (which i did not wanted)
{ "_id" : ObjectId("524bfc39e6bed5cc5a9f3a33"),
"x" : [
{ "age":100},{ "id":1.0, "name":"bbb"}
]
}
Is this possible without changing schema.
$set : {"x.$" : {"age": 100}}
x.$ sets the entire matched array element to {age: 100}
This should work:
db.m.update({_id:ObjectId("524bfc39e6bed5cc5a9f3a33"),
"x.id": 0.0}, {$set: {"x.$.age": 100 }});
Using elemMatch:
db.test.update({x: {$elemMatch: {id: 1}}},{$set: {"x.$.age": 44}})
Note that the upsert option here, is redundant and wouldn't work if the id isn't present in x because the positional operator $ doesn't support upserting.
This is not possible without changing schema. If you can change schema to use an object to store your items (rather than an array), you can follow the approach I outlined in this answer.