Get one element from the array of results by index - mongodb

So every user has 0-3 items in the database. I don't have their indexes, I sort them by creation date, from oldest to youngest. I was wondering if it is possible to get the element from result array by its index by native mongo/mongoose tools.
For example I have these 3 documents in DB for user theguy:
{ "_id" : 1, "name" : "theguy", otherdata: ["data 1", "data 2"] }
{ "_id" : 10, "name" : "theguy", otherdata: ["data 1", "data 2"] }
{ "_id" : 333, "name" : "theguy", otherdata: ["data 1", "data 2"] }
_id will be ObjectId
Then user will try to get some data. He inputs a number in the range from 1 to 3. There can be 0, 1, 2 or 3 entries in database under his name. The example above displays the situation when user has all 3 entries filled. But user doesn't care about all of them right now, he entered the index, he needs only second result out of these 3.
What works for me right now is this:
//user defined the index
index = 2;
//search all docs by this user, but get only ids
let usrlist = await User.find({"name": name}).distinct('_id');
//based on the ids array we can now request exact second document from the database:
//usrlist[index-1] or usrlist[1]
let exactusr = await User.findOne({"_id": usrlist[index-1]});
So the result for exactusr will be:
{ "_id" : 10, "name" : "theguy", otherdata: ["data 1", "data 2"] }
I try to minimize the load by getting only ids, instead of all 3 documents at once. Now the thing is, well, this doesn't look "nice". Getting an array of _id to create another query based on that doesn't seem optimal. And at the end, I don't even know what is better: to do 2 queries(as above) or do 1 query for all user documents and choose the one user needs by index. Documents may be kind of big, containing up to 12000 characters each.
So looking for native ways I found $slice, but I don't think it works with the result array, or I don't understand how.
My attempt of using $slice:
index = 2;
usr = await User.find({"name": name}, {$slice: [index-1, 1]});
Result:
[ {"_id" : 1}, {"_id" : 10}, {"_id" : 333} ]
Expected result:
{ "_id" : 10, "name" : "theguy", otherdata: ["data 1", "data 2"] }
Any ideas? Or other methods that I could make this work?

I created a collection called "sample" and inserted the sample data that you provided.
db.sample.aggregate([{$match : {"name": "theguy"}}]);
{ "_id" : 1, "name" : "theguy", "otherdata" : [ "data 1", "data 2" ] }
{ "_id" : 10, "name" : "theguy", "otherdata" : [ "data 1", "data 2" ] }
{ "_id" : 333, "name" : "theguy", "otherdata" : [ "data 1", "data 2" ] }
Initialize a variable index : var index=1;
Now if we consider the above data, the indexes of the three rows would be 0,1,2. If I want to retrieve the 2nd row with _id : 10, then the index is supposed to be 1. In that case, the aggregate query would look like :
db.sample.aggregate([
{ $match : {"name": "theguy"}},
{ $skip : index},
{ $limit : 1 }
]);
In case you want your index value to mean the position i.e. in this case the position is 2, then modify the query like :
var index=2;
db.sample.aggregate([
{ $match : {"name": "theguy"}},
{ $skip : index-1},
{ $limit : 1 }
]);
Try this solution & let us know, if it worked for you!

Related

Mongodb aggregate lookup return only one field of array

