How to delete everything in an array except the first index? - mongodb

I have a collection people. It has an array field numbers, where each document has a varying number of elements in said array.
My goal is to keep the elements in the 0th index and recursively remove the rest. How could I go about doing this?
For example:
{_id: 1, numbers: [100, 200, 300]}, -> {_id: 1, numbers: [100]},
{_id: 2, numbers: [101, 201]}, -> {_id: 2, numbers: [101]}
{_id: 3, numbers: [102, 202, 400, 500]}, -> {_id: 3, numbers: [102]},

An alternative solution to #R2D2 is to use $slice. Unlike $first, $slice works for more than one element.
collection.updateMany(
{},
{ $push: { numbers: { $each: [], $slice: 1 } } }
);
You could also use $slice: -1 to start from the last element.
See this on Mongo Playground.

You can use the operator $first in update pipeline as follow:
db.collection.update({},
[
{
$addFields: {
numbers: {
$first: "$numbers"
}
}
}
],
{
multi: true
})
Explained:
Replace the "numbers" array in all documents with array with only the first element taken from "numbers"
Playground
If you want the "numbers" to be still of type array you can enclose the output from $first in square brackets [] as follow:
numbers: [{
$first: "$numbers"
}]
Playground 2
Btw , $first is alias of $arrayElemAt that you can use to extract other elements from the array ...
But afcourse the operation can be done via $slice as mentioned earlier by #Clashsoft

Related

MongoDB: Add field to all objects in array, based on other fields on same object?

I am fairly new to MongoDB and cant seem to find a solution to this problem.
I have a database of documents that has this structure:
{
id: 1
elements: [ {elementId: 1, nr1: 1, nr2: 3}, {elementId:2, nr1:5, nr2: 10} ]
}
I am looking for a query that can add a value nr3 which is for example nr2/nr1 to all the objects in the elements array, so that the resulting document would look like this:
{
id: 1
elements: [ {elementId: 1, nr1: 1, nr2: 3, nr3:3}, {elementId:2, nr1:5, nr2: 10, nr3: 2} ]
}
So I imagine a query along the lines of this:
db.collection.updateOne({id:1}, {$set:{"elements.$[].nr3": nr2/nr1}})
But I cant find how to get the value of nr2 and nr1 of the same object in the array.
I found some similar questions on stackoverflow stating this is not possible, but they were 5+ years old, so I thought maybe they have added support for something like this.
I realize I can achieve this with first querying the document and iterate over the elements-array doing updates along the way, but for the purpose of learning I would love to see if its possible to do this in one query.
You can use update with aggregation pipeline starting from MongoDB v4.2,
$map to iterate loop of elements
divide nr2 with nr1 using $divide
merge current object and new field nr3 using $mergeObjects
db.collection.updateOne(
{ id: 1 },
[{
$set: {
elements: {
$map: {
input: "$elements",
in: {
$mergeObjects: [
"$$this",
{ nr3: { $divide: ["$$this.nr2", "$$this.nr1"] } }
]
}
}
}
}
}]
)
Playground
db.collection.update(
{ id:1},
{ "$set": { "elements.$[elem].nr3":elements.$[elem].nr2/elements.$[elem].nr1} },
{ "multi": true }
);
I guess this should work

Mongo remove document if all elements in array match condition

Docs:
{
_id: 1,
items: [{thing: 5}, {thing: 7}]
}
{
_id: 2,
items: [{thing: 5}, {thing: 11}]
}
I would like to remove all docs from the collection above if all elements in array have "thing" < 10. IE for that case doc 1 should be removed, doc 2 should remain.
Is it possible with a query to find only docs where all elements in the array match with a $lt query?
I tried this:
db.mycollection.remove({items: {$all: [{$elemMatch: {thing: {$lt: 11}}}]}})
However that will remove all docs if any of the elements in the array match the condition.
Use double negative (De-Morgan law):
{items: {$not: {$elemMatch: {thing: {$gte: 11}}}}}

How to return elements of array matching criteria in MongoDB

I'm new to mongodb and I'm still trying to wrap my head around queries so forgive me if my question is too simple or have been answered elsewhere.
I have a collection like the one below
[
{"_id":1,
"data" :[{"a":1,"b":2},{"a":1,"b":3},{"a":2,"b":3},{"a":4,"b":1}]
},
{"_id":2,
"data" :[{"a":3,"b":2},{"a":2,"b":4},{"a":5,"b":3},{"a":7,"b":1}]
}
]
How can I write a query that looks inside the data array of the first document and returns all elements where "a" is equal to one.
Something like this is the expected output:
[{"a":1,"b":2},{"a":1,"b":3}]
This is my current attempt
db.myCollection.find({_id:1},{data: {$elemMatch: {a : 1}}})
But this gives me
{"_id": 1, "data":
[{"a":1,"b":2},{"a":1,"b":3}]
}
I don't need or want anything other than the results in "data".
Can someone give me a hand?
I'd also appreciate any pointers for sifting through an array in a document for elements matching more general conditions on a single document (ie for the example above finding the documents where a+b < 5, a > b, a>1 and b>2, a >3 or b<1, etc.)
Edit: I'm using mongodb version 4.2.6
$filter is the general purpose way to, well, filter arrays inside of docs. Expanding your input set to give a little more context and variety:
var r =
[
{
"_id": 0,
"other":6,
"data" :[{"a":1,"b":2,"c":"X"},
{"a":1,"b":3,"c":"Y"},
{"a":2,"b":3,"c":"Q"},
{"a":4,"b":1,"c":"Z"}]
},
{
"_id": 1,
"other":7,
"data" :[{"a":1,"b":2,"c":"A"},
{"a":1,"b":3,"c":"B"},
{"a":7,"b":7,"c":"C"},
{"a":1,"b":8,"c":"D"}]
}
];
db.foo.insert(r);
Then these two pipelines demo the versatility of $filter:
c = db.foo.aggregate([
{$project: {_id:false,
// Notice input is $data and output project is data; this
// means overwrite the old data array with the filtered array.
// Also: The double dollar is how we reference the "as"
// variable. Single dollar variables refer to the fields
// of the incoming doc; we show how to use $other in this
// example. $other remains constant for the doc as $filter
// marches down the $data array:
data: {$filter: {
input: "$data",
as: "z",
cond: { $lt: [ {$add:["$$z.a","$$z.b"]} , "$other" ]}
}}
}}
]);
c = db.foo.aggregate([
{$project: {_id:true,
data: {$filter: {
input: "$data",
as: "z",
cond: { $or: [ {$gt:["$$z.a",3]}, {$lt:["$$z.b",7]} ] }
}}
}}
]);
You have almost everything, you just forget the projection at end:
db.myCollection.find({_id:1},{data: {$elemMatch: {a : 1}}}, {_id: 0, data: 1})

