mongodb aggregation lookup using ObjectId - mongodb

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

Mongoose find query on nested objects returns empty array

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'),
});

How to query subdocuments using $lookup in mongodb?

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

Select data from two MongoDB tables and update the results

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

How can i display count of related documents on parent level?

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

Mongoose findOneAndUpdate creates new record every time - also when conditions are met?

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