i have some collections for our project.
Casts collection contains movie casts
Contents collection contains movie contents
i want to run aggregate lookup for get information about movie casts with position type.
i removed collections details unnecessary fields.
Casts details:
{
"_id" : ObjectId("5a6cf47415621604942386cd"),
"fa_name" : "",
"en_name" : "Ehsan",
"fa_bio" : "",
"en_bio" : ""
}
Contents details:
{
"_id" : ObjectId("5a6b8b734f1408137f79e2cc"),
"casts" : [
{
"_id" : ObjectId("5a6cf47415621604942386cd"),
"fa_fictionName" : "",
"en_fictionName" : "Ehsan2",
"positionType" : {
"id" : 3,
"fa_name" : "",
"en_name" : "Director"
}
},
{
"_id" : ObjectId("5a6cf47415621604942386cd"),
"fa_fictionName" : "",
"en_fictionName" : "Ehsan1",
"positionType" : {
"id" : 3,
"fa_name" : "",
"en_name" : "Writers"
}
}
],
"status" : 0,
"created" : Timestamp(1516997542, 4),
"updated" : Timestamp(1516997542, 5)
}
when i run aggregate lookup with bellow query, in new generated lookup array only one casts contents If in accordance with above casts array value aggregate lookup should return two casts content with two type. in casts array value exists two type of casts, 1) writers and directors. but returned director casts content. _casts should contains two object not one object!
aggregate lookup query:
{$lookup:{from:"casts",localField:"casts._id",foreignField:"_id",as:"_casts"}}
result:
{
"_id" : ObjectId("5a6b8b734f1408137f79e2cc"),
"casts" : [
{
"_id" : ObjectId("5a6cf47415621604942386cd"),
"fa_fictionName" : "",
"en_fictionName" : "Ehsan2",
"positionType" : {
"id" : 3,
"fa_name" : "",
"en_name" : "Director"
}
},
{
"_id" : ObjectId("5a6cf47415621604942386cd"),
"fa_fictionName" : "",
"en_fictionName" : "Ehsan1",
"positionType" : {
"id" : 3,
"fa_name" : "",
"en_name" : "Writers"
}
}
],
"_casts" : [
{
"_id" : ObjectId("5a6cf47415621604942386cd"),
"fa_name" : "",
"en_name" : "Ehsan",
"fa_bio" : "",
"en_bio" : ""
}
],
"status" : 0,
"created" : Timestamp(1516997542, 4),
"updated" : Timestamp(1516997542, 5)
}
EDIT-1
finally my problem is solved. i have only one problem with this query, this query doesn't show root document fields. finally solve this problem. finally query exists in EDIT-2.
query:
db.contents.aggregate([
{"$unwind":"$casts"},
{"$lookup":{"from":"casts","localField":"casts._id","foreignField":"_id","as":"casts.info"}},
{"$unwind":"$casts.info"},
{"$group":{"_id":"$_id", "casts":{"$push":"$casts"}}},
])
EDIT-2
db.contents.aggregate([
{"$unwind":"$casts"},
{"$lookup":{"from":"casts","localField":"casts._id","foreignField":"_id","as":"casts.info"}},
{"$unwind":"$casts.info"},
{$group:{"_id":"$_id", "data":{"$first":"$$ROOT"}, "casts":{"$push":"$casts"}}},
{$replaceRoot:{"newRoot":{"$mergeObjects":["$data",{"casts‌​":"$casts"}]}}},
{$project:{"casts":0}}
]).pretty()
This is expected behavior.
From the docs,
If your localField is an array, you may want to add an $unwind stage
to your pipeline. Otherwise, the equality condition between the
localField and foreignField is foreignField: { $in: [
localField.elem1, localField.elem2, ... ] }.
So to join each local field array element with foreign field element you have to $unwind the local array.
db.content.aggregate([
{"$unwind":"$casts"},
{"$lookup":{"from":"casts","localField":"casts._id","foreignField":"_id","as":"_casts"}}
])
Vendor Collection
Items Collection
db.items.aggregate([
{ $match:
{"item_id":{$eq:"I001"}}
},
{
$lookup:{
from:"vendor",
localField:"vendor_id",
foreignField:"vendor_id",
as:"vendor_details"
}
},
{
$unwind:"$vendor_details"
},
{
$project:{
"_id":0,
"vendor_id":0,
"vendor_details.vendor_company_description":0,
"vendor_details._id":0,
"vendor_details.country":0,
"vendor_details.city":0,
"vendor_details.website":0
}
}
]);
Output
Your Casts collection shows only 1 document. Your Contents collection, likewise, shows only 1 document.
This is 1 to 1 - not 1 to 2. Aggregate is working as designed.
The Contents document has 2 "casts." These 2 casts are sub-documents. Work with those as sub-documents, or re-design your collections. I don't like using sub-documents unless I know I will not need to use them as look-ups or join on them.
I would suggest you re-design your collection.
Your Contents collection (it makes me think of "Movies") could look like this:
_id
title
releaseDate
genre
etc.
You can create a MovieCasts collection like this:
_id
movieId (this is _id from Contents collection, above)
castId (this is _id from Casts collection, below)
Casts
_id
name
age
etc.

