MongoDB - AND request on array with GeoJSON element - arrays

I've looked at other issues without finding a suitable answer for my problem, so I hope you may help me.
SITUATION
I am currently working on a localisation app with MongoDB. For the moment I test my queries in the MongoDB shell.
I have a collection of Point documents with locations. I use GeoJSON objects for the coordinates of my point.
Here is what a Point document looks like :
{
_id: "Roma_DellArte",
localisations: [
{ location_type_id: 1, location: { type: "Point", coordinates: [ 41.9097306,12.2558141 ] } }
]
}
My location_type_id refers to another collection, juste for you to know.
I already made a query which gets me all the points near a precise location :
db.point.createIndex({ 'localisations.location': "2dsphere" })
db.point.find({
'localisations.location': {
$near: {
$geometry: { type: "Point", coordinates: [ 48.8588377, 2.2770207 ] },
$minDistance: 0,
$maxDistance: 100000
}
}
})
Now I would like to query all the points which are near a precise location AND those with a specific location_type_id.
TRIES AND FAILS
I tried many queries in the MongoDB shell but none of them produced a satisfying result.
Query 1
I think it doesn't return anything because location: isn't an exact field.
db.point.createIndex({ location: "2dsphere" })
db.point.find({
'localisations': {
location_type_id: 1,
location: {
$near: {
$geometry: { type: "Point", coordinates: [ 48.8588377, 2.2770207 ] },
$minDistance: 0,
$maxDistance: 100000
}
}
}
})
Query 2
The main problem here is it gets me a point document if in the localisations field there is an object with a correct location_type_id and an object with the correct location. It is not necessarily the same object, which I want it to be.
see: https://docs.mongodb.com/manual/tutorial/query-array-of-documents/#combination-of-elements-satisfies-the-criteria - second paragraph at the end.
db.point.createIndex({ 'localisations.location': "2dsphere" })
db.point.find({
'localisations.location_type_id': 1,
'localisations.location': {
$near: {
$geometry: { type: "Point", coordinates: [ 48.8588377, 2.2770207 ] },
$minDistance: 0,
$maxDistance: 100000
}
}
})
Query 3
I wanted to try this method : https://docs.mongodb.com/manual/tutorial/query-arrays/#query-for-an-array-element-that-meets-multiple-criteria
db.point.createIndex({ location: "2dsphere" })
db.point.find({
localisations: {
$elemMatch: {
location_type_id: 1,
location: {
$near: {
$geometry: { type: "Point", coordinates: [ 48.8588377, 2.2770207 ] },
$minDistance: 0,
$maxDistance: 100000
}
}
}
}
})
Unfortunately, I get this error:
Error: error: {
"ok" : 0,
"errmsg" : "geoNear must be top-level expr",
"code" : 2,
"codeName" : "BadValue"
}
Now you know everything, I hope you can help me.

Related

Find records that start with specific digits MongoDB from other collection

I have two collections that look like this:
Ship_Data
{
_id: ObjectId("63d19d7a1991a09011aa35ef"),
sourcemmsi: 228051000,
navigationalstatus: 0,
course: {
rateofturn: -127,
speedoverground: 0,
courseoverground: 291.5,
trueheading: 511
},
coordinates: {
longitude: -4.4850965,
latitude: 48.38132,
timestamp: 1443650424
}
}
SourceMMSI
{
_id: ObjectId("63d19f671991a09011e4eba4"),
mmsi_code: 228,
country: 'France'
}
The first one (Ship_Data) has informations about specific ships while the second one (SourceMMSI) shows what the first digits of source mmsi code represent. For example in our case since the sourcemmsi field in Ship_Data start with 228, based on the second collection, the ship might be from France.
What I want to accomplish is to create a query using those two collection and returns records based on the first three digits from the sourcemmsi field. Is there any way I can accomplish that?
You can convert the code into strings by $toString (sourcemmsi need an extra conversion to Long first before converting to string). Then, use $indexOfCP to perform substring check and see whether the index is 0. (i.e. at start of the sourcemmsi).
db.SourceMMSI.aggregate([
{
"$lookup": {
"from": "Ship_Data",
"let": {
code: {
"$toString": "$mmsi_code"
}
},
"pipeline": [
{
$match: {
$expr: {
$eq: [
0,
{
"$indexOfCP": [
{
$toString: {
$toLong: "$sourcemmsi"
}
},
"$$code"
]
}
]
}
}
}
],
"as": "shipDataLookup"
}
}
])
Mongo Playground

Mongo updateMany statement with an inner array of objects to manipulate

