I'm using MongoDB 2.6.6
I have these documents in a MongoDB collection and here is an example:
{ ..., "field3" : { "one" : [ ISODate("2014-03-18T05:47:33Z"),ISODate("2014-06-02T20:00:25Z") ] }, ...}
{ ..., "field3" : { "two" : [ ISODate("2014-03-18T05:47:33Z"),ISODate("2014-06-02T20:00:25Z") ] }, ...}
{ ..., "field3" : { "three" : [ ISODate("2014-03-18T05:47:39Z"),ISODate("2014-03-19T20:18:38Z") ] }, ... }
I would like the merge these documents in one field. For an example, I would like the new result to be as follows:
{ "field3", : { "all" : [ ISODate("2014-03-18T05:47:39Z"),ISODate("2014-03-19T20:18:38Z"),...... ] },}
I'm just not sure any more how to have that result!
Doesn't really leave much to go on here but you can arguably get the kind of merged result with mapReduce:
db.collection.mapReduce(
function() {
var field = this.field3;
Object.keys(field).forEach(function(key) {
field[key].forEach(function(date) {
emit( "field3", { "all": [date] } )
});
});
},
function (key,values) {
var result = { "all": [] };
values.forEach(function(value) {
value.all.forEach(function(date) {
result.all.push( date );
});
});
result.all.sort(function(a,b) { return a.valueOf()-b.valueOf() });
return result;
},
{ "out": { "inline": 1 } }
)
Which being mapReduce is not exactly in the same output format given it's own restrictions for doing things:
{
"results" : [
{
"_id" : "field3",
"value" : {
"all" : [
ISODate("2014-03-18T05:47:33Z"),
ISODate("2014-03-18T05:47:33Z"),
ISODate("2014-03-18T05:47:39Z"),
ISODate("2014-03-19T20:18:38Z"),
ISODate("2014-06-02T20:00:25Z"),
ISODate("2014-06-02T20:00:25Z")
]
}
}
],
"timeMillis" : 86,
"counts" : {
"input" : 3,
"emit" : 6,
"reduce" : 1,
"output" : 1
},
"ok" : 1
}
Since the aggregation here into a single document is fairly arbitrary you could pretty much argue that you simply take the same kind of approach in client code.
At any rate this is only going to be useful over a relatively small set of data with next to the same sort of restrictions on the client processing. More than the 16MB BSON limit for MongoDB, but certainly limited by memory to be consumed.
So I presume you would need to add a "query" argument but it's not really clear from your question. Either using mapReduce or your client code, you are basically going to need to follow this sort of process to "mash" the arrays together.
I would personally go with the client code here.
Related
In a Typesctipt code, I would like to use a varible value in an aggregation pipeline in MongoDB; the problem is that the "keyToCheck" field is a variable that is set by the Typescript code and, therefore, can change based by many conditions.
Is there a way to expand the variable "keyToCheck"?
I have tried $$keyToCheck, $keyToCheck with no result (compilation errors).
Thanks.
...
const pipeline = [
{
$match: {
[this.countryOriginFieldName!]: {
$in: members
},
**keyToCheck**: {
$nin: dictionaryNotAbsoluteFieldList
}
}
},
...
UPDATE: try with this example:
var keyToCheck = "indicator";
var queryMatch = {"`$${keyToCheck}`": "US$millions"}
printjson(queryMatch);
db.getCollection("temp_collection").aggregate([
{
$match: queryMatch
},
{$project: {indicator: 1, value: 1}}
]
);
db.getCollection("temp_collection").insertMany([
{
"indicator" : "US$millions",
"value" : 1.0
},
{
"indicator" : "US$millions",
"value" : 2.0
},
{
"indicator" : "EUROmillions",
"value" : 3
}
]);
Desired output:
{
"indicator" : "US$millions",
"value" : 1.0
}
{
"indicator" : "US$millions",
"value" : 2.0
}
Query
the [keyToCheck] is to take the value of the variable, its not an array
here its assumed that you want to project also the keyToCheck, and not always project the indicator
var keyToCheck = "indicator";
db.getCollection("temp_collection").aggregate([
{
$match: {[keyToCheck]: "US$millions"}
},
{$project: {[keyToCheck]: 1, value: 1}}
]
);
This will work, key will be just a string,and in project also just a string.
You dont need $ or $$ with this query.
I have a document structure something along the lines of the following:
{
"_id" : "777",
"someKey" : "someValue",
"someArray" : [
{
"name" : "name1",
"someNestedArray" : [
{
"name" : "value"
},
{
"name" : "delete me"
}
]
}
]
}
I want to delete the nested array element with the value "delete me".
I know I can find documents which match this description using nested $elemMatch expressions. What is the query syntax for removing the element in question?
To delete the item in question you're actually going to use an update. More specifically you're going to do an update with the $pull command which will remove the item from the array.
db.temp.update(
{ _id : "777" },
{$pull : {"someArray.0.someNestedArray" : {"name":"delete me"}}}
)
There's a little bit of "magic" happening here. Using .0 indicates that we know that we are modifying the 0th item of someArray. Using {"name":"delete me"} indicates that we know the exact data that we plan to remove.
This process works just fine if you load the data into a client and then perform the update. This process works less well if you want to do "generic" queries that perform these operations.
I think it's easiest to simply recognize that updating arrays of sub-documents generally requires that you have the original in memory at some point.
In response to the first comment below, you can probably help your situation by changing the data structure a little
"someObjects" : {
"name1": {
"someNestedArray" : [
{
"name" : "value"
},
{
"name" : "delete me"
}
]
}
}
Now you can do {$pull : { "someObjects.name1.someNestedArray" : ...
Here's the problem with your structure. MongoDB does not have very good support for manipulating "sub-arrays". Your structure has an array of objects and those objects contain arrays of more objects.
If you have the following structure, you are going to have a difficult time using things like $pull:
array [
{ subarray : array [] },
{ subarray : array [] },
]
If your structure looks like that and you want to update subarray you have two options:
Change your structure so that you can leverage $pull.
Don't use $pull. Load the entire object into a client and use findAndModify.
MongoDB 3.6 added $[] operator that facilitates updates to arrays that contain embedded documents. So the problem can be solved by:
db.test.update(
{ _id : "777" },
{$pull : {"someArray.$[].someNestedArray" : {"name":"delete me"}}}
)
As #Melkor has commented (should probably be an answer as itself),
If you do not know the index use:
{
_id: TheMainID,
"theArray._id": TheArrayID
},
{
$pull: {
"theArray.$.theNestedArray": {
_id: theNestedArrayID
}
}
}
From MongoDB 3.6 on you can use arrayFilters to do this:
db.test.update(
{ _id: "777" },
{ $pull: { "someArray.$[elem].someNestedArray": { name: "delete me" } } },
{ arrayFilters: [{ "elem.name": "name1"}] }
)
see also https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/index.html#update-all-documents-that-match-arrayfilters-in-an-array
Other example and usage could be like this:
{
"company": {
"location": {
"postalCode": "12345",
"Address": "Address1",
"city": "Frankfurt",
"state": "Hessen",
"country": "Germany"
},
"establishmentDate": "2019-04-29T14:12:37.206Z",
"companyId": "1",
"ceo": "XYZ"
},
"items": [{
"name": "itemA",
"unit": "kg",
"price": "10"
},
{
"name": "itemB",
"unit": "ltr",
"price": "20"
}
]
}
DELETE : Mongodb Query to delete ItemB:
db.getCollection('test').update(
{"company.companyId":"1","company.location.city":"Frankfurt"},
{$pull : {"items" : {"name":"itemB"}}}
)
FIND: Find query for itemB:
db.getCollection('test').find(
{"company.companyId":"1","company.location.city":"Frankfurt","items.name":"itemB"},
{ "items.$": 1 }
)
3.UPDATE : update query for itemB:
db.getCollection('test').update
(
{"company.companyId":"1","company.location.city":"Frankfurt","items.name":"itemB"},
{ $set: { "items.$[].price" : 90 }},
{ multi: true });
I have a collection with following structure:
{
"_id" : "Pd2fl7xcT3iWEmpAafv4DA",
"slot" : 1,
"stat" : [
{
"unitStat" : "5"
"value" : 13
},
{
"unitStat" : "18",
"value" : 1.96
},
{
"unitStat" : "28",
"value" : 1373
},
{
"unitStat" : "41",
"roll" : 2,
"value" : 69
}
]
}
I want to get 5 sorted objects (by any unitStat type) for every slot.
In that moment, I can perform 6 calls to db, but it isn't a good idea.
I tried to use aggregation, but I can perform it only for one slot:
db.collection.aggregate(
{
`$match`: {
slot: 1,
secondaryStat: {
`$elemMatch`: {
unitStat:'5'
}
}
}
},
{
`$unwind`: `'$secondaryStat'`
},
{
`$match`: {
'secondaryStat.unitStat' : '5'
}
},
{
`$sort`: {
'secondaryStat.value': -1
}
},
{
`$limit`: 5
}
)
Can I find, for example top 5 sorted objects from 6 different slots?
The following query can get us the expected output:
db.collection.aggregate([
{
$unwind:"$stat"
},
{
$match:{
"stat.unitStat":"5"
}
},
{
$sort:{
"slot":1,
"stat.value":1
}
},
{
$group:{
"_id":"$slot",
"slot":{
$first:"$slot"
},
"stat":{
$push:"$stat"
}
}
},
{
$project:{
"_id":0,
"slot":1,
"stat":{
$slice:["$stat",0,5]
}
}
}
]).pretty()
Aggregation stages details:
Stage I: Unwind the stat array
Stage II: Filter unitStat for any specified value. "5" in this case.
Stage III: Sort the data in ascending order on the basis of slot and
stat.value
Stage IV: Group back the data on the basis of slot and push all filtered stat into an array with name 'stat'
Stage V: Slice the stat array with the specified length. 5 in this
case.
I have a document in my mongo instance in below format,
{
"_id" : "08d4a242-08fb-07f7-46e5-8717a81d5b70",
"fname" : "john",
"created_date" : ISODate("2017-05-24T01:13:06.829Z"),
"customProp" : [
[
"customX","{\"some data related to X \"}"
],
[
"customY","{\"some data related to Y \"}"
],
[
"customZ","{\"some data related to Z \"}"
]
]
}
the elements/values like "customX","customY" & "customZ" are not necessarily be in all documents. How to retrieve all the values in second element of "customProp" array, in this document it contains "customZ"?
I'm able to use following query to filter & find all the documents which are having "customZ" element,
db.getCollection('col1').find({$and : [{"customProp":{$elemMatch:{0:"customZ"}}}, {"created": { $gte: ISODate("2017-05-22T00:00:00.000Z") }}] },{"created":1}).limit(1) .pretty()
output:
{
"_id" : "08d4a242-08fb-07f7-46e5-8717a81d5b45",
"created" : ISODate("2017-05-24T01:13:06.829Z")
}
but finding a way to retrieve all the values in second element of array where the first value is "customZ".
expected result:
{
"_id" : "08d4a242-08fb-07f7-46e5-8717a81d5b45",
"created" : ISODate("2017-05-24T01:13:06.829Z"),
"customPro": ["customZ","{\"some data related to Z \"}"]
}
I'm fine if my query just returns
{
"{\"some data related to Z \"}"
}
Well it is a nested array, which is not a great idea but you are in fact matching the element with the $elemMatch expression, so you do get the position in the "outer" array of customProp, which allows you to project with the positional $ operator:
db.getCollection('coll1').find(
{
"customProp":{ "$elemMatch": { "0": "customZ" } },
"created_date": { "$gte": ISODate("2017-05-22T00:00:00.000Z") }
},
{ "created_date": 1, "customProp.$": 1 }
)
That yields the result:
{
"_id" : "08d4a242-08fb-07f7-46e5-8717a81d5b70",
"created_date" : ISODate("2017-05-24T01:13:06.829Z"),
"customProp" : [
[
"customZ",
"{\"some data related to Z \"}"
]
]
}
Where customProp is of course still in a nested array, but when processing the individual documents in python you can just access the property at the array index:
doc['customProp'][0][1]
Which of course returns the value:
'{"some data related to Z "}'
Same goes for JavaScript really, which is basically identical in syntax. As a shell example:
db.getCollection('coll1').find(
{
"customProp":{ "$elemMatch": { "0": "customZ" } },
"created_date": { "$gte": ISODate("2017-05-22T00:00:00.000Z") }
},
{ "created_date": 1, "customProp.$": 1 }
).map(function(doc) {
doc['customProp'] = doc['customProp'][0][1];
return doc;
})
And the output:
{
"_id" : "08d4a242-08fb-07f7-46e5-8717a81d5b70",
"created_date" : ISODate("2017-05-24T01:13:06.829Z"),
"customProp" : "{\"some data related to Z \"}"
}
And the positional $ projection here ensures there is only one element in the returned array, so the notation is always the same to extract from all document results. So you get the matched element from the database, and you extract the property through the code.
Also note that you do not need $and here since all the query arguments are already AND conditions. This is the MongoDB default, so you do not need to explicitly express it. See how much nicer this looks without it.
After performing some aggregation magic, I have arrived at this data:
{ "_id" : "5700edfe03fcdb000347bebb", "size" : 3, "count" : 2 }
{ "_id" : "5700edfe03fcdb000347bebf", "size" : 2, "count" : 2 }
Now, I want to eliminate all the entries where size is equal to count.
So I ran this aggregation instruction:
match3 = { "$match" : { "size" : { "$ne" : "count"} } }
But it doesn't eliminate anything and returns the two lines as it is.
I want the result to be just this one line as it is the only one where size is not equal to count:
{ "_id" : "5700edfe03fcdb000347bebb", "size" : 3, "count" : 2 }
You need to add a $redact stage to your aggregation pipeline:
{ "$redact": {
"$cond": [
{ "$eq": [ "$size", "$count" ] },
"$$PRUNE",
"$$KEEP"
]
}}
You can use the $where operator for this
db.collection.find({ $where: "this.size != this.count" })
db.collection.remove({ $where: "this.size != this.count" })
UPDATE:
After I got downvoted I decided to compare the 2 solutions.
Both use a COLLSCAN and both return the same results.
So please enlighten me what is so wrong about my solution? :)