mongoDB: Querying for documents that may have some specifics options

I'm quite new to mongodb and there is one thing I can't solve right now:
Let's pretend, you have the following document structure:
{
"_id": ObjectId("some object id"),
name: "valueName",
options: [
{idOption: "optionId", name: "optionName"},
{idOption: "optionId", name: "optionName"}
]
}
And each document can have multiples options that are already classified.
I'm trying to get all the documents in the collection that have, at least one, of the multiples options that I pass for the query.
I was trying with the operator $elemMatch something like this:
db.collectioName.find({"options.name": { $elemMatch: {"optName1","optName2"}}})
but it never show me the matches documents.
Can someone help and show me, what I'm doing wrong?
Thanks!
Given a collection which contains the following documents:
{
"_id" : ObjectId("5a023b8d027b5bd06add627a"),
"name" : "valueName",
"options" : [
{
"idOption" : "optionId",
"name" : "optName1"
},
{
"idOption" : "optionId",
"name" : "optName2"
}
]
}
{
"_id" : ObjectId("5a023b9e027b5bd06add627d"),
"name" : "valueName",
"options" : [
{
"idOption" : "optionId",
"name" : "optName3"
},
{
"idOption" : "optionId",
"name" : "optName4"
}
]
}
This query ...
db.collection.find({"options": { $elemMatch: {"name": {"$in": ["optName1"]}}}})
.. will return the first document only.
While, this query ...
db.collection.find({"options": { $elemMatch: {"name": {"$in": ["optName1", "optName3"]}}}})
...will return both documents.
The second example (I think) meeets this requirement:
I'm trying to get all the documents in the collection that have, at least one, of the multiples options that I pass for the query.

Update document if a field in last element in array does not equal value

I wonder how I should match a document by last element in an Array in mongodb document.
Say I want to update a specific document with new data if a field in last element of array is not equal some specific value.
I know that I can do this to check if a field in Array does not contain that value already:
myTable.update({ Thing: thisThing,
'myArray.Element': {$ne: parseInt(thisValue)} }, ...)
But how should one check that the last Element (myArray.Element) in myArray is not equal to thisValue?
Note that I want to do this with findand not aggregate.
Best Regards
Let's say we have collection names, looking like this:
/* 1 */
{
"_id" : ObjectId("58de74f8c1bb7f4256adf32c"),
"user" : "John",
"list_friends" : [
"Alice",
"Bob"
]
}
/* 2 */
{
"_id" : ObjectId("58de75d3c1bb7f4256adf32d"),
"user" : "Pop",
"list_friends" : [
"Eve",
"Oscar"
]
}
Now, let's say we want to change "user" field to "Updated" for all users whose last friend name is different than "Oscar" (in this case that is John). This query:
db.getCollection('names').update({$where: "this.list_friends[this.list_friends.length - 1] !== 'Oscar'"}, {"$set": {"user": "Updated"}})
modifies the collection and the final result is:
/* 1 */
{
"_id" : ObjectId("58de74f8c1bb7f4256adf32c"),
"user" : "Updated",
"list_friends" : [
"Alice",
"Bob"
]
}
/* 2 */
{
"_id" : ObjectId("58de75d3c1bb7f4256adf32d"),
"user" : "Pop",
"list_friends" : [
"Eve",
"Oscar"
]
}
I tested the solution using Mongo 3.2, I am not sure if it works for older versions.

Using $addToSet to update an array field using another array field

