Using $in but matching on specific fields - mongodb

I'm attempting to use $in to query a mongoDB collection. The issue I'm running up against is that the arrays are arrays of JSON objects, not just arrays of a single field. So the full object looks like this:
{
items: [
{
name: "Jacket",
other_field: "1234"
}
]
}
So if I have an array that looks like this:
[{name:"Jacket", other_field:"3456"}]
I'd like to query for documents that contain within their items array any object which has a matching name field, ignoring all other fields. Is that possible?

You can use $elemMatch for this.
Example -
db.users.find({items: { $elemMatch: {name:'Jacket'} } } )
For more understanding, you can refer to - $elemMatch

As per your question there should be two possibilities if your documents structure looks like below
{
items: [
{
name: "Jacket",
other_field: "1234"
}
]
}
In this case you use following query
db.collectionName.find({"items.name":"Jacket"},{"items":1})
so above query will return only matching items.name matching criteria.
And another more possibilities if your documents structure looks like nested as below
{
items: [
{
name: "Jacket",
other_field: "1234"
},
{
name: "Jacket",
other_field: "12345"
},
{
name: "Jacket2",
other_field: "1234e"
}
]
}
In this case should use aggregate function as below
db.collectionName.aggregate({
"$unwind": "$items"
},
{
"$match": {
"items.name": "Jacket"
}
},
{
"$project": {
"_id": 0,
"items.name": 1,
"items.other_field": 1
}
})

if I understood your requirement correctly then could could ask
db.collectionName.find({'items.name': 'Jacket'})
MongoDB automatically does the "descending down" into the arrays and pick the ones that match.
fricke

Related

MongoDB filter with composite object

