Mongoose Inner Join - node.js

user collection
user : {
"_id" : md5random,
"nickname" : "j1",
"name" : "jany"
}
user : {
"_id" : md5random,
"nickname" : "j2",
"name" : "jenneffer"
}
friendship collection
friendship : {
"_id" : md5rand,
"nick1" : "j1",
"nick2" : "j2",
"adTime" : date
}
for example SQL
SELECT friendship.adTime, user.name
FROM friendship
INNER JOIN user ON
(user.nickname=friendship.nick1 or user.nickname=friendship.nick2)
Is there any way in the MONGO to get this SQL result?
I WANT GET RESULTS, i know mongo not supporting this request.
But what is this better solution? Any body can write an exeample for me?

MongoDB is n document-oriented, not a relational database. So it doesn't have a query language like sql. Therefore you should change you database schema.
Here is a example:
Define your schema. You can't save relations in mongodb like in sql databases. so add the friends directly to the user. to add attributes to these relations you have to create a new model (here: Friend).
var userSchema = mongoose.Schema({
nickname: String,
name: String,
friends: [{type: mongoose.Schema.Types.ObjectId, ref: 'Friend'}]
});
var friendSchema = mongoose.Schema({
user: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
addTime: Date
});
var User = mongoose.Model('User', userSchema);
var Friend = mongoose.Model('Friend', friendSchema);
Your query to get all addTimes's could look like this:
User.find().populate('friends').exec(function(err, users) {
if (err) throw err;
var adTimes = [];
users.forEach(function(user) {
user.friends.forEach(function(friend) {
adTimes.push(friend.adTime);
});
});
response.send(adTimes); // adTimes should contain all addTimes from his friends
});
NOTE: The above schema should work, but maybe you should use a relational (like MySQL) or graph database (like Neo4j) instead of a document-oriented like MongoDB.

Use $lookup, Performs a left outer join to an unsharded collection in the same database to filter in documents from the “joined” collection for processing. The $lookup stage does an equality match between a field from the input documents with a field from the documents of the “joined” collection.
To each input document, the $lookup stage adds a new array field whose elements are the matching documents from the “joined” collection. The $lookup stage passes these reshaped documents to the next stage.
For more detail click $lookup documentation

Related

Mongoose : How to find documents in which fields match an ObjectId or a string?

This is mongoose DataModel in NodeJs
product: {type: mongoose.Schema.Types.ObjectId, ref: 'products', required: true}
But in DB, this field is having multiple type of values in documents, have String and ObjectId
I'm querying this in mongoose
{
$or: [
{
"product": "55c21eced3f8bf3f54a760cf"
}
,
{
"product": mongoose.Types.ObjectId("55c21eced3f8bf3f54a760cf")
}
]
}
But this is only fetching the documents which have that field stored as ObjectId.
Is there any way that it can fetch all the documents having both type of values either String OR ObjectId?
Help is much appreciated. Thanks
There is a schema in Mongoose, so when you query a document, it will search it by this schema type. If you change the model's product type to "string", it will fetch only documents with string IDs.
Even if there is a way to fetch either a string OR ObjectId, it's smelly to me to have such inconsistency.
I've encountered the same problem, so the solution was to standardize all documents by running a script to update them.
db.products.find().forEach(function(product) {
db.products.update({ type: product.type},{
$set:{ type: ObjectId(data.type)}
});
});
The only problem I see there is if this type field is actually an _id field. _id fields in MongoDB are immutable and they can't be updated. If that is your case, you can simply create a new document with the same (but parsed) id and remove the old one.
This is what I did: (in Robomongo)
db.getCollection('products').find().forEach(
function(doc){
var newDoc = doc;
newDoc._id = ObjectId(doc._id);
db.getCollection('products').insert(newDoc);
}
)
Then delete all documents, which id is a string:
db.getCollection('products').find().forEach(
function(doc){
db.getCollection('products').remove({_id: {$regex: ""}})
}
)
There is another way to do this. If we Update the type to Mixed then it will fetch all the documents with each type, either String or ObjectId
Define this in your Schema
mongoose.Schema.Types.Mixed
Like
product: {type: mongoose.Schema.Types.Mixed, required: true}

Defining a map with ObjectId key and array of strings as value in mongoose schema

