How to match two different Object ids using MongoDB find() query? - mongodb

I have an entry like below,
[
{
"_id":ObjectId("59ce020caa87df4da0ee2c78"),
"name": "Tom",
"owner_id": ObjectId("59ce020caa87df4da0ee2c78")
},
{
"_id":ObjectId("59ce020caa87df4da0ee2c79"),
"name": "John",
"owner_id": ObjectId("59ce020caa87df4da0ee2c78")
}
]
now, I need to find the person whose _id is equal to owner_id using find() in MongoDB.
Note, we can't not use $match (aggregation) due to some reason.
I am using this query,
db.people.find({ $where: "this._id == this.owner_id" })
but, it's not returning the expected output. Can anyone help me with this.
Thanks.

Using $expr and $eq you can get desired values avoiding the use of $where into a find stage (not aggregation necessary).
db.collection.find({
"$expr": {
"$eq": [
"$_id",
"$owner_id"
]
}
})

Related

MongoDB - How can I use a field's value in the first argument of $centerSphere

I'm trying to get a negative match for $geoWithin, will be used in mongodb Charts.
all of the required information is in the result of the latest stage in an aggregation i'm constructing in mongodb compass, the result of that stage looks like this:
{
"PizzaId": "123",
"info": {
"timestamp": {
"$date": "2021-02-15T05:00:00.000Z"
},
"location": {
"type": "Point",
"coordinates": [33.21883773803711, 33.802675247192383]
},
"dayOfWeek": 2,
},
"PizzaLocation": [{
"_id": "456",
"location": {
"type": "Point",
"coordinates": [37.83396911621094, 37.07674026489258]
}
}]
}
I want to add a stage after that a filter that checks that info.location is not in a 100 km radius within Pizzalocation.0.location:
{
$match: {
"info.location.coordinates": {
$not:
{
$geoWithin: {
$centerSphere: [
"$PizzaLocation.0.location.coordinates",
100 / 6378.1
]
}
}
}
}
}
I get an error: Point must be an array or object
Things I tried:
playing with the field name in centerSphere: removing the 0, or $, using:
{$arrayElemAt: ["$PizzaLocation.location.coordinates",0]}
even used the [lon,lat] format and put
[{$arrayElemAt: [{$arrayElemAt: ["$PizzaLocation.location.coordinates",0]},0]},
{$arrayElemAt: [{$arrayElemAt: ["$PizzaLocation.location.coordinates",0]},1]}]
setting literal coordinates instead of field name, it worked, but I need to use a field.
creating a view that will hold the centerSphere itself, and use a lookup to get it, but mongoDB didn't recognize $geoWithin nor $centerSphere in $addField aggregation
Things I verified:
I used $project stage on {$arrayElemAt: ["$PizzaLocation.location.coordinates",0]} , and indeed it showed in the array: [lon,lat]
I used $project stage on
{$arrayElemAt: [{$arrayElemAt: ["$PizzaLocation.location.coordinates",0]},0]}
and
{$arrayElemAt: [{$arrayElemAt: ["$PizzaLocation.location.coordinates",0]},1]}
and indeed it showed a number for each one.
So, how can I use a field's value(s) in the first argument of $centerSphere.
thank you.
So you can't do it, let's first understand why not.
From the $match docs:
The query syntax is identical to the read operation query syntax;
This means $match queries use the same syntax as find queries. and unsurprisingly $geoWithin is a query operator.
Unfortunately query syntax can not access the document values as part of the query. This is also the reason why your query fails, the "coordinates" you pass are being parsed as a string expression.
For example this following query:
{
$match: {
field1: {$eq: "$field2"}
}
}
Matches: { field1: "$field2"} but no { field1: 1, field2: 1 }
Again this is just the query language parser's behaviour so there's not much you can do.
The alternative is to use an the $geoNear stage, but not only there is no easy way to combine it with $not logic there are additional restrictions like it having to be the first stage of the pipeline and so on.
The best I can recommend is split your query into 2 parts, 1 fetch the document you need and only then re-query it using $geoWithin with the proper coordinates input.

Trying to fetch data from Nested MongoDB Database?