MongoDB, right projection subfield [duplicate]

Is it possible to rename the name of fields returned in a find query? I would like to use something like $rename, however I wouldn't like to change the documents I'm accessing. I want just to retrieve them differently, something that works like SELECT COORINATES AS COORDS in SQL.
What I do now:
db.tweets.findOne({}, {'level1.level2.coordinates': 1, _id:0})
{'level1': {'level2': {'coordinates': [10, 20]}}}
What I would like to be returned is:
{'coords': [10, 20]}
So basically using .aggregate() instead of .find():
db.tweets.aggregate([
{ "$project": {
"_id": 0,
"coords": "$level1.level2.coordinates"
}}
])
And that gives you the result that you want.
MongoDB 2.6 and above versions return a "cursor" just like find does.
See $project and other aggregation framework operators for more details.
For most cases you should simply rename the fields as returned from .find() when processing the cursor. For JavaScript as an example, you can use .map() to do this.
From the shell:
db.tweets.find({},{'level1.level2.coordinates': 1, _id:0}).map( doc => {
doc.coords = doc['level1']['level2'].coordinates;
delete doc['level1'];
return doc;
})
Or more inline:
db.tweets.find({},{'level1.level2.coordinates': 1, _id:0}).map( doc =>
({ coords: doc['level1']['level2'].coordinates })
)
This avoids any additional overhead on the server and should be used in such cases where the additional processing overhead would outweigh the gain of actual reduction in size of the data retrieved. In this case ( and most ) it would be minimal and therefore better to re-process the cursor result to restructure.
As mentioned by #Neil Lunn this can be achieved with an aggregation pipeline:
And starting Mongo 4.2, the $replaceWith aggregation operator can be used to replace a document by a sub-document:
// { level1: { level2: { coordinates: [10, 20] }, b: 4 }, a: 3 }
db.collection.aggregate(
{ $replaceWith: { coords: "$level1.level2.coordinates" } }
)
// { "coords" : [ 10, 20 ] }
Since you mention findOne, you can also limit the number of resulting documents to 1 as such:
db.collection.aggregate([
{ $replaceWith: { coords: "$level1.level2.coordinates" } },
{ $limit: 1 }
])
Prior to Mongo 4.2 and starting Mongo 3.4, $replaceRoot can be used in place of $replaceWith:
db.collection.aggregate(
{ $replaceRoot: { newRoot: { coords: "$level1.level2.coordinates" } } }
)
As we know, in general, $project stage takes the field names and specifies 1 or 0/true or false to include the fields in the output or not, we also can specify the value against a field instead of true or false to rename the field. Below is the syntax
db.test_collection.aggregate([
{$group: {
_id: '$field_to_group',
totalCount: {$sum: 1}
}},
{$project: {
_id: false,
renamed_field: '$_id', // here assigning a value instead of 0 or 1 / true or false effectively renames the field.
totalCount: true
}}
])
Stages (>= 4.2)
$addFields : {"New": "$Old"}
$unset : {"$Old": 1}

Add subdocument array element to subdocument array element in mongoDB

Is this possible?
I have a collection C, with an array of attributes A1.
Each attribute has an array of subattributes A2.
How can I add a subdocument to a specific C.A1 subdocument ?
Here is an example.
db.docs.insert({_id: 1, A1: [{A2: [1, 2, 3]}, {A2: [4, 5, 6]}]})
If you know the index of the subdocument you want to insert, you can use dot notation with the index (starting from 0) in the middle:
db.docs.update({_id: 1}, {$addToSet: {'A1.0.A2': 9}})
This results in:
{
"A1" : [
{
"A2" : [
1,
2,
3,
9
]
},
{
"A2" : [
4,
5,
6
]
}
],
"_id" : 1
}
Yes, this is possible. If you post an example I can show you more specifically what the update query would look like. But here's a shot:
db.c.update({ A1: value }, { $addToSet: { "A1.$.A2": "some value" }})
I haven't actually tried this (I'm not in front of a Mongo instance right now) and I'm going off memory, but that should get you pretty close.
Yes, $push can be used to do the same. Try below given code.
db.c.update({ A1: value }, { $push: { "A1.$.A2": num }});