I'm facing a problem while creating Mongoose schema for my DB. I want to create a map with a objectId as key and an array of string values as its value. The closest that I can get is:
var schema = new Schema({
map: [{myId: {type:mongoose.Schema.Types.ObjectId, ref: 'MyOtherCollection'}, values: [String]}]
});
But somehow this is not working for me. When I perform an update with {upsert: true}, it is not correctly populating the key: value in the map. In fact, I'm not even sure if I have declared the schema correctly.
Can anyone tell me if the schema is correct ? Also, How can I perform an update with {upsert: true} for this schema?
Also, if above is not correct and can;t be achieved then how can I model my requirement by some other way. My use case is I want to keep a list of values for a given objectId. I don't want any duplicates entries with same key, that's why picked map.
Please suggest if the approach is correct or should this be modelled some other way?
Update:
Based on the answer by #phillee and this, I'm just wondering can we modify the schema mentioned in the accepted answer of the mentioned thread like this:
{
"_id" : ObjectId("4f9519d6684c8b1c9e72e367"),
... // other fields
"myId" : {
"4f9519d6684c8b1c9e73e367" : ["a","b","c"],
"4f9519d6684c8b1c9e73e369" : ["a","b"]
}
}
Schema will be something like:
var schema = new Schema({
myId: {String: [String]}
});
If yes, how can I change my { upsert:true } condition accordingly ? Also, complexity wise will it be more simpler/complex compared to the original schema mentioned in the thread?
I'd suggest changing the schema so you have one entry per myId,
var schema = new Schema({
myId : {type:mongoose.Schema.Types.ObjectId, ref: 'MyOtherCollection'},
values : [String]
})
If you want to update/upsert values,
Model.update({ myId : myId }, { $set : { values : newValues } }, { upsert : true })

Is it possible for Mongoose to automatically extract schemas from Mongodb?

I'm still learning Mongodb, Nodejs, and Mongoose, so please excuse my ignorance if this question lacks understanding.
I find it somewhat redundant that each Mongodb collection have to be dissected in Mongoose. Specifically, all the fields of each Mongodb collection and their types need to be stated in Mongoose's schema.
So if I have a collection that contains documents sharing the same fields, such as:
> db.people.find()
{ "_id" : ObjectId("1111"), "name" : "Alice", "age": 30 }
{ "_id" : ObjectId("2222"), "name" : "Bob", "age": 25 }
{ "_id" : ObjectId("3333"), "name" : "Charlie", "age": 40 }
The way that Mongoose+Nodejs connect to this Mongodb
var mongoose = require('mongoose');
var personSchema = new mongoose.Schema({
name : String,
age : Number
});
mongoose.model("Person", personSchema, 'people');
where the last line contains the collection name as the 3rd parameter (explained here).
Is it possible to have Mongoose automatically extract the schema somehow from a Mongodb collection for a collection that contains documents of identical fields (i.e. they would have the same schema)? So that we don't have to define the schema in Mongoose.
Mongoose does not currently have a way of automatically building a Schema and Model given an example document.
While a simple document to Schema tool could be written and it would handle some cases reasonably well, depending on the nature of the collections and documents in your database, it wouldn't accurately reflect various aspects of the data model.
For example, if you had two collections that were related:
var personSchema = Schema({
_id : Number,
name : String,
age : Number,
stories : [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});
and
var storySchema = Schema({
title : String
author : String
});
As you can see the stories field is an array of ObjectIds that are associated with the story collection. When stored in the MongoDB collection, it would be something like:
{
"_id" : ObjectId("52a1d3601d02442354276cfd"),
"name" : "Carl",
"age" : 27,
"stories" : [
ObjectId("52a1d33b1d02442354276cfc")
]
}
And stories:
{
"_id" : ObjectId("52a1d33b1d02442354276cfc"),
"title" : "Alice in Wonderland",
"author" : "Lewis Carroll"
}
As you can see, the stories array contains only an ObjectId without storing what it maps to (a document in the stories collection). One functionality of Mongoose that's lost without this connection being established in the schema is populate (reference).
Maybe more importantly, part of the benefit of using Mongoose is to have a declared schema. While it may be "NoSQL" and MongoDB allows documents to be schema-less, many of the drivers in fact encourage developers to have a schema as it helps enforce a consistent document structure in a collection. If you're doing "production" development, having a declared rathered than inferred schema just seems prudent to me. While you can use a design document, having a rigid Schema defined in source code makes it not only the design, but also helps to enforce the Schema from being inadvertently changed.
It's quite easy to declare a Schema in Mongoose and it only needs to be done once per application instance.
You can of course use the underlying driver for MongoDB on NodeJS which doesn't have schema support at all.

Mongoose findOne embedded document by _id

I am trying to push menus to the embedded document. But I am getting not defined findOne in restaurant. I just want to push some documents into the restaurant's menu categories. As you can see in the schema:
var RestaurantSchema = new mongoose.Schema({
contactTelphone : String,
address : String,
branchID : String,
email : String,
restaurantName : String,
userID : String,
menuCategory : [MenuCategorySchema]
});
var MenuCategorySchema = new mongoose.Schema({
menuCategory : String,
menuCategoryAlt : String,
sequence : Number,
menus : [MenuSchema],
restaurantInfo : { type: Schema.Types.ObjectId, ref: 'Restaurant' },
});
var MenuSchema = new mongoose.Schema({
foodName : String,
foodNameAlt : String,
picName : String,
price : String,
rating : Number,
menuSequence : Number,
category : { type: Schema.Types.ObjectId, ref: 'MenuCategory' },
});
exports.menuForMenuCategory = function(newData, callback)
{
console.log(newData);
var menuCategoryId = newData.menuCategoryId;
var restaurantId = newData.restaurantId;
var newMenu = new Menu({
foodName : newData.foodName,
foodNameAlt : newData.foodNameAlt,
picName : newData.picName,
price : newData.price,
cookingCategory : newCookingCategory,
dishSpecial : newDishSpeical
});
Restaurant.findOne( {'_id' : restaurantId }, function(err, restaurant){
if (!err) {
//Is it how to do this? It says "findOne not defined"
restaurant.findOne( "_id" : menuCategoryId, function(err, category){
category.push(newMenu);
});
}
});
}
Subdocuments have an .id() method so you can do this:
myModel.findById(myDocumentId, function (err, myDocument) {
var subDocument = myDocument.mySubdocuments.id(mySubDocumentId);
});
See http://mongoosejs.com/docs/subdocs.html for reference.
restaurant is just an object containing the result to your query. It does not have a findOne method, only Restaurant does.
Since, MenuCategory is just a sub-document of Restaurant, this will come pre-populated whenever you retrieve a restaurant. E.g.
Restaurant.findById(restaurantId, function(err, restaurant){
console.log(restaurant.menuCategory);
// Will show your array of Menu Categories
// No further queries required
});
Adding a new Menu Category is a matter of pushing a new MenuCategory instance to the menuCategory array and saving the restaurant. This means the new menu category is saved with the restaurant and not in a separate collection. For example:
Restaurant.findById(restaurantId, function(err, restaurant){
// I'm assuming your Menu Category model is just MenuCategory
var anotherMenuCategory = new MenuCategory({
menuCategory: "The name",
menuCategoryAlt: "Alternative name",
sequence: 42,
menus: []
});
restaurant.menuCategory.push(anotherMenuCategory);
restaurant.save(); // This will save the new Menu Category in your restaurant
});
Saving a menu to a menu category follows the same procedure since, according to your schema, Menus are embedded sub-documents within each MenuCategory. But note that you need to save the restaurant as its the restaurant collection that is storing all your menus and menu categories as sub-documents
Having answered your question (I hope) I should also point out your schema design should be rethought. Having sub-documents nested within sub-documents is arguably not a good idea. I think I can see where you're coming from - you're trying to implement a SQL-like many-to-one association within your schemas. But this isn't necessary with NoSQL databases - the thinking is somewhat different. Here are some links to some SO questions about efficient schema design with NoSQL databases:
MongoDB Schema Design - Many small documents or fewer large
documents?
How should I implement this schema in MongoDB?
MongoDB relationships: embed or reference?

mongoose distinct and populate with documents

I have the following model:
var followerSchema = new Schema({
id_follower: {type: Schema.Types.ObjectId, ref: 'Users'},
id_post: {type: Schema.Types.ObjectId, ref: 'Posts'}
});
I want to be able to find all posts for a list of followers. When I use find, it returns me of course multiple times the same post as multiple users can follow the same post.
So I tried to use distinct, but I have the feeling the "populate" does not work afterwards.
Here is my code:
followerModel
.distinct('id_post',{id_follower:{$in:followerIds}})
.populate('id_post')
.sort({'id_post.creationDate':1})
.exec(function (err, postFollowers) {
console.log(postFollowers);
})
It only returns me the array of the posts, and it is not populated.
I am new to mongoDB, but according to the documentation of mongoose, the "distinct" method should return a query, just as the "find" method. And on a query you can execute the "populate" method, so I don't see what I am doing wrong.
I also tried to use the .distinct() method of the query, so then my code was like this:
followerModel
.find({id_follower:{$in:followerIds}})
.populate('id_post')
.distinct('id_post')
.sort({'id_post.creationDate':1})
.exec(function (err, postFollowers) {
console.log(postFollowers);
})
In that case it works, but as in the documentation of mongoose you need to provide a callback function when you use the distinct method on a query, and so in my logs I get errors all over. A workaround would be to have a dummy callback function, but I want to avoid that...
Does anybody has an idea why the first attempt is not working? And if the second approach is acceptable by providing a dummy callback?
Would this be the right way considering the current lack of support in mongoose?
followerModel
.find({id_follower:{$in:followerIds}})
.distinct('id_post',function(error,ids) {
Posts.find({'_id':{$in : ids}},function(err,result) {
console.log(result);
});
});
You can simply use aggregate to group and populate the collection.
now we have the desired result
db.<your collection name>.aggregate([
{
$match: {<match your fields here>}
},
{
$group: {_id: <your field to group the collection>}
},
{
$lookup: {
from: "<your collection of the poupulated field or referenced field>",
localField: "<give the id of the field which yout want to populate from the collection you matched above cases>",
foreignField: "_id", //this references the id of the document to match the localField id in the from collection
as: 'arrayName', //<some name to the returned document, this is a single document array>
}
},
{
$project: {
//you really don't want the whole populated fields, you can select the fields you want
<field name>:
<1 or 0>, // 1 to select and 0 to not select
//you can add multiple fields here
//to select the fields that just returned from the last stage we can use
"arrayName._id": <1 or 0>,
}
}
])
//at last you can return the data
.then((data) =>{
console.log(data);
});
we have distinct() by $group and
populate() by $lookup
and we also select() by $project

Resources