I am beginner in MongoDB and struck at a place I am trying to fetch data from nested array but is it taking so long time as data is around 50K data, also it is not much accurate data, below is schema structure please see once -
{
"_id": {
"$oid": "6001df3312ac8b33c9d26b86"
},
"City": "Los Angeles",
"State":"California",
"Details": [
{
"Name": "Shawn",
"age": "55",
"Gender": "Male",
"profession": " A science teacher with STEM",
"inDate": "2021-01-15 23:12:17",
"Cars": [
"BMW","Ford","Opel"
],
"language": "English"
},
{
"Name": "Nicole",
"age": "21",
"Gender": "Female",
"profession": "Law student",
"inDate": "2021-01-16 13:45:00",
"Cars": [
"Opel"
],
"language": "English"
}
],
"date": "2021-01-16"
}
Here I am trying to filter date with date and Details.Cars like
db.getCollection('news').find({"Details.Cars":"BMW","date":"2021-01-16"}
it is returning details of other persons too which do not have cars- BMW , Only trying to display details of person like - Shawn which have BMW or special array value and date too not - Nicole, rest should not appear but is it not happening.
Any help is appreciated. :)
A combination of $match on the top-level fields and $filter on the array elements will do what you seek.
db.foo.aggregate([
{$match: {"date":"2021-01-16"}}
,{$addFields: {"Details": {$filter: {
input: "$Details",
as: "zz",
cond: { $in: ['BMW','$$zz.Cars'] }
}}
}}
,{$match: {$expr: { $gt:[{$size:"$Details"},0] } }}
]);
Notes:
$unwind is overly expensive for what is needed here and it likely means "reassembling" the data shape later.
We use $addFields where the new field to add (Details) already exists. This effectively means "overwrite in place" and is a common idiom when filtering an array.
The second $match will eliminate docs where the date matches but not a single entry in Details.Cars is a BMW i.e. the array has been filtered down to zero length. Sometimes you want to know this info so if this is the case, do not add the final $match.
I recommend you look into using real dates i.e. ISODate instead of strings so that you can easily take advantage of MongoDB date math and date formatting functions.
Is a common mistake think that find({nested.array:value}) will return only the nested object but actually, this query return the whole object which has a nested object with desired value.
The query is returning the whole document where value BMW exists in the array Details.Cars. So, Nicole is returned too.
To solve this problem:
To get multiple elements that match the criteria you can do an aggregation stage using $unwind to separate the different objects into array and match by the criteria you want.
db.collection.aggregate([
{
"$match": { "Details.Cars": "BMW", "date": "2021-01-26" }
},
{
"$unwind": "$Details"
},
{
"$match": { "Details.Cars": "BMW" }
}
])
This query first match by the criteria to avoid $unwind over all collection.
Then $unwind to get every document and $match again to get only the documents you want.
Example here
To get only one element (for example, if you match by _id and its unique) you can use $elemMatch in this way:
db.collection.find({
"Details.Cars": "BMW",
"date": "2021-01-16"
},
{
"Details": {
"$elemMatch": {
"Cars": "BMW"
}
}
})
Example here
You can use $elemenMatch into query or projection stage. Docs here and here
Using $elemMatch into query the way is this:
db.collection.find({
"Details": {
"$elemMatch": {
"Cars": "BMW"
}
},
"date": "2021-01-16"
},
{
"Details.$": 1
})
Example here
The result is the same. In the second case you are using positional operator to return, as docs says:
The first element that matches the query condition on the array.
That is, the first element where "Cars": "BMW".
You can choose the way you want.

Can I use the existing document field value in the $push of mongodb ?, I tried the below approach , its not working

db.getCollection('test').update({
"b_id": "3"
},
{
"$push": {"books": {
"status": "available",
"shelf_id": "$shelf_id",
"rack_no": "$rack_no",
"book_id": new ObjectId()
}
}
})
shelf_id and rack_no are already available in the document. Here, I need to create the array "books" which includes status, shelf_id, rack_no and book_id.
I am not able to get the existing value of shelf_id and rack_no in this case. It considers $rack_id as string instead the value from the document. Please suggest a way.
Yes, you can do that in MongoDB 4.2, using the pipeline form of update, but not with $push.
Passing an array of pipeline stages as the second argument to update will allow you to use aggregation operators.
The $push aggregation operator behaves very differently from the $push update operator, so in a pipeline use $set with $concatArrays, like:
db.getCollection('test').update({
"b_id": "3"
},
[{$set:
{
books: {$concatArrays:[
"$books",
[{
"status": "available",
"shelf_id": "$shelf_id",
"rack_no": "$rack_no",
"book_id": new ObjectId()
}]
]}
}
}])

calculating $avg value within a given geo polygon

i'm trying to calculate a value within a given polygon:
acutally i'm using this pipeline:
'aggregation': {
'pipeline': [
{ "$match" : { "location" : "$loc" } },
{ "$group": { "_id": 'Average', "AvgField": { "$avg": "$myavgvalue" } , "count": {"$sum": 1} } },
]
}
but it seems the $match is ignoring the geospatial index.
any idea how i can do this ?
best regards
Harald
You need to use the ?aggregate={"$loc": ...} query syntax, so the parser knows it has to invoke the aggregation engine instead of the standard query parser. This example comes straight from the documentation:
$ curl -i http://example.com/posts?aggregate={"$value": 2}
Also, make sure the proper geo index has been added to the collection. Eve won't automatically do that for you, unless you explicitly choose to do so by setting mongo_indexes.

MongoDB: Sort by field existing and then alphabetically

In my database I have a field of name. In some records it is an empty string, in others it has a name in it.
In my query, I'm currently doing:
db.users.find({}).sort({'name': 1})
However, this returns results with an empty name field first, then alphabetically returns results. As expected, doing .sort({'name': -1}) returns results with a name and then results with an empty string, but it's in reverse-alphabetical order.
Is there an elegant way to achieve this type of sorting?
How about:
db.users.find({ "name": { "$exists": true } }).sort({'name': 1})
Because after all when a field you want to sort on is not actually present then the returned value is null and therefor "lower" in the order than any positive result. So it makes sense to exclude those results if you really are only looking for something with a matching value.
If you really want all the results in there and regarless of a null content, then I suggest you "weight" them via .aggregate():
db.users.aggregate([
{ "$project": {
"name": 1,
"score": {
"$cond": [
{ "$ifNull": [ "$name", false ] },
1,
10
]
}
}},
{ "$sort": { "score": 1, "name": 1 } }
])
And that moves all null results to the "end of the chain" by assigning a value as such.
If you want to filter out documents with an empty "name" field, change your query: db.users.find({"name": {"$ne": ""}}).sort({"name": 1})