Imagine having a MongoDB collection with this "schema":
mycollection: {
name: String
treatments: [{
type: ObjectId,
colors: ObjectId[]
}]
}
and have a couple of record like this
{
name: "Foo",
treatments:[
{ type: ObjectId('123456'), colors: [ObjectId('654321')] }
]
},
{
name: "Bar",
treatments:[
{ type: ObjectId('123456'), colors: [ObjectId('789012')] }
]
}
If I try to filter those record using:
{ "treatments.type": ObjectId('123456'), "treatments.colors": { $in: [ObjectId('654321')] } }
I expect that returns only the record with name "Foo", but instead it returns both records, treating the filter like an OR. Even
{ $and: [{ "treatments.type": ObjectId('123456') }, { "treatments.colors": { $in: [ObjectId('654321')] } ] }
returns the same.
Any help or clarification would be greatly appreciated ❤️
In this case you need to use elemMatch in mongodb this will find the element from array which has both the condition true
{ treatments: { $elemMatch: { type: ObjectId('123456'), colors: { $in: [ObjectId('654321')] } } } }
otherwise it is default behaviour in mongodb that when specifying conditions on more than one field nested in an array of documents, you can specify the query such that either a single document meets these condition or any combination of documents (including a single document) in the array meets the conditions.
check this link for further explanation https://docs.mongodb.com/manual/tutorial/query-array-of-documents/

How can I add a new field from another one value?

I have a collection "people" with documents like this one:
{
_id: "...",
name: "...",
age: "..."
}
I want to perform an update to all the documents of this collection so they have an extra field "actualName" with the exact same content like "name". I have tried this:
db.people.updateMany({}, { $set { actualName: $name } })
But I get this error:
$name is not defined
I'm doing it from MongoSH (Compass).
You can use something with aggregation updates
db.collection.update({},
[
{
"$set": {
"actualName": "$name"
}
}
])
Working Mongo playground
Starting with Mongo 4.2, you can use aggregation pipeline with update() method to update fields with values of other fields. You can do it like this:
db.collection.update({},
[
{
"$set": {
"actualName": "$name"
}
}
],
{
"multi": true
})
Here is a working example: https://mongoplayground.net/p/sqZBDLGJy48

How to query mongodb to fetch results based on values nested parameters?

I am working with MongoDB for the first time.
I have a collection whose each document is roughly of the following form in MongoDB:
{
"name":[
{
"value":"abc",
"created_on":"2020-02-06 06:11:21.340611+00:00"
},
{
"value":"xyz",
"created_on":"2020-02-07 06:11:21.340611+00:00"
}
],
"score":[
{
"value":12,
"created_on":"2020-02-06 06:11:21.340611+00:00"
},
{
"value":13,
"created_on":"2020-02-07 06:11:21.340611+00:00"
}
]
}
How will I form a query so that I get the latest updated values of each field in the given document. I went through Query Embedded Documents, but I wasn't able to figure out how It is.
My expected output is:
{
"name": "xyz",
"score": "13"
}
If you always do push new/latest values to arrays name & score, then you can try below query, it would get last element from array as in general new/latest values will always be added as last element in an array :
db.collection.aggregate([
{ $addFields: { name: { $arrayElemAt: ['$name', -1] }, score: { $arrayElemAt: ['$score', -1] } } },
{ $addFields: { name: '$name.value', score: '$score.value' } }])
Test : MongoDB-Playground

MongoDB select from array based on multiple conditions [duplicate]

This question already has answers here:
Retrieve only the queried element in an object array in MongoDB collection
(18 answers)
Closed 3 years ago.
I have the following structure (this can't be changed, that is I have to work with):
{
"_id" : ObjectId("abc123"),
"notreallyusedfields" : "dontcare",
"data" : [
{
"value" : "value1",
"otherSomtimesInterestingFields": 1
"type" : ObjectId("asd123=type1"),
},
{
"value" : "value2",
"otherSometimesInterestingFields": 1
"type" : ObjectId("asd1234=type2"),
},
many others
]
}
So basically the fields for a schema are inside an array and they can be identified based on the type field inside 1 array element (1 schema field and it's value is 1 element in the array). For me this is pretty strange, but I'm new to NoSQL so this may be ok. (also for different data some fields may be missing and the element order in the data array is not guaranteed)
Maybe it's easier to understand like this:
Table a: type1 column | type2 column | and so on (and these are stored in the array like the above)
My question is: how can you select multiple fields with conditions? What I mean is (in SQL): SELECT * FROM table WHERE type1=value1 AND type2=value2
I can select 1 like this:
db.a.find( {"data.type":ObjectId("asd1234=type2"), "data.value":value2}).pretty()
But I don't know how could I include that type1=value1 or even more conditions. And I think this is not even good because it can match any data.value field inside the array so it doesn't mean that where the type is type2 the value has to be value2.
How would you solve this?
I was thinking of doing 1 query for 1 condition and then do another based on the result. So something like the pipeline for aggregation but as I see $match can't be used more times in an aggregation. I guess there is a way to pipeline these commands but this is pretty ugly.
What am I missing? Or because of the structure of the data I have to do these strange multiple queries?
I've also looked at $filter but the condition there also applies to any element of the array. (Or I'm doing it wrong)
Thanks!
Sorry if I'm not clear enough! Please ask and I can elaborate.
(Basically what I'm trying to do based on this structure ( Retrieve only the queried element in an object array in MongoDB collection ) is this: if shape is square then filter for blue colour, if shape is round then filter for red colour === if type is type1 value has to be value1, if type is type2 value has to be value2)
This can be done like:
db.document.find( { $and: [
{ type:ObjectId('abc') },
{ data: { $elemMatch: { type: a, value: DBRef(...)}}},
{ data: { $elemMatch: { type: b, value: "string"}}}
] } ).pretty()
So you can add any number of "conditions" using $and so you can specify that an element has to have type a and a value b, and another element type b and value c...
If you want to project only the matching elements then use aggregate with filter:
db.document.aggregate([
{$match: { $and: [
{ type:ObjectId('a') },
{ data: { $elemMatch: { Type: ObjectId("b"), value: DBRef(...)}}},
{ data: { $elemMatch: { Type: ObjectId("c"), value: "abc"}}}
] }
},
{$project: {
metadata: {
$filter: {
input: "$data",
as: "data",
cond: { $or: [
{$eq: [ "$$data.Type", ObjectId("a") ] },
{$eq: [ "$$data.Type", ObjectId("b") ] }]}
}
}
}
}
]).pretty()
This is pretty ugly so if there is a better way please post it! Thanks!
If you need to retrieve documents that have array elements matching
multiple conditions, you have to use $elemMatch query operator.
db.collection.find({
data: {
$elemMatch: {
type: "type1",
value: "value1"
}
}
})
This will output whole documents where an element matches.
To output only first matching element in array, you can combine it with $elemMatch projection operator.
db.collection.find({
data: {
$elemMatch: {
type: "type1",
value: "value1"
}
}
},
{
data: {
$elemMatch: {
type: "type1",
value: "value1"
}
}
})
Warning, don't forget to project all other fields you need outside data array.
And if you need to output all matching elements in array, then you have to use $filter in an aggregation $project stage, like this :
db.collection.aggregate([
{
$project: {
data: {
$filter: {
input: "$data",
as: "data",
cond: {
$and: [
{
$eq: [
"$$data.type",
"type1"
]
},
{
$eq: [
"$$data.value",
"value1"
]
}
]
}
}
}
}
}
])

How to filter sub array and project multiple matching values of a matching document in mongoDb

I have a structure like:
{
_id: ObjectId("doc1"),
SubArrayDocs: [
{
_id: ObjectId("subDoc1"),
name: "name1"
},
{
_id: ObjectId("subDoc2"),
name: "name2"
},
{
_id: ObjectId("subDoc3"),
name: "name3"
}
]
},
{
_id: ObjectId("doc2"),
....
}
I would like to match doc1 and when projecting it to return filtered SubArrayDocs by _id so that I get only subDoc1 and subDoc2.
I've tried a few things and I can either get all subArrayDocs or just first one that matches $in operator.
Is there a way to filter Subarray documents on a matching document so that I get back multiple subarray documents, without using aggregation framework.
Expected result for given orgId: "doc1" and
sub array docs: "subDoc1, subDoc2" should be:
{
_id: ObjectId("doc1"),
SubArrayDocs: [
{
_id: ObjectId("subDoc1"),
name: "name1"
},
{
_id: ObjectId("subDoc2"),
name: "name2"
}
]
}
So basically, get the doc but with filtered contents of its sub array.
I've tried everything that I could with $in $elemMatch, projections and positional params.