I am trying to find elements from my MongoDB database with meteor.
I managed to filter and go through the structure of my array, but the result is a single element, and not all the elements matching the criteria.
Query :
var json = Tests1VerlIR.find({}, {fields: {entries: {$elemMatch: {'payload.id': {$eq: this.params._id}} } } }).fetch();
this.response.setHeader('Content-Type', 'application/json');
this.response.end(JSON.stringify(json));
Data Structure :
{"entries":
[{"method":"POST",
"source":"ex",
"path":"/ex",
"time":1464615406900,
"payload":
{"slot_frame_number":"4",
"slot_HTUTemp":"2306",
"data":"0400f008561655270209a314",
"slot_BMEPres":"10069",
"slot_HTUHumi":"5283",
"slot_BMETemp":"2288",
"time":"1464615404",
"device":"79",
"slot_BMEHumi":"5718",
"signal":"7.22",
"id":"2"},
"_id":"574c41ee578d01af3664cbaf"},
{"method":"POST",
"source":"ex",
"path":"/ex",
"time":1464615406900,
"payload":
{"slot_frame_number":"4",
"slot_HTUTemp":"2306",
"data":"0400f008561655270209a314",
"slot_BMEPres":"10069",
"slot_HTUHumi":"5283",
"slot_BMETemp":"2288",
"time":"1464615404",
"device":"79",
"slot_BMEHumi":"5718",
"signal":"7.22",
"id":"2"},
"_id":"574c41ee578d01af3664cbaf"}, {...}]}
Response :
[
{
"_id":
{
"_str": "576155d7a605348159cd1f1a"
},
"entries":
[
{
"method": "POST",
"source": "ex",
"path": "/ex",
"time": 1464615406900,
"payload":
{
"slot_frame_number":"4",
"slot_HTUTemp":"2306",
"data":"0400f008561655270209a314",
"slot_BMEPres":"10069",
"slot_HTUHumi":"5283",
"slot_BMETemp":"2288",
"time":"1464615404",
"device":"79",
"slot_BMEHumi":"5718",
"signal":"7.22",
"id":"2"
},
"_id": "574c41ee578d01af3664cbaf"
}
]
}
]
You cannot return multiple elements of an array matching your criteria in any form of a basic .find() query. To match more than one element you need to use the .aggregate() method instead.
refer this link.
Tests1VerlIR.aggregate([
{ "$match": { "entries.payload.id": "2" } },
// Unwind the array to denormalize
{ "$unwind": "$entries" },
// Match specific array elements
{ "$match": { "entries.payload.id": "2" } },
// Group back to array form
{ "$group": {
"_id": "$_id",
"entries": { "$push": "$entries" }
}}
])
Solution :
var json = Tests1VerlIR.aggregate({"$unwind": "$entries"}, {$match: {'entries.payload.id': this.params._id} });
Related
How can i filter 'array' of object ids inside of mongo document, so that it doesn't include "60ba4ca6f170dsfa234", and will update document. Thank you!
{
"_id" : ObjectId("60bfdae011abd61c3eec"),
"array" : [
ObjectId("60ba4ca6f170dsfa234"),
ObjectId("60bd240df30ead9293d")
}
You can make use of the $project pipeline stage with the $filter option.
db.collection.aggregate([
{
"$project": {
"array": {
"$filter": {
"input": "$array",
"cond": {
"$ne": [
"$$this",
{ // <-- Ignore this block and pass value directly you are passing `ObjectId` directly drom driver and conversion from string is not required
"$toObjectId": "60407d54a8204c86a62bf231" // <-- ObjectId you want to be removed from the array
}
],
},
}
},
},
},
{
"$merge": {
"into": "collection", // <-- Change to source collection name where the changes has to be updated
"on": "_id",
"whenMatched": "merge",
},
},
])
Mongo Playground Sample Execution
Consider the following document format which has an array field tasks holding embedded documents
{
"foo": "bar",
"tasks": [
{
"status": "sleep",
"id": "1"
},
{
"status": "active",
"id": "2"
}
]
}
There exists a partial index on key tasks.id
{
"v": 2,
"unique": true,
"key": {
"tasks.id": 1
},
"name": "tasks.id_1",
"partialFilterExpression": {
"tasks.id": {
"$exists": true
}
},
"ns": "zardb.quxcollection"
}
The following $elemMatch query with multiple conditions on the same array element
db.quxcollection.find(
{
"tasks": {
"$elemMatch": {
"id": {
"$eq": "1"
},
"status": {
"$nin": ["active"]
}
}
}
}).explain()
does not seem to use the index
"winningPlan": {
"stage": "COLLSCAN",
"filter": {
"tasks": {
"$elemMatch": {
"$and": [{
"id": {
"$eq": "1"
}
},
{
"status": {
"$not": {
"$eq": "active"
}
}
}
]
}
}
},
"direction": "forward"
}
How can I make the above query use the index? The index does seem to be used via dot notation
db.quxcollection.find({"tasks.id": "1"})
however I need the same array element to match multiple conditions which includes the status field, and the following does not seem to be equivalent to the above $elemMatch based query
db.quxcollection.find({
"tasks.id": "1",
"tasks.status": { "$nin": ["active"] }
})
The way the partial indexes work is it uses the path as a key. With $elemMatch you don't have the path explicitly in the query. If you check it with .explain("allPlansExecution") it is not even considered by the query planner.
To benefit from the index you can specify the path in the query:
db.quxcollection.find(
{
"tasks.id": "1",
"tasks": {
"$elemMatch": {
"id": {
"$eq": "1"
},
"status": {
"$nin": ["active"]
}
}
}
}).explain()
It duplicates part of the elemMatch condition, so the index will be used to get all documents containing tasks of specific id, then it will filter out documents with "active" tasks at fetch stage. I must admit the query doesn't look nice, so may be add some comments to the code with explanations.
I have somewhat large data being stored under one document, it has some rough structure as following:
{
"_id": "rPKzOqhVQfCwy2PzRqvyXA",
"name": "test",
"raw_data": [
{},
...,
{}
],
"records": [
{
"_id": "xyz_1", // customer generated id
...other data
},
{
"_id": "xyz_2", // customer generated id
...other data
},
{},
{},
...
]
}
Now there can be 1000s of records in the document that I need to store from an imported file and each record will have it's own id (programmatically generated). The use case is, after saving this file user wants to do some processing on selected records only (i.e, with id xyz_1, xyz_2).
There are a lot of other data can be stored under this single document and I'm not interested to pull all of them while the above use case.
How do I query this document so that I can get the output such as the following:
[
{
"_id": "xyz_1", // customer generated id
...other data
},
{
"_id": "xyz_2", // customer generated id
...other data
}
]
You need to run $unwind and $replaceRoot:
db.collection.aggregate([
{ $unwind: "$records" },
{ $replaceRoot: { newRoot: "$records" } }
])
as per #mickl's suggestion, my solution is to achieve the output as follows:
db.collection.aggregate([
{ $unwind: "$records" },
{ $replaceRoot: { newRoot: "$records" } },
{ $match: { "_id": { $in: ["xyz_1", "xyz_2"] } } },
])
Update
I think the above solution will go through each document and replace root in each of them, then do match query.
I wanted to search for records from one parent document only, not from all parent documents within the collection. My concern was it should not target other parent documents within a collection so I ended up with the solution, as follows:
db.collection.aggregate([
{ "$match": { "_id": parent_doc_id } },
{ "$unwind": "$records" },
{ "$match": { "records._id": { "$in": ["xyz_1", "xyz_2"] } } },
{ "$group": { "_id": "$_id", "records": { "$push": "$records" } } },
{ "$limit": 1 },
])
I am trying to match a record if one value is in an array of the document
OR other criteria are met. I don't get an error, but I get no records returned.
Any help appreciated (I have looked at other posts regarding arrays and aggregation - I could not get them to work either - so this is probably a duplicate, sorry)
Data:
_id:5c93f908aa338366bd00b966
name:"Davidson"
type:["G"]
_id:5c93f908aa338366bd00b977
name:"Robertson"
type:["G","R"]
Query:
thing.aggregate([{
$match: {
$or: [
{
"name": {
$regex: "xx",
$options: "i"
}
},
{
"type": {
$elemMatch: {
"type": "G"
}
}
}
]
}
}])
You don't need $elemMatch:
db.col.aggregate([{
$match: {
$or: [
{
"name": {
$regex: "xx",
$options: "i"
}
},
{
"type": "G"
}
]
}
}])
it checks if type array contains any "G"
I have a collection in MongoDB which has a field called "geometry" with latitude and longitude like this :
{
"abc":"xyz",
"geometry" : [
{
"lat" : 45.0,
"lng" : 25.0
},
{
"lat" : 46.0,
"lng" : 26.0
}
]
}
I want to convert the field geometry into something like this, to be compliant with the GeoJSON format:
{
"abc":"xyz",
"geometry": {
"type": "LineString",
"coordinates": [
[
25.0,
45.0
],
[
26.0,
46.0
]
]
}
}
The operation essentially involves taking an array of objects with two K/V pairs and pick only the values and store them as array of arrays(with the order reversed- so value of "lng" comes first).
My failed attempts:
I tried using an aggregate and tried to project the following:
"geometry": {"type":"LineString", "coordinates":["$points.lng","$points.lat"] }
which gave me a result similar to:
"geometry": {
"type": "LineString",
"coordinates": [
[
25.0,
26.0
],
[
45.0,
46.0
]
]
}
I've tried working with this and modifying data record by record, but the results are not consistent. And, I'm trying to avoid going through every record and changing the structure one by one. Is there a way to do this efficiently ?
You would think that the following code should theoretically work:
db.collection.aggregate({
$project: {
"abc": 1, // include the "abc" field in the output
"geometry": { // add a new geometry sub-document
"type": "LineString", // with the hardcoded "type" field
"coordinates": {
$map: {
"input": "$geometry", // transform each item in the "geometry" array
"as": "this",
"in": [ "$$this.lng", "$$this.lat" ] // into an array of values only, ith "lng" first, "lat" second
}
}
}
}
}, {
$out: "result" // creates a new collection called "result" with the transformed documents in it
})
However, the way MongoDB works at this stage as per SERVER-37635 is that the above query results in a surprising output where the coordinates field contains the desired result several times. So the following query can be used to generate the desired output instead:
db.collection.aggregate({
$addFields: {
"abc": 1,
"geometry2": {
"type": "LineString",
"coordinates": {
$map: {
"input": "$geometry",
"as": "this",
"in": [ "$$this.lng", "$$this.lat" ]
}
}
}
}
}, {
$project: {
"abc": 1,
"geometry": "$geometry2"
}
}, {
$out: "result"
})
In the comments section of the JIRA ticket mentioned above, Charlie Swanson mentions another workaround which uses $let to "trick" MongoDB into interpreting the query in the desired way. I re-post it here (note that it's missing the $out part):
db.collection.aggregate([
{
$project: {
"abc": 1,
"geometry": {
$let: {
vars: {
ret: {
"type": "LineString",
"coordinates": {
$map: {
"input": "$geometry",
"as": "this",
"in": [
"$$this.lng",
"$$this.lat"
]
}
}
}
},
in: "$$ret"
}
}
}
}
])