I should start with: I'm knew to MongoDB, and document-style databases in general.
I have a collection that looks something like this:
{
"_id" : ObjectId("554a5e72b16f31ff0894310e"),
"title" : "ABC",
"admins" : [
"personA",
"personB",
],
"email_address" : "ABC#mysite.com"
}
{
"_id" : ObjectId("554a5e72b16f31ff0894310f"),
"title" : "Junk Site",
"admins" : [
"personA",
"personB"
],
"email_address" : "garbage#mysite.com"
}
{
"_id" : ObjectId("554a5e72b16f31ff08943110"),
"title" : "Company Three Site",
"admins" : [
"personC"
"personD",
],
"email_address" : "company2plus1#mysite.com"
}
What I need to do, is append the admins list from Company One, to Company Three such that Company Three now has four admins (A, B, C, D).
I tried the following, because it seemed pretty straight forward to me - get the data from the origin and append to destination directly:
db.runCommand({
findAndModify : 'sites',
query : {'title' : 'Company Three Site'},
update : { '$addToSet' :
{'admins' :
db.projects.find({'title' : 'ABC'}, {'_id' : 0, 'admins' : 1}
}
}
})
However, this does not work correctly.
I am still trying to figure out ways I could do this directly, but questions...
1) Is this even possible by using single command, or do I need to split this up?
2) Does my train of logical thought make sense, or should I be doing this some other/easier way that is more conventional for MongoDB style databases?
db.projects.find actually returns a cursor, which you definitely don't want to add to your set. Since you know ahead of time that you will be only finding one value, you can get the properties out of the cursor specifically by using .next().admin -- but remember that this will only work with the first value returned from .find. Otherwise, I think you will have to use a loop.
$addToSet will also add the array as a whole, so instead you have to append multiple values using $each
All together:
db.runCommand({
findAndModify: 'sites',
query: {'title': 'Company Three Site'},
update: {
$addToSet: {
"admins": {
$each: db.projects.find(
{"title": "ABC"},
{"_id": 0, "admins": 1}
).next().admins
}
}
}
})
This is not possible with an atomic update. However, a workaround is to query the source collection using the find() method and use the cursor's forEach() method to iterate over the results, get the array and update the destination collection using the $addToSet operator and the $each modifier.
Let's demonstrate this with the above sample documents inserted to a test collection:
db.test.insert([
{
"title" : "ABC",
"admins" : [
"personA",
"personB"
],
"email_address" : "ABC#mysite.com"
},
{
"title" : "Junk Site",
"admins" : [
"personA",
"personB"
],
"email_address" : "garbage#mysite.com"
},
{
"title" : "Company Three Site",
"admins" : [
"personC",
"personD"
],
"email_address" : "company2plus1#mysite.com"
}
])
The following operation will add the admins array elements from company "ABC" to the company "Company Three Site" admin array:
db.test.find({"title" : "ABC"}).forEach(function (doc){
var admins = doc.admins;
db.test.update(
{"title" : "Company Three Site"},
{
"$addToSet": {
"admins": { "$each": admins }
}
},
{ "multi": true }
);
});
Querying the collection for the document with company "Company Three Site" db.collection.find({"title" : "Company Three Site"});
will yield:
/* 0 */
{
"_id" : ObjectId("554a7dc35c5e0118072dd885"),
"title" : "Company Three Site",
"admins" : [
"personC",
"personD",
"personA",
"personB"
],
"email_address" : "company2plus1#mysite.com"
}

MongoDB Query double nested documents in array

I was wondering how I can query from an embedded document inside an array. I have following structure:
{ "targetId" : 2, "metaData" : [ { "key" : "id", "value" : 1 }, { "key" : "name", "value" : "Parisa" }, { "key" : "img", "value" : { "imgid" : 1, "imgName" : "img1" } } ]
I could search simple key-values like key = id and value =1, but I could not search based on the values with embedded document e.g. key="img"
I tried following query but it does not work:
db.test.find({"metaData":{$elemMatch:{"key":"img", "value":{"imgid":1}}}})
Could you please help me!
I think the "value" part of your query is a little off. You need to put the document element in the criteria:
b.test.find({"metaData":{$elemMatch:{"key":"img", "value.imgid":1}}})