I'm struggling to write a Mongo UpdateMany statement that can reference and update an object within an array.
Here I create 3 documents. Each document has an array called innerArray always containing a single object, with a single date field.
use test;
db.innerArrayExample.insertOne({ _id: 1, "innerArray": [ { "originalDateTime" : ISODate("2022-01-01T01:01:01Z") } ]});
db.innerArrayExample.insertOne({ _id: 2, "innerArray": [ { "originalDateTime" : ISODate("2022-01-02T01:01:01Z") } ]});
db.innerArrayExample.insertOne({ _id: 3, "innerArray": [ { "originalDateTime" : ISODate("2022-01-03T01:01:01Z") } ]});
I want to add a new date field, based on the original date field, to end up with this:
{ _id: 1, "innerArray": [ { "originalDateTime" : ISODate("2022-01-01T01:01:01Z"), "copiedDateTime" : ISODate("2022-01-01T12:01:01Z") } ]}
{ _id: 2, "innerArray": [ { "originalDateTime" : ISODate("2022-01-02T01:01:01Z"), "copiedDateTime" : ISODate("2022-01-02T12:01:01Z") } ]}
{ _id: 3, "innerArray": [ { "originalDateTime" : ISODate("2022-01-03T01:01:01Z"), "copiedDateTime" : ISODate("2022-01-03T12:01:01Z") } ]}
In pseudo code I am saying take the originalDateTime, run it through a function and add a related copiedDateTime value.
For my specific use-case, the function I want to run strips the timezone from originalDateTime, then overwrites it with a new one, equivalent to the Java ZonedDateTime function withZoneSameLocal. Aka 9pm UTC becomes 9pm Brussels (therefore effectively 7pm UTC). The technical justification and methodology were answered in another Stack Overflow question here.
The part of the query I'm struggling with, is the part that updates/selects data from an element inside an array. In my simplistic example, for example I have crafted this query, but unfortunately it doesn't work:
This function puts copiedDateTime in the correct place... but doesn't evaluate the commands to manipulate the date:
db.innerArrayExample.updateMany({ "innerArray.0.originalDateTime" : { $exists : true }}, { $set: { "innerArray.0.copiedDateTime" : { $dateFromString: { dateString: { $dateToString: { "date" : "$innerArray.0.originalDateTime", format: "%Y-%m-%dT%H:%M:%S.%L" }}, format: "%Y-%m-%dT%H:%M:%S.%L", timezone: "Europe/Paris" }}});
// output
{
_id: 1,
innerArray: [
{
originalDateTime: ISODate("2022-01-01T01:01:01.000Z"),
copiedDateTime: {
'$dateFromString': {
dateString: { '$dateToString': [Object] },
format: '%Y-%m-%dT%H:%M:%S.%L',
timezone: 'Europe/Paris'
}
}
}
]
}
This simplified query, also has the same issue:
b.innerArrayExample.updateMany({ "innerArray.0.originalDateTime" : { $exists : true }}, { $set: { "innerArray.0.copiedDateTime" : "$innerArray.0.originalDateTime" }});
//output
{
_id: 1,
innerArray: [
{
originalDateTime: ISODate("2022-01-01T01:01:01.000Z"),
copiedDateTime: '$innerArray.0.originalDateTime'
}
]
}
As you can see this issue looks to be separate from the other stack overflow question. Instead of being able changing timezones, it's about getting things inside arrays to update.
I plan to take this query, create 70,000 variations of it with different location/timezone combinations and run it against a database with millions of records, so I would prefer something that uses updateMany instead of using Javascript to iterate over each row in the database... unless that's the only viable solution.
I have tried putting $set in square brackets. This changes the way it interprets everything, evaluating the right side, but causing other problems:
test> db.innerArrayExample.updateMany({ "_id" : 1 }, [{ $set: { "innerArray.0.copiedDateTime" : "$innerArray.0.originalDateTime" }}]);
//output
{
_id: 1,
innerArray: [
{
'0': { copiedDateTime: [] },
originalDateTime: ISODate("2022-01-01T01:01:01.000Z")
}
]
}
Above it seems to interpret .0. as a literal rather than an array element. (For my needs I know the array only has 1 item at all times). I'm at a loss finding an example that meets my needs.
I have also tried experimenting with the arrayFilters, documented on my mongo updateMany documentation but I cannot fathom how it works with objects:
test> db.innerArrayExample.updateMany(
... { },
... { $set: { "innerArray.$[element].copiedDateTime" : "$innerArray.$[element].originalDateTime" } },
... { arrayFilters: [ { "originalDateTime": { $exists: true } } ] }
... );
MongoServerError: No array filter found for identifier 'element' in path 'innerArray.$[element].copiedDateTime'
test> db.innerArrayExample.updateMany(
... { },
... { $set: { "innerArray.$[0].copiedDateTime" : "$innerArray.$[element].originalDateTime" } },
... { arrayFilters: [ { "0.originalDateTime": { $exists: true } } ] }
... );
MongoServerError: Error parsing array filter :: caused by :: The top-level field name must be an alphanumeric string beginning with a lowercase letter, found '0'
If someone can help me understand the subtleties of the Mongo syntax and help me back on to the right path I'd be very grateful.
You want to be using pipelined updates, the issue you're having with the syntax you're using is that it does not allow the usage of aggregation operators and document field values.
Here is a quick example on how to do it:
db.collection.updateMany({},
[
{
"$set": {
"innerArray": {
$map: {
input: "$innerArray",
in: {
$mergeObjects: [
"$$this",
{
copiedDateTime: "$$this.originalDateTime"
}
]
}
}
}
}
}
])
Mongo Playground

MongoDB Aggregate pipeline with $group and $count on a Pointer reference returns wrong data

I have a set of data in MongoDB with parse-server in the following format-
Rating => objectId, user<_User>, rating...
_User => objectId, gender<m|f|nb|na>
I have been trying to group the data based on the user's gender to find out how many male, female, non-binary or N/A users have rated. user field in a pointer reference to _User. I am using the following aggregate pipeline.
const pipeline = [
{
lookup: {
from: '_User',
localField: 'user',
foreignField: 'objectId',
as: 'user'
}
},
{
unwind: { path: '$user' }
},
{
group: {
objectId: '$user.gender',
count: {
$sum: 1
}
}
}
]
const data = await new Query('Rating').aggregate(pipeline)
Result =>
[
{
"count": 54,
"objectId": "na"
},
{
"count": 405,
"objectId": null
},
{
"count": 27,
"objectId": "f"
},
{
"count": 540,
"objectId": "m"
}
],
However, returned data count doesn't match with actual data. The actual database has only 27 ratings with 1 f, 2 na, 24 m.
For MongoDB developers, objectId is equavalent to _id.
I am a novice to aggregation framework. What am I doing wrong?
Server Environment-
parse-server: 3.2.3
mongodb: 4.0.2
It is tricky because you need to understand how Parse Server stores the data inside the MongoDB. The following query should solve your problem:
const query = new Parse.Query('Rating');
const pipeline = [
{
project: {
objectId: 1,
userId: { $substr: ['$_p_user', '_User$'.length, -1] }
}
},
{
lookup: {
from: '_User',
localField: 'userId',
foreignField: '_id',
as: 'user'
}
},
{
unwind: { path: '$user' }
},
{
group: {
objectId: '$user.gender',
count: {
$sum: 1
}
}
}
];
return await query.aggregate(pipeline, { useMasterKey: true });

Meteor $push multipoint array

I set coordinates in a geolocation array:
Crowds.insert({
location: {"type": "MultiPoint","coordinates":
[[1, 1]]
}
});
Crowds._ensureIndex({ location: "2dsphere" });
Then I try to add value. To do so I make a §push to add new value in "coordinates" array:
Crowds.update(
{ _id: crowd[0]._id },
{ $push: { location: { "coordinates": [ 2, 2 ]
}}}
);
I get the folling error:
Exception in Mongo write: TypeError: object is not a function
It seems that I am not updating coordinates array the proper way... I tried various combinations but cannot find how to add value in a nested array...
Please help ;) Thanks
Is there a typo there?
crowd[0]._id
"crowd" is singular there, yet you're calling [0] as if it's an array.
Should it be:
crowds[0]._id
I think I found the problem, and actually I didn't ask the question well... sorry
The following code returned error:
Crowds.update(
{ _id: '123' },
{ $inc: { people: 1 } },
{ $push: { "location.coordinates": [ Meteor.user().profile.location.coords.longitude, Meteor.user().profile.location.coords.latitude ],
{ $set: { modified: new Date().valueOf() } },
function(error){
return "Update error: " + error;
}
);
But it works with:
Crowds.update(
{ _id: '123' },
{ $inc: { people: 1 } },
{ $set: { modified: new Date().valueOf() } },
function(error){
return "Update error: " + error;
}
);
Crowds.update(
{ _id: crowd[0]._id },
{ $push: { "location.coordinates": [ Meteor.user().profile.location.coords.longitude, Meteor.user().profile.location.coords.latitude ]
}},
function(error){
return "Update error: " + error;
}
);
It seems that $push needs to be used alone, tell me if I am wrong in diagnostic ;)

Update embedded mongoose document in array

Lets say that I have the following document in the books collection:
{
_id:0 ,
item: "TBD",
stock: 0,
info: { publisher: "1111", pages: 430 },
tags: [ "technology", "computer" ],
ratings: [ { _id:id1, by: "ijk", rating: 4 }, {_id:id2 by: "lmn", rating: 5 } ],
reorder: false
}
I would like to update the value of ratings[k].rating and all I know is the id of the collection and the _id of the objects existing in the array ratings.
The tutorial of mongoDB has the following example that uses the position of the object inside the array but I suppose that if the update can only be done by knowing the position, this means that I firstly have to find the position and then proceed with the update? Can I do the update with only one call and if so how I can do that?
db.books.update(
{ _id: 1 },
{
$inc: { stock: 5 },
$set: {
item: "ABC123",
"info.publisher": "2222",
tags: [ "software" ],
"ratings.1": { by: "xyz", rating: 3 }
}
}
)
Sorry for late answer; I think this is what you want to do with mongoose.
Books.findOneAndUpdate({
_id: 1,
'ratings._id': id1
},
{
$set: {
'ratings.$.rating' : 3
}
}, function(err, book){
// Response
});
Positional operator may help you:
db.books.update(
// find book by `book_id` with `rating_id` specified
{ "_id": book_id, "ratings._id": rating_id },
// set new `value` for that rating
{ $set: { 'ratings.$.rating': value }}
);
$ will save position of matched document.

Resources