I am struggling with what seems to a basic lookup in mongodb. I can't figure out what is going wrong after trying multiple combinations. I see that there are many questions on SO addressing it, but nothing works after trying all the answers.
Here's my users collection
Here's my items collection
Each item has a single owner which maps to the user's _id in users collection.
For my query, I am trying to fetch items with their corresponding users. This is the query
db.items.aggregate([
{
"$lookup" : {
localField : "owner",
from : "users",
foreignField : "_id",
as : "users"
}
}
])
It returns this -
I have tried different variations - such as
db.items.aggregate([
{
"$lookup" : {
localField : "owner.str",
from : "users",
foreignField : "_id.str",
as : "users"
}
}
])
which results in the array of users getting populated incorrectly (with all users!).
What am I doing wrong?
EDIT
here's the items collection
{
"_id": {
"$oid": "5b268395c176db1548bd92c2"
},
"title": "Item #1",
"description": "Item 1 Description",
"categories": [
{
"$ref": "categories",
"$id": {
"$oid": "5b268248c176db1548bd92af"
}
}
],
"status": "borrowed",
"image": "http://cdn.site.com/testimage.png",
"borrower": "5b2684a0c176db1548bd92d5",
"owner": {
"$ref": "users",
"$id": {
"$oid": "5aecc8012d3d947ba130800d"
}
}
}
{
"_id": {
"$oid": "5b2c2c4d3c70af2da07d1266"
},
"title": "Item 2",
"description": "Item 2 Description",
"categories": [
{
"$ref": "categories",
"$id": {
"$oid": "5b268248c176db1548bd92af"
}
}
],
"status": "Available",
"image": "http://cdn.site.com/testimage1.png",
"borrower": null,
"owner": {
"$ref": "users",
"$id": {
"$oid": "5b2684a0c176db1548bd92d5"
}
}
}
Here's the users collection :
{
"_id": {
"$oid": "5aecc8012d3d947ba130800d"
},
"email": "n.y#gmail.com",
"registeredOn": "20/10/2018 12:12:29 AM",
"avatarURL": "http://gravtar.com/12334",
"friends": []
}
{
"_id": {
"$oid": "5b2684a0c176db1548bd92d5"
},
"email": "j.v#gmail.com",
"registeredOn": "20/10/2018 12:12:29 AM",
"avatarURL": "http://gravtar.com/12334",
"friends": [],
"wishlist": [
{
"$ref": "items",
"$id": {
"$oid": "5b2831543c70af2da07d11da"
}
}
]
}
The localField and foreignField should be of the same data type. Your are DBRef and ObjectId respectively.
If you want to populate a items record with the corresponding user, I think you can use the populate option in mongoose find API
Sample code:
this.read = function(query, populate, onSuccess, onFailure, forcePull, sortQuery, ttl, limitValue, selectQuery) {
var scope = this;
logger.info(this.TAG, "Read operation execution started on ORM Object: "
+ modelName + " with query: " + JSON.stringify(query));
if (query) {
var queryObject = this._model.find(query);
if (populate) {
queryObject = queryObject.populate(populate);
}
if (selectQuery) {
queryObject = queryObject.select(selectQuery);
}
if (sortQuery) {
queryObject = queryObject.sort(sortQuery);
}
if (limitValue) {
queryObject = queryObject.limit(limitValue);
}
queryObject.exec(function(error, records) {
if (error) {
logger.error(scope.TAG, "Error while ORM read: " + JSON.stringify(error));
onFailure(error);
return;
}
logger.info(scope.TAG, "Record read successfully: " + JSON.stringify(records));
onSuccess(records);
});
}
}
You can pass populate as an array of strings like ['owner'] in your case
Related
With Mongoose, I would like to be able to get a list of trainings by exercise _id parameter.
My training collection looks like this :
[
{
"_id": "617420adb9a7d7e02d591416",
"workout": {
"exercises": [
{
"_id": "61742066b9a7d7e02d5913ea",
"name": "Exercise 1"
}
],
"name": "Workout 1",
"_id": "617420a1b9a7d7e02d591401"
},
},
{
"_id": "617420b5b9a7d7e02d59141f",
"workout": {
"exercises": [
{
"_id": "61742066b9a7d7e02d5913ea",
"name": "Exercise 2"
}
],
"name": "Workout 2",
"_id": "617420a1b9a7d7e02d591401"
},
},
{
"_id": "6174226830610e43b0f6a283",
"workout": {
"exercises": [
{
"_id": "6174225630610e43b0f6a267",
"name": "Exercise 2"
}
],
"name": "Workout 3",
"_id": "6174226030610e43b0f6a275"
},
}
]
Based on MongoDB documentation I tried something like this :
this.trainingModel.find({
'workout.exercises._id': '61742066b9a7d7e02d5913ea',
});
This code returns an empty array. I was expecting to have two trainings (the two first of collection).
I also did this :
this.trainingModel.find({
'workout.exercises': { _id: '61742066b9a7d7e02d5913ea' },
});
But I also get an empty array as response.
I have just found the solution. As #turivishal said I had first to convert the string id to ObjectId. But then I also had to change the query like this :
this.trainingModel.find({
'workout.exercises._id': new Types.ObjectId('my_string_id'),
});
I have a personal project and I am new in MongoDB aggregate function.
I got the correct result querying two collections using $lookup but I want to modify the result and get my desired output.
Here is my Sample Collection
"users": [
{
"_id" : "60499e72b60a8819c4e0fa03"
"LastName": "Doe"
"FirstName" "John"
}
]
"userdocuments": [
{
"_id": "61025b9f890bacbe8f450f6a",
"userid": 60499e72b60a8819c4e0fa03,
documents: {
documentOne: {
documentTitle: "This is Document One"
},
documentTwo: {
documentTitle: "This is Document Two"
},
documentThree: {
documentTitle: "This is Document Three"
},
}
}
]
I'm getting a correct result like this using $lookup and $unwind
https://mongoplayground.net/p/k1lmvJg-tB7
[
{
"FirstName": "John",
"LastName": "Doe",
"_id": "60499e72b60a8819c4e0fa03",
"documents": {
"_id": "61025b9f890bacbe8f450f6a",
"userid": "60499e72b60a8819c4e0fa03"
"documents": {
"documentOne": {
"documentTitle": "This is Document One"
},
"documentThree": {
"documentTitle": "This is Document Three"
},
"documentTwo": {
"documentTitle": "This is Document Two"
}
},
}
}
]
But I want my output like this. And I want to project userid and _id so I can get my desired output. Thanks you so much for helping
[
{
"FirstName": "John",
"LastName": "Doe",
"_id": "60499e72b60a8819c4e0fa03",
"documents": {
"documentOne": {
"documentTitle": "This is Document One"
},
"documentThree": {
"documentTitle": "This is Document Three"
},
"documentTwo": {
"documentTitle": "This is Document Two"
}
},
}
}
]
You can select first element from documents array by $arrayElemAt or $first(v4.4) operators after lookup stage,
{
$addFields: {
Documents: {
$arrayElemAt: ["$Documents.documents", 0]
}
}
}
Playground
We have the following problem
Given are the tables and fields
Offer
OfferId
State
Article
OfferId
ArticleId
NetPrice
GrossPrice
VatRate
Example-data:
Offer-Collection
{
"_id": "1",
"State": "INITIAL",
"_class": "com.example.dto.OfferData"
}
{
"_id": "2",
"State": "COMPLETED",
"_class": "com.example.dto.OfferData"
}
Article-Collection
{
"_id": {
"$oid": "a"
},
"Description": "asdf",
"NetPrice": "100",
"GrossPrice": "116",
"VatRate": "16",
"OfferId": "1",
"_class": "com.example.dto.Article"
}
{
"_id": {
"$oid": "b"
},
"Description": "my description",
"NetPrice": "100",
"GrossPrice": "119",
"VatRate": "19",
"OfferId": "1",
"_class": "com.example.dto.Article"
}
{
"_id": {
"$oid": "c"
},
"Description": "my description",
"NetPrice": "100",
"GrossPrice": "116",
"VatRate": "16",
"OfferId": "2",
"_class": "com.example.dto.Article"
}
Now we have to update all articles belonging to an offer with the state "initial" in the following way: if the VatRate is equal to 16 than it must be updated to 19 AND the GrossPrice must be recalculated from the existing NetPrice.
The result should be: the article with _id = "a" and VatRate = 16 for OfferId = 1 (State = INITIAL) should have VatRate = 19 and GrossPrice = 119. The fields should be updated and persisted in the original MongoDB collection.
Can we do this only with Mongo-shell? Our Version is 3.6.
Our tries:
We have played around with .aggregate, $lookup, $match and $project but without much luck. It's the first time we are using the Mongo-shell.
db.getCollection("Offers").aggregate([{
$lookup:{
from:"Articles",
localField:"OfferId",
foreignField:"OfferId",
as:"selected-articles"
}
},
{
$match: { "state": { "$eq": "INITIAL" } }
},
{
$project: { "articles": 1 }
}
]).forEach(...?)
$match your State condition
$lookup with Articles collection
$map to iterate loop of selected-articles array, check condition using $cond if VatRate is "16" then updated to 19 and recalculate GrossPrice as per NetPrice using $multiply before it convert NetPrice to integer because its in string type, back to merge objects with current objects using $mergeObjects
db.getCollection("Offers").aggregate([
{ $match: { State: { $eq: "INITIAL" } } },
{
$lookup: {
from: "Articles",
localField: "_id",
foreignField: "OfferId",
as: "selected-articles"
}
},
{
$addFields: {
"selected-articles": {
$map: {
input: "$selected-articles",
in: {
$mergeObjects: [
"$$this",
{
$cond: [
{ $eq: ["$$this.VatRate", "16"] },
{
VatRate: 19,
GrossPrice: {
$multiply: [{ $toInt: "$$this.NetPrice" }, 19]
}
},
{}
]
}
]
}
}
}
}
}
])
Playground
I'm trying to build a voting system where you can have X num of options to vote for on an entry.
The query I'm building now is when retrieving an entry, I would like to get the numbers of votes per option on an entry.
I have a very clear understanding of how I would do this in SQL but grasping to understand the concepts of aggregation, lookup, and group in MongoDB
The model looks like this:
Entries
{
"_id": "5fc2765938401a2308e18ac5",
"options": [
{
"name": "First Option"
"_id": "5fc2765938401a2308e18ac6",
},
{
"name": "Second Option"
"_id": "5fc2765938401a2308e18are",
},
{
"name": "Third Option"
"_id": "5fc2765938401a2308e18aef",
},
],
},
{
"_id": "5fc2766438401a2308e18ac8",
"options": [
{
"name": "Some other option"
"_id": "5fc2766438401a2308e18ac9",
},
{
"_id": "5fc2766438401a2308e18aca",
"name": "This is also an option"
}
],
}
Votes
{
"_id": "5fc2765938401a2308e18ac5",
"entryId": "5fc2765938401a2308e18ac6"
},
{
"_id": "5fc2765938401a2308e18aer",
"entryId": "5fc2765938401a2308e18are"
},
{
"_id": "5fc2765938401a2308e18ek",
"entryId": "5fc2765938401a2308e18ac6"
}
...
And I want the results of Entry to look like this.
{
"_id": "5fc2765938401a2308e18ac5",
"options": [
{
"name": "First Option"
"_id": "5fc2765938401a2308e18ac6",
"votes": 1,
},
{
"name": "Second Option"
"_id": "5fc2765938401a2308e18are",
"votes": 0,
},
{
"name": "Third Option"
"_id": "5fc2765938401a2308e18aef",
"votes": 5,
},
],
},
{
"_id": "5fc2766438401a2308e18ac8",
"options": [
{
"name": "Some other option"
"_id": "5fc2766438401a2308e18ac9",
"votes": 3,
},
{
"_id": "5fc2766438401a2308e18aca",
"name": "This is also an option"
"votes": 10,
}
],
}
$lookup to join votes collection, pass local field optoins._id and foreign field entryId
$project get options votes, $map to iterate loop of options array, $filter to get matching entryId records and $size to get count of element in return array, merge votes field and current object using $mergeObjects
db.entries.aggregate([
{
$lookup: {
from: "votes",
localField: "options._id",
foreignField: "entryId",
as: "votes"
}
},
{
$project: {
options: {
$map: {
input: "$options",
as: "a",
in: {
$mergeObjects: [
"$$a",
{
votes: {
$size: {
$filter: {
input: "$votes",
cond: { $eq: ["$$this.entryId", "$$a._id"] }
}
}
}
}
]
}
}
}
}
}
])
Playground
The values in the conditions should be consistence with the data in the collection. However, every time i make a new findOneAndUpdate with the same data, it creates new records in the collection.
The data in my collection looks like this
{
"_id": {
"$oid": "5bc2e6bea49c6200130e1efe"
},
"email": "c#c.com",
"password": "c",
"createdAt": {
"$date": "2018-10-14T06:48:30.159Z"
},
"signedToEvents": [
{
"eventSignedDate": {
"$date": "2018-10-15T18:42:37.879Z"
},
"_id": {
"$oid": "5bc4e0717287250013b66907"
},
"event": {
"$oid": "5b8ed4effb6fc013752b5e1e"
},
"isActive": true
}
],
"__v": 0
}
My call to looks like this:
User.findOneAndUpdate({_id : req.userId, "signedToEvents.event" : mongoose.Types.ObjectId(event._id)}, { $push : { 'signedToEvents': { event: event._id, isActive: true } } }, {upsert:true})
.exec()
.then(docs =>{
res.status(200).send(docs);
})
Please point me in the right direction