I struggle writing Mongo Queries. I can never understand why it doesn't return what I expect and yes I am reading the documentation but apparently to dumb to understand.
In Compass I write this query for a State collection with a document array of cities.
{"Cities.CityName":"Denver"}
This returns to me the State of Colorado document with ALL the cities in the document array. My sample below just shows one city document but they are all there.
{
"_id": {"$oid":"6146ada531696ee91a3f9fa4"},
"StateName": "Colorado",
"StateCode": "CO",
"Cities": [{
"_id": {"$oid":"6146ada531696ee91a3f5a50"},
"CityName": "Denver",
"Latitude": "39.55666000",
"Longitude": "-104.89609000"
}...]
}
OK so I'm thinking clearly we matched on the CityName now just project the _id of the City document.
{"Cities._id":1}
But this always returns to me the State document id NOT the matched City Document _id.
What am I doing wrong?
1) You can use $ (projection) or $elemMatch (projection):
use case: The positional $ operator limits the contents of an <array> to return the first element that matches the query condition on the array.
ex:
{ "Cities.$": 1 } or {"Cities": { "$elemMatch": { "CityName": "Denver" } } }
result:
{
"_id":{"$oid":"6146ada531696ee91a3f9fa4"},
"Cities":[{
"_id":{"$oid":"6146ada531696ee91a3f5a50"},
"CityName":"Denver",
"Latitude":"39.55666000",
"Longitude":"-104.89609000"
}]
}
Playground
Note: You have to specify the required result fields in the projection!
2) You can use $filter aggregation operator, supported from MongoDB 4.4 in find() method:
use case: Selects a subset of an array to return based on the specified condition. Returns an array with only those elements that match the condition. The returned elements are in the original order.
ex:
{
"Cities": {
"$filter": {
"input": "$Cities",
"cond": { "$eq": ["$$this.CityName", "Denver"] }
}
}
}
result:
{
"_id":{"$oid":"6146ada531696ee91a3f9fa4"},
"Cities":[{
"_id":{"$oid":"6146ada531696ee91a3f5a50"},
"CityName":"Denver",
"Latitude":"39.55666000",
"Longitude":"-104.89609000"
}...{}]
}
Playground
Note: You have to specify the required result fields in the projection!
2.1) You can use $map aggregation operator to select only _ids from the Cities array:
use case: Applies an expression to each item in an array and returns an array with the applied results.
ex:
{
"Cities": {
"$map": {
"input": {
"$filter": {
"input": "$Cities",
"cond": { "$eq": ["$$this.CityName", "Denver"] }
}
},
"in": "$$this._id"
}
}
}
result:
{
"_id":{"$oid":"6146ada531696ee91a3f9fa4"},
"Cities":[
{"$oid":"6146ada531696ee91a3f5a50"},
{"$oid":"6146ada531696ee91a3f5a51"},
....
]
}
Playground
Note: You have to specify the required result fields in the projection!
3) You can use aggregation method aggregate() for more customization:
- ex:
$match to check query condition
$addFields to add or format the existing properties
$filter and $map i have explained in 2) point
db.collection.aggregate([
{ "$match": { "Cities.CityName": "Denver" } },
{
"$addFields": {
"Cities": {
"$map": {
"input": {
"$filter": {
"input": "$Cities",
"cond": { "$eq": ["$$this.CityName", "Denver"] }
}
},
"in": "$$this._id"
}
}
}
}
])
Playground
result:
[
{
"Cities": [
ObjectId("6146ada531696ee91a3f5a50")
],
"StateCode": "CO",
"StateName": "Colorado",
"_id": ObjectId("6146ada531696ee91a3f9fa4")
}
]
Related
I have data stored in following format in mongodb .. help me in knowing how can I write a query to find if the database has a particular id stored in one of the sheet or not
the structure of data is like the following :-
{
"name": "abc",
"linked_data": {
"sheet 1": [
"7d387e05d3f8180a",
"8534sfjog904395"
],
"sheet 2": [
"7d387e05d3f8180a",
"54647sgdsevey67r34"
]
}
}
for example if id "8534sfjog904395" is mapped with "sheet 1".. then it should return me this data where id is mapped with the sheet.
I am passing id in the query and want to find inside linked_data and then all the sheets
Using dynamic value as field name is considered as an anti-pattern and introduce unnecessary complexity to queries. Nevertheless, you can use $objectToArray to convert the linked_data into array of k-v tuples. $filter to get only the sheet you want. Finally, revert back to original structure using $arrayToObject
db.collection.aggregate([
{
"$set": {
"linked_data": {
"$objectToArray": "$linked_data"
}
}
},
{
$set: {
linked_data: {
"$filter": {
"input": "$linked_data",
"as": "ld",
"cond": {
"$in": [
"8534sfjog904395",
"$$ld.v"
]
}
}
}
}
},
{
"$set": {
"linked_data": {
"$arrayToObject": "$linked_data"
}
}
},
{
$match: {
linked_data: {
$ne: {}
}
}
}
])
Mongo Playground
I have a MongoDB collection with a schema like this:
{
"_id":"0e5101cb8c39356305830f36",
"person1":{
"name":"Test",
"status":"valid"
},
"person2":{
"name":"Test2",
"status":"invalid"
},
"person3":{
"name":"Test3",
"status":"valid"
}
}
The status field can be either valid, invalid or pending. I want to query this collection and then count, for the whole collection, how many valid, invalid and pending persons it has. I am using a no-code tool to run a query so I think I need to use something like Aggregation Framework to process this. I am new to Aggregation framework, I tried a bunch of stages but couldn't make this work. Any help is appreciated.
The data model is awkward (the first two stages below are just to get the data in a "reasonable" format), but here's one way you could do it.
db.collection.aggregate([
{
"$project": {
"rootArray": {"$objectToArray": "$$ROOT"}
}
},
{
"$project": {
"persons": {
"$map": {
"input": {
"$filter": {
"input": "$rootArray",
"cond": {
"$regexMatch": {
"input": "$$this.k",
"regex": "person"
}
}
}
},
"as": "person",
"in": "$$person.v"
}
}
}
},
{"$unwind": "$persons"},
{
"$group": {
"_id": "$persons.status",
"count": {"$count": {} }
}
}
])
Try it on mongoplayground.net.
I have a collection name 'persons'. Inside the collection, I have the following structure. I would like to get to present all the fields that contain the term "friend".
name: 'David',
contacts: {
friend_1: 'Bill'
friend_2: 'George'
friend_3: 'Donald'
friend_4: 'Richard'
managaer: 'James'
mentor: 'Andy'
}
I thought to try maybe db.persons.find({}, {/.*friend.*/: 1}), but obviously it didn't work.
How can I achieve it?
It doesn't seem to be a correct structure. If this is the case, maybe you can store an array in 'friends' field.
name: 'David',
contacts: {
friends: ['Bill', 'George', 'Donald', 'Richard'],
manager: 'James',
mentor: 'Andy'
}
First of all, obviously this is not the best structure, but is what it is, and then you can use this aggregation query:
First set the object as array with $objectToArray to filter by the regex.
Then use $filter with $regexMatch to get only values you want into the array.
And recreate the object again using $arrayToObject.
db.collection.aggregate([
{
"$set": {
"contacts": {
"$objectToArray": "$contacts"
}
}
},
{
"$project": {
"name": 1,
"contacts": {
"$filter": {
"input": "$contacts",
"as": "c",
"cond": {
"$regexMatch": {
"input": "$$c.k",
"regex": ".*friend.*",
"options": "i"
}
}
}
}
}
},
{
"$set": {
"contacts": {
"$arrayToObject": "$contacts"
}
}
}
])
Example here
can anyone please help I want to query mongo to unwind the array,and i am using mongdb native driver My collection document is as follow,Also please ignore my objectId its just for sample
{
"_id":ObjectId(123),
"name":"Sam",
"age":20,
"hobbiesDetail":[
{
"description":"FootBall",
"level":"20%"
},
{
"description":"Cricket",
"level":"80%"
}
]
},
{
"_id":ObjectId(124),
"name":"Ted",
"age":26,
"hobbiesDetail":[
{
"description":"FootBall",
"level":"20%"
}
]
}
And my expected output is
[
{
"name":"Sam",
"age":20,
"hobbies":"Football,Cricket"
},
{
"name":"Ted",
"age":26,
"hobbies":"Football"
}
]
I just want to unwind my array and add a comma between hobbies description in one query, Thanx for any help
All you need is a $projection with the $map array operator.
db.collection.aggregate([
{ "$project": {
"name": 1,
"age": 1,
"hobbies": {
"$map": {
"input": "$hobbiesDetail",
"as": "hobby",
"in": "$$hobby.description"
}
}
}}
])
I want to get all matching values, using $elemMatch.
// create test data
db.foo.insert({values:[0,1,2,3,4,5,6,7,8,9]})
db.foo.find({},{
'values':{
'$elemMatch':{
'$gt':3
}
}
}) ;
My expecected result is {values:[3,4,5,6,7,8,9]} . but , really result is {values:[4]}.
I read mongo document , I understand this is specification.
How do I search for multi values ?
And more, I use 'skip' and 'limit'.
Any idea ?
Using Aggregation:
db.foo.aggregate([
{$unwind:"$values"},
{$match:{"values":{$gt:3}}},
{$group:{"_id":"$_id","values":{$push:"$values"}}}
])
You can add further filter condition in the $match, if you would like to.
You can't achieve this using an $elemMatch operator since, mongoDB doc says:
The $elemMatch projection operator limits the contents of an array
field that is included in the query results to contain only the array
element that matches the $elemMatch condition.
Note
The elements of the array are documents.
If you look carefully at the documentation on $elemMatch or the counterpart to query of the positional $ operator then you would see that only the "first" matched element is returned by this type of "projection".
What you are looking for is actually "manipulation" of the document contents where you want to "filter" the content of the array in the document rather than return the original or "matched" element, as there can be only one match.
For true "filtering" you need the aggregation framework, as there is more support there for document manipulation:
db.foo.aggregate([
// No point selecting documents that do not match your condition
{ "$match": { "values": { "$gt": 3 } } },
// Unwind the array to de-normalize as documents
{ "$unwind": "$values },
// Match to "filter" the array
{ "$match": { "values": { "$gt": 3 } } },
// Group by to the array form
{ "$group": {
"_id": "$_id",
"values": { "$push": "$values" }
}}
])
Or with modern versions of MongoDB from 2.6 and onwards, where the array values are "unique" you could do this:
db.foo.aggregate([
{ "$project": {
"values": {
"$setDifference": [
{ "$map": {
"input": "$values",
"as": "el",
"in": {
"$cond": [
{ "$gt": [ "$$el", 3 ] },
"$$el",
false
]
}
}},
[false]
]
}
}}
])