I have a DB with a set of fields f1,f2,f3, where f3 is an array. I would like to do an update operation such as this
db.collection.update ({f1:1},{$push:{f3:{$each:[f2's value],$slice:-2}}})
I searched on the internet and was not abole to find anything. I am not sure if this is even possible. Any help would be much appreciated.
EXAMPLE:
This is my set of documents:
doc1 = { name: "User1", score: "Good", History of scores: ["Good", "Bad", "Average", "Bad"] }
doc2 = { name: "User2", score: "Bad", History of scores: ["Good", "Average", "Average", "Bad"] }
doc3 = { name: "User3", score: "Good", History of scores: ["Good", "Good", "Average", "Good"] }
Now suppose we have to insert the corresponding data:
{name : "User1", score: "Good"}
I would like the document to update user1's history of scores so that doc1 becomes as follows:
doc1 = { name: "User1", score: "Good", History of scores: ["Bad", "Average", "Bad", "Good"] }
another of the same update should change doc1 to:
doc1 = { name: "User1", score: "Good", History of scores: ["Average", "Bad", "Good", "Good"] }
I hope now my question has become clearer. Thanks.
Try this:
> db.c.find()
{ "_id" : ObjectId("51c156d25a334e9347b576a7"), "name" : "User1", "score" : "Good", "scores" : [ "Good", "Bad", "Average", "Bad" ] }
> db.c.update({}, {$push: {scores:{$each:['111', '222'], '$slice': -4}}})
> db.c.find()
{ "_id" : ObjectId("51c156d25a334e9347b576a7"), "name" : "User1", "score" : "Good", "scores" : [ "Average", "Bad", "111", "222" ] }
btw, I there is a problem with this kind of updates: if new object is grater then previous in size, it cause moving this object to another location on disk(e.g. you pushed "Average" and popped "Bad"). Updates "in-place" is faster, you can preallocate space for objects on first insert, like so:
> db.c.insert({ "_id" : ObjectId("51c156d25a334e9347b576a7"), "name" : "<big_tmp_string>", "score" : "<big_tmp_string>", "scores" : [ "<big_tmp_string>", "<big_tmp_string>", "<big_tmp_string>", "<big_tmp_string>" ] })
> db.c.update({ "_id" : ObjectId("51c156d25a334e9347b576a7")}, {<your_real_obj>}
Now that update commands can contain pipelines as of MongoDB 4.2, something like this is possible.
db.collection.updateOne({ f1: 1 }, [{
$set: {
historyOfScores: {
$concatArrays: [
"$historyOfScores",
["$score"]
]
}
}
}, {
$set: {
score: 'Good'
}
}]
Related
Using Mongo 4.4
I'm looking to to lookups across collections and add a human readable value from the target collection to the source collection using a aggregate.
This works fine for individual values, but for some lookups the ObjectIds are in objects in arrays, and I can't get that work. I can pull all the values back, but not place the individual values in the array objects.
In this test case, I have a library database with a books collection and a subscribers collection. The subscribers have a checkouts entry with is an array of objects, containing a reference to a book, and the checkout date. I want to add the book title to each object in the array.
Test Database:
books collection:
[
{
"_id" : ObjectId("63208c9f0d97eff0cfbefde6"),
"title" : "There and back again",
"author" : "Bilbo Baggins",
"publisher" : "Middle Earth Books"
},
{
"_id" : ObjectId("63208cd10d97eff0cfbeff02"),
"title" : "Two Towers",
"author" : "JRR Tolkin",
"publisher" : "Dude Books"
},
{
"_id" : ObjectId("63208cf10d97eff0cfbeffa3"),
"title" : "Dune",
"author" : "Frank Herbert",
"publisher" : "Classic Books"
},
{
"_id" : ObjectId("63208d1d0d97eff0cfbf0087"),
"title" : "Old Man's War",
"author" : "John Scalzi",
"publisher" : "Old Man Books"
}
]
subscribers collection:
[
{
"_id" : ObjectId("63208c2e0d97eff0cfbefb46"),
"name" : "Tom",
"checkouts" : [
{
"bookId" : ObjectId("63208cd10d97eff0cfbeff02"),
"checkoutDate" : ISODate("2022-01-01T21:21:20.202Z")
},
{
"bookId" : ObjectId("63208d1d0d97eff0cfbf0087"),
"checkoutDate" : ISODate("2022-01-02T21:22:20.202Z")
}
],
"address" : "123 Somewhere"
},
{
"_id" : ObjectId("63208c4e0d97eff0cfbefc1f"),
"name" : "Bob",
"checkouts" : [],
"address" : "123 Somewhere"
},
{
"_id" : ObjectId("63208c640d97eff0cfbefc9a"),
"name" : "Mary",
"checkouts" : [],
"address" : "123 Somewhere Else"
}
Desired Output for user Tom:
{
"_id" : ObjectId("63208c2e0d97eff0cfbefb46"),
"name" : "Tom",
"checkouts" : [
{
"bookId" : ObjectId("63208cd10d97eff0cfbeff02"),
"checkoutDate" : ISODate("2022-01-01T21:21:20.202Z"),
"title" : "Two Towers"
},
{
"bookId" : ObjectId("63208d1d0d97eff0cfbf0087"),
"checkoutDate" : ISODate("2022-01-02T21:22:20.202Z"),
"title" : "Old Man's War"
}
],
"address" : "123 Somewhere",
}
Using this aggregate:
db.getCollection('subscribers').aggregate([
{$match: {_id: ObjectId("63208c2e0d97eff0cfbefb46") } },
{$lookup: {from: "books", localField: "checkouts.bookId", foreignField: "_id", as: "book_tmp_field" }},
{$addFields: { "checkouts.title": "$book_tmp_field.title"}},
{$project: { book_tmp_field: 0}}
])
This is the closest I can get:
{
"_id" : ObjectId("63208c2e0d97eff0cfbefb46"),
"name" : "Tom",
"checkouts" : [
{
"bookId" : ObjectId("63208cd10d97eff0cfbeff02"),
"checkoutDate" : ISODate("2022-01-01T21:21:20.202Z"),
"title" : [
"Two Towers",
"Old Man's War"
]
},
{
"bookId" : ObjectId("63208d1d0d97eff0cfbf0087"),
"checkoutDate" : ISODate("2022-01-02T21:22:20.202Z"),
"title" : [
"Two Towers",
"Old Man's War"
]
}
],
"address" : "123 Somewhere"
}
Before performing the lookup, you should UNWIND the checkouts array. After all the processing is done, group the documents, to obtain the checkouts in the array. Finally, project your desired output document. Like this:
db.subscribers.aggregate([
{
$match: {
_id: ObjectId("63208c2e0d97eff0cfbefb46")
}
},
{
"$unwind": "$checkouts"
},
{
$lookup: {
from: "books",
localField: "checkouts.bookId",
foreignField: "_id",
as: "book_tmp_field"
}
},
{
$addFields: {
"checkouts.title": "$book_tmp_field.title"
}
},
{
$project: {
book_tmp_field: 0
}
},
{
"$group": {
"_id": {
_id: "$_id",
address: "$address",
name: "$name"
},
"checkouts": {
"$push": "$checkouts"
}
}
},
{
"$replaceRoot": {
"newRoot": {
"$mergeObjects": [
"$_id",
{
checkouts: "$checkouts"
}
]
}
}
}
])
Here's the playground link.
I was practicing on mongoDB. Currently, this is my first attempt on NoSql databases. I have a generic movie collection(video_movieDetails). In it, there are "_id" "title" "year" "director" "writers" "countries". I am able to write queries like "Construct a query and return all movies where Ian McKellen or Elijah Woods played in, but not both in movies.
db.video_movieDetails.find({$or:[{$and:[{"actors":"Ian McKellen"},{"actors":{$ne:"Elijah Woods"}}]},{$and:[{"actors":"Elijah Woods"},{"actors":{$ne:"Ian McKellen"}}]}]})
Now I am trying to achieve,
Titles and number of movies of each movie director.(For aggregation)
Updating a director's name from "Tim Burton" to "T. Burton". Eventually, all of the names should be updated on corresponding movie. (Updating)
How may I achieve this two queries? Thank you.
With this setup:
db.foo.insert([
{"_id": 0,title: "Movie A", year: 2010, director: "D1", actors: ["A1","A2"] }
,{"_id": 1,title: "Movie B", year: 2010, director: "D1", actors: ["A1","A5","A6"]}
,{"_id": 2,title: "Movie C", year: 2010, director: "D2", actors: ["A4"]}
,{"_id": 3,title: "Movie D", year: 2012, director: "D3", actors: ["A7"]}
,{"_id": 4,title: "Movie E", year: 2015, director: "D3", actors: ["A7"]}
]);
c = db.foo.aggregate([
{$group: {_id: "$director", movies: {$push: "$title"}, count: {$sum:1}}}
]);
show(c, true);
// For update(), note the option multi:true. Without it, mongodb will update ONLY the
// first doc matching the query criteria, not ALL the docs.
rc = db.foo.update({director: "D3"},{$set: {"director":"T. Burton"}},{multi:true});
printjson(rc);
c = db.foo.aggregate([]);
show(c, true);
you get this:
{ "_id" : "D1", "movies" : [ "Movie A", "Movie B" ], "count" : 2 }
{ "_id" : "D2", "movies" : [ "Movie C" ], "count" : 1 }
{ "_id" : "D3", "movies" : [ "Movie D", "Movie E" ], "count" : 2 }
found 3
{ "nMatched" : 2, "nUpserted" : 0, "nModified" : 2 }
{
"_id" : 0,
"title" : "Movie A",
"year" : 2010,
"director" : "D1",
"actors" : [
"A1",
"A2"
]
}
{
"_id" : 1,
"title" : "Movie B",
"year" : 2010,
"director" : "D1",
"actors" : [
"A1",
"A5",
"A6"
]
}
{
"_id" : 2,
"title" : "Movie C",
"year" : 2010,
"director" : "D2",
"actors" : [
"A4"
]
}
{
"_id" : 3,
"title" : "Movie D",
"year" : 2012,
"director" : "T. Burton",
"actors" : [
"A7"
]
}
{
"_id" : 4,
"title" : "Movie E",
"year" : 2015,
"director" : "T. Burton",
"actors" : [
"A7"
]
}
The $match stage will work on arrays as if they were scalars. For example, to find all movies in which actor A1 or A4 appears:
db.foo.aggregate([
{$match: {"actors": {$in: ["A1","A4"]} }}
]);
A simple $lookup example:
db.foo2.insert([
{_id:0, location: "Spain"},
{_id:1, location: "Italy"},
{_id:2, location: "Italy"},
{_id:3, location: "USA"},
{_id:4, location: "Sweden"},
]);
c = db.foo2.aggregate([
{$match: {"location": "Italy"}}
,{$lookup: {from: "foo", localField: "_id", foreignField: "_id", as: "X"}}
// Lookup can produce a one-to-many relationship; thus, "X" will come back
// as an array. Since we know that the _id-to-_id relationship is 1:1, we
// can just grab the first element of X and reset X to be that. In this
// case, $addFields acts like an assignment expression "x = x + 1" where
// x is overwritten by a new x.
,{$addFields: {X: {$arrayElemAt:["$X",0]} }}
// At this point, if you really want, you can $project just the title and
// location or whatever you want. Sometimes it is more flexible just to
// return the whole shape. Comment out the next line as an experiment:
,{$project: {_id:true, location:true, title: "$X.title"}}
]);
yields:
{ "_id" : 1, "location" : "Italy", "title" : "Movie B" }
{ "_id" : 2, "location" : "Italy", "title" : "Movie C" }
I have 2 collections in MongoDB:
User: favourite_tags: array[] and Event: title: string, tags: array[], so my question is: how can i find an event title that has at least 1 of the favourites tag of a user?
For example the user "A" has in his favourites tags rock, dance and food, the events with tags are: marathon (run, sport), eating contest (food, drink) and general rock band concert (music, rock).
Excpected result after the find are:
concert, eating contest
I believe this can be done using $lookup (be sure to refer to the documentation matching your version of mongoDB).
In this answer, I used mongoDB v3.4.19
Sample data
db.getCollection('users').insert(
{
name: "bob",
favourite_tags: ["rock", "dance", "food"]
}
)
db.getCollection('events').insertMany([
{
title: "marathon",
tags: ["run", "sport"]
},
{
title: "eating contest",
tags: ["food", "drink"]
},
{
title: "general rock band concert",
tags: ["music", "rock"]
}
])
Query for events that have at least 1 matching user (includes matched users in results):
db.getCollection('events').aggregate([
{
$lookup: {
from: "users",
localField: "tags",
foreignField: "favourite_tags",
as: "users"
}
},
{
$match: {
users: {
$ne: []
}
}
}
])
Results:
{
"title" : "eating contest",
"tags" : [
"food",
"drink"
],
"users" : [
{
"name" : "bob",
"favourite_tags" : [
"rock",
"dance",
"food"
]
}
]
},
{
"title" : "general rock band concert",
"tags" : [
"music",
"rock"
],
"users" : [
{
"name" : "bob",
"favourite_tags" : [
"rock",
"dance",
"food"
]
}
]
}
Query for users that matches them to events (includes events they matched with):
db.getCollection('users').aggregate([
{
$lookup: {
from: "events",
localField: "favourite_tags",
foreignField: "tags",
as: "matchingEvents"
}
}
])
Results:
{
"name" : "bob",
"favourite_tags" : [
"rock",
"dance",
"food"
],
"matchingEvents" : [
{
"title" : "eating contest",
"tags" : [
"food",
"drink"
]
},
{
"title" : "general rock band concert",
"tags" : [
"music",
"rock"
]
}
]
}
I started playing with MongoDB recently and I created a small database for learning purposes.
I used this to get an array with items
> var myitems = db.items.find({ $or: [ {"title": "item 1"}, {"title": "item 2"}, {"title": "item 3"}] })
> myitems
{ "_id" : ObjectId("570a841a8b71efa49d08fdda"), "title" : "item 1", "date" : ISODate("2016-04-10T16:49:30.242Z") }
{ "_id" : ObjectId("570a850c8b71efa49d08fddb"), "title" : "item 2", "date" : ISODate("2016-04-10T16:53:32.554Z") }
{ "_id" : ObjectId("570a85128b71efa49d08fddc"), "title" : "item 3", "date" : ISODate("2016-04-10T16:53:38.554Z") }
I also have a list object
{
"_id": int,
"total_items": int,
"items": [
"item_id": int
]
}
I want to insert my items array into list collection entries. Something like this:
> db.lists.insert({ "total_items": 5, "items": { $addToSet: myitems} })
Of course it didn't work. And that's what I'm looking for. A way to get the IDs from my items list and use them to insert a new entry to lists.
How can I do that?
You're using the insert command when you should be using the update command.
db.lists.updateOne({
_id: "abc"
}, {
$addToSet: {
items: episodes
}
})
Full docs here.
This is my User schema:
var UserSchema = new Schema({
name:String,
groups: [String]
});
I want to get array of names of all users who are in x group.
For example data set:
[
{
"name":"a",
"groups":[
"1",
"2"
]
},
{
"name":"b",
"groups":[
"3x",
"4"
]
},
{
"name":"c",
"groups":[
"1",
"4"
]
},
{
"name":"d",
"groups":[
"2",
"3"
]
},
{
"name":"e",
"groups":[
]
}
]
I need results:
for group 1:
["a","c"]
for group 2:
["a","d"]
for group 3:
["b","d"]
for group 4:
["c"]
for group 5:
[]
Is it possible to write query like this? How to do it?
It is quiet simple, Just requires an aggregate operation with two stages.
$unwind by the groups field.
$group by the groups field
Code:
db.t.aggregate([
{$unwind:"$groups"},
{$group:{"_id":"$groups","names":{$addToSet:"$name"}}}
])
Sample Result:
{ "_id" : "3x", "names" : [ "b" ] }
{ "_id" : "4", "names" : [ "c", "b" ] }
{ "_id" : "2", "names" : [ "d", "a" ] }
{ "_id" : "3", "names" : [ "d" ] }
{ "_id" : "1", "names" : [ "c", "a" ] }