MongoDB schema and structure - mongodb

I just started learning mongoDB and mongoose here.
Is it possible to have value as key in mongoDB? For example, I'd like to have a structure like this:
Person collection:
USA: {
'John Doe': { phone: '...', somethingElse: '...' },
'Jane Doe': { phone: '...', somethingElse: '...' },
},
Australia: {
'John Doe': { phone: '...', somethingElse: '...' },
'Jane Doe': { phone: '...', somethingElse: '...' },
},
England: {
'John Doe': { phone: '...', somethingElse: '...' },
'Jane Doe': { phone: '...', somethingElse: '...' },
}
I know it's a terrible example, and I understand alternatively we can store the documents like:
{_id: 1, name: 'John Doe', address: 'USA', phone: '...', ...},
{_id: 2, name: 'John Doe', address: 'Australia', phone: '...', ...},
{_id: 3, name: 'John Doe', address: 'England', phone: '...', ...},
I guess I'm just trying to understand if storing value as key is even possible here. And if it is, how do we define the schema with mongoose?

Theoretically you could use a schema like:
const testSchema = new Schema({
countries: {
type: Map,
of: {
type: Map,
of: Object,
},
},
});
taking advantage of Map type in mongoose, then you can assign your dynamic object to contries property.
Personally I believe that second approach you mentioned is a way better idea (unless you really have a good reason for using the first one).
First reason is that having dynamic key names makes querying difficult. Instead of simple .find({name: 'John Doe'}) you need to run complicated aggregation queries like here. Basically any traversing like counting people, filtering by phone etc will be painful with dynamic keys.
Second reason is that MongoDB document has 16MB limitation so gathering too many people means you can approach that limit.

Related

Can I edit multiple documents from an array of them in Mongo?

well...
I have a collection of users, one user looks like
{ userId: "someId", fullName: "AnyName", email: "anyEmail#mail.com"}
and of course Mongo adds one _id
all fine here, but a have an express server and I receive and array of existing users with a POST.
[
{ userId: "123", fullName: "name1 EDITED", email: "anyEmail1#mail.com"},
{ userId: "124", fullName: "name2 EDITED", email: "anyEmail2#mail.com"},
{ userId: "125", fullName: "name3 EDITED", email: "anyEmail3#mail.com"},
{ userId: "126", fullName: "name4 EDITED", email: "anyEmail4#mail.com"}
]
I want edit the existing user with the news values receive in the array but I don't know if I have to iterate and update each document or if I can pass the array of users to update all of them with a single query.
similar to what "insertMany" method does

Mongoose populate array and return all documents (even if populate wasn't found)

Just like the title suggests, is there a way to populate an array using mongoose and return the populated documents, along side the array items that failed to find an associated document.
Example, I have the following Model and example document. The document in question has two users (stored in array). One of the emails, is associated with a user in the database and the other is not. If I run virtual populate on this array, I am only given the one user document where the reference was found.
Here is my Model
const organizationSchema = new Schema({
name: {
type: String,
required: true
},
users: [String],
}, { timestamps: { createdAt: 'created_at' } });
organizationSchema.virtual('users_populated',{
ref: 'users',
localField: 'users',
foreignField: 'email'
});
Example Document
{
name: "Test Organization", users:["email1#gmail.com",
"email2#gmail.com"]
}
What i'm looking to do
If one of the user's doesn't exist, I would like it to return this:
[{_id: 109753097121092, name: 'steve', email: 'email1#gmail.com'}, "email2#gmail.com"]
Or even:
[{_id: 109753097121092, name: 'steve', email: 'email1#gmail.com'}, {email: "email2#gmail.com"}]
What i'm getting (notice the entry that failed to populate is missing:
[{_id: 109753097121092, name: 'steve', email: 'email1#gmail.com'}]

What's a good way to look up list of enumerated values?

I have a collection of case with a field named status (integer) whose valid values are 0, 1, 2, 4 and 8, representing "New", "WIP", "Solved", "Canceled" and "Closed" respectively.
So, in mongoose syntax, it might be like:
const caseSchema = new Schema({
createdOn: Date,
subittedBy: String,
status: Number,
...
});
const statusSchema = new Schema({
value: Number,
description: String
});
Is this a good way to organize the data? How do I make a query to retrieve cases with the status field properly filled with the description?
It is one way to do it sure. You could do the query by using $lookup. It would look something like this:
db.getCollection('<YourCasesColName>').aggregate([
{ $match : { 'status' : 1 } }, // or { $in: [1,2,3] },
{
$lookup: {
from: '<YourStatusColName>',
localField: 'status',
foreignField: 'value',
as: 'statusDoc',
}
}
])
Another way is to add a reference to the actual status via ObjectId so that instead of numbers in the cases you would be storing references to the actual Status objects and in this way have a better referential integrity. However you would still need to do similar query to get both in one shot. So here is what I am talking about:
const caseSchema = new Schema({
createdOn: Date,
subittedBy: String,
status: { type: mongoose.Schema.Types.ObjectId, ref: 'Status' },
// ^ now your status hows reference to exactly the type of status it has
});
const statusSchema = new Schema({
value: Number,
description: String
});
So the actual data would look like this:
// Statuses
[{
_id: <StatusMongoObjectID_1>,
value: 1,
description: 'New'
},{
_id: <StatusMongoObjectID_2>,
value: 2,
description: 'New'
}]
// Cases
[{
_id: <MongoObjectID>,
createdOn: '<SomeISODate>',
subittedBy: '<SomeString>',
status: <StatusMongoObjectID_1>
},
{
_id: <MongoObjectID>,
createdOn: '<SomeISODate>',
subittedBy: '<SomeString>',
status: <StatusMongoObjectID_2>
}]

Should I use separate collection or embed fields that I know won't be used for all models. MongoDB

Background:
I'm planning an app that will have 3 types of posts, for n number of games; Single-posts, team-posts, coach-posts. Now I'm not sure of the best Schema for a single type of post.
The posts of a certain type share a couple fundamental attributes, like: user_id, comments, status, etc. But the fields relevant to the game will be unique.
These are the two possibilities I'm considering:
1. Separate collection for each game:
As you can see the playerposts type requires different fields for each game but has a similar structure.
// game1_playerposts
{
_id: ObjectId(),
user_id: ObjectId(),
game: ObjectId(),
comments: [{
user_id: ObjectId(),
comment: String,
score: Number
}],
rank: {
name: String,
abbr: String,
img: String
},
roles: [String],
gamemode: [String]
}
// game2_playerposts
{
_id: ObjectId(),
user_id: ObjectId(),
game: ObjectId(),
comments: [{
user_id: ObjectId(),
comment: String,
score: Number
}],
level: {
name: String,
abbr: String,
img: String
},
champions: [String],
factions: [{
name: String,
abbr: String,
img: String
}]
}
2. One collection for all games:
This way I only need one collection, and will always only use the fields I need, and the rest would remain empty.
{
_id : ObjectId(),
user_id : ObjectId(),
game1 : {
game: ObjectId(),
rank: {
name: String,
  abbr: String,
img: String
},
roles: [String],
gamemodes: [String]
},
game2 : {
game: ObjectId(),
level: {
name: String,
  abbr: String,
img: String
},
champions: [String],
factions: {
name: String,
  abbr: String,
img: String
}
},
game_n {
...
},
comments : [{
user_id: ObjectId(),
comment: String,
score: Number
}],
}
What's better?
Which one of these options would be better suited? Performance is important, but I also want it to be simple to add to the Schema when we decide to add support for another game in the future.
MongoDB is schemaless.
I don't see why you have to have fields you know won't be used. Why not just have a separate document for each individual player post and that document will have the schema that relates to the type of post it is?
You can have in a single collection both of the documents that you have as examples under the "Separate collection for each game" header.
I have not worked with Mongoose, but if using it removes the benefits of MongoDB being schemaless, I don't think it would be as popular a tool as it is, so I think there's a way for it to work.

Reference field within same schema

Is it possible to reference field within same schema? See my example below. Or am I going about this wrong way?
var UserSchema = new mongoose.Schema ({
username: String,
password: String,
email: String,
foods: [{
name: String,
category: String,
ingredients: // how to reference values in the ingredients array?
}],
ingredients: [{
name: String,
category: String
}]
});
Short answer
This is a core MongoDB design decision: MongoDB relationships: embed or reference?
Storing references to objects, rather than independent copies of them, as you would do in a relational database is possible in MongoDB and often done, it just results in more and more complex queries when you need to look them up.
Long answer
If the goal is just to keep the definitions of ingredient schemas consistent, you can define a schema and use it twice. The ingredients will be stored as independent copies, e.g.
[{ username: 'bob',
ingredients: [ { name: 'Carrot', category: 'Vegetable' } , ...],
foods: [ { name: 'Salad', category: 'Lunch', ingredients: [ { name: 'Carrot', category: 'Vegetable'}, ...]}]
}, ...]
var IngredientSchema = new mongoose.Schema({
name: String,
category: String,
});
var UserSchema = new mongoose.Schema ({
username: String,
password: String,
email: String,
foods: [{
name: String,
category: String,
ingredients: [IngredientSchema] // brackets indicates it's an array,
}],
ingredients: [IngredientSchema]
});
Alternatively you can reference ingredients by objectId:
var UserSchema = new mongoose.Schema ({
username: String,
password: String,
email: String,
foods: [{
name: String,
category: String,
ingredients: [mongoose.Schema.Types.ObjectId] // IDs reference individual ingredients,
}],
ingredients: [IngredientSchema]
});
By defining IngredientSchema explicitly, each ingredient object gets its own ObjectId when it is declared. The upside to storing IDs of ingredients (rather than copies of ingredient objects) is more concise and consistent storage. The downside is there will be many more and more complex queries.
[{ username: 'bob',
ingredients: [ { _id: ObjectId('a298d9ef898afc98ddf'), name: 'Carrot', category: 'Vegetable' } , ...],
foods: [ { name: 'Salad', category: 'Lunch', ingredients: [ {$oid: 'a298d9ef898afc98ddf'}, ]}]
}, ...]
A better approach if you want to store references to Ingredients, may be to store Ingredients as its own first class collection. You'll still have many separate queries when you want to look up foods by ingredient, or ingredients by food, but the queries will be simpler.
var UserSchema = new mongoose.Schema ({
username: String,
password: String,
email: String,
foods: [{
name: String,
category: String,
ingredients: [mongoose.Schema.Types.ObjectId] // IDs reference individual ingredients,
}],
ingredients: [mongoose.Schema.Types.ObjectId]
});
if the goal is store normalized references to ingredients and search foods based on them, to quote another [SO post][1], "this is one of those cases where relational databases really shine"
See this SO post for querying subdocuments by Id:
Reading categories and number of articles in a single query
As one respondent notes, "this is one of those cases where relational databases really shine"