Creating With a more complex association structure in sequelize - javascript

I am going off this small example off the sequelize docs
https://sequelize.org/docs/v6/advanced-association-concepts/creating-with-associations/#belongsto--hasmany--hasone-association
and I am not 100% following, for our project, we have a table that has a one to many association, but also two, one to many, associations within a one to many (I know a bit hard to word that)
so Table 1 has a one to many to table 2 and table 2 has a one to many to table 3 and a one to many to table 4
and I am trying to understand how to translate that to the example in that site (url) above into something workable for myself.
I just dont fully understand how to do it.
for example, looking at the example,
include: [{
association: Product.User,
include: [ User.Addresses ]
}]
I am not sure what include means vs association means. Other examples on that page seem to use association in the same way as where include is used (I think). I also am not sure how this works with multiple layers as I described above vs this more simple example.
Now I know this is a bigger-ish type of create but we are wondering if its a pluasble thing to do vs multiple calls to create data for those other tables when creating a largr dataset vs just doing a single create like this exmaple says is possible :)
if anyone has any advice I would aapperciate it, thanks!

association option is just another alternative to indicate an associated model: you either use include and indicate both a model ans an alias OR use association and indicate an association which already stores an associated model and its alias.
As for 4 tables each of which in its turn is linked as one-to-many to the next one you just need to use nested include/association options and that's all. Also if you want to get all-in-one then don't forget to indicate separate: true on each level of include/association options otherwise a generated SQL query might have too many records and you'll get the out of memory error.
const allInOneItems = Table1.findAll({
include: [{
model: Table2,
separate: true,
include: [{
model: Table3,
separate: true,
include: [{
model: Table4,
separate: true,
}]
}]
}]
})

Related

Sequelize: Selecting distinct field values from multiple columns

I'm using sequelize and mysql2 and need to select all the distinct values from multiple columns.
For example I've a 'Title' and a 'Location' column.
At the moment this:
Job.findAll({
attributes: [
[Sequelize.fn('DISTINCT', Sequelize.col('title')), 'title']
]
})
works, returning all the job titles that are distinct. But if I add a second line for the location column I get a syntax error
Job.findAll({
attributes: [
[Sequelize.fn('DISTINCT', Sequelize.col('title')), 'title'],
[Sequelize.fn('DISTINCT', Sequelize.col('location')), 'location']
]
})
It feels like it should work, from what I've read in the docs at least - but I'm not exactly experienced or confident with sequelize, so any help would be appreciated.
Thanks
I haven't yet found a way to do this less-hackily, but you can exploit the fact that DISTINCT isn't really a function, but a statement and always operates on the entirety of the selected column set:
Job.findAll({
attributes: [
[Sequelize.fn('DISTINCT', Sequelize.col('title')), 'title'],
'location'
]
})
would generate SQL similar to
SELECT DISTINCT(`title`) AS `title`, `location` FROM `Jobs`;
but since DISTINCT is not a function, this is really the same as
SELECT DISTINCT (`title`) AS `title`, `location` FROM `Jobs`;
which does do what you want because parenthesis around column names are optional, that query up there would be the same as
SELECT DISTINCT `title`, `location` FROM `Jobs`;
Source: https://dzone.com/articles/sql-distinct-is-not-a-function (the article also talks about the DISTINCT ON PostgreSQL extension, if someone happens to need that too)
Please note that this feels very hacky and sort of fragile and could/might break if Sequelize ever decides to change the way functions are applied when generating the SQL, but it works in the version of Sequelize I use (5.22.3).

Filtering with search-index

I want to implement a full-text-search for *.epub-Files. Therefore I forked the epub-full-text-search module (https://github.com/friedolinfoerder/epub-full-text-search).
I will have many ebooks to search through, so I want to have a way to only search in a specific ebook one at a time.
How could I do this with search-index. I coded a solution which allows to search in the fields filename (the unique filename of the epub) and body (the content of the chapters), but this doesn't feel like it's the right way to do this and the performance is also not ideal.
Here is an example how I do the search with search-index:
searchIndex.search({
query: [{
AND: [
{body: ['epub']},
{filename: ['accessible_epub_3']}
]
}]
});
Is there a better way to do this. Maybe with buckets, categories and filters?
Thanks for your help!
Search-index, which epub-full-text-search is based on, gives one search result back for each document/item that has a match for any given query. My guess is that you would like to know where in the epub-file you get a hit. If a certain paragraph is a good enough search result item, I would index paragraphs. Each paragraph would have a unique book-key as a filter, and maybe a reference to where it is in the epub-file (page/percentage/etc).
Disclaimer: I'm working on the search-index project.

Get part of a list effectively with Mongoose

I have a schema like this.
peopleSchema = Schema({
followerRefs: {
type: [
{ type: Schema.Types.ObjectId, ref: 'peoples'}
],
select: false
},
followRefs: {
type: [
{ type: Schema.Types.ObjectId, ref: 'peoples'}
],
select: false
}
});
Every time, I just want to select part of the followerRefs or followRefs. For example, I want to implement a paging, so I just want to select first 20 in the followRefs, or first 21 ~ 40 in the followRefs.
So, are there any way to get part of the followerRefs with select all of the list?
It seems that I didn't explain my question clearly. I assume that there are over one million entity in the followerRefs in database, and I just want to get the first 20 of them, which mean I just want to get the index of 0~19 of them. So I don't want to load all of the one million entity into the memory.
So I'm wondering whether there are any way to get the first 20 entity without load all of them?
If it's just a list of names, I don't think you should handle the sorting via mongoose nor the backend.
But it depends on the size of data still.
Via javascript(frontend) I would sort the list first depending on what category, then I'd sort it.
Sorting with plain javascript could be complicated and I'm not an expert with it, since I use plugins. XD
So here's a link with a link with an answer about javascript sorting.
Sorting an array of JavaScript objects
Since you are asking about parts of the array, just use javascript slice() function.
var myList = ["Follower1", "Follower2", "Follower3", ...]; //pretend it goes til 20
var page1 = myList.slice(0, 19);

Mongoose behavior and schema

I am learning nodejs along with mongodb currently, and there are two things that confuse me abit.
(1),
When a new Schema and model name are used (not in db), the name is changed into its plural form. Example:
mongoose.model('person', personSchema);
in the database, the table will be called "people" instead.
Isn't this easy to confuse new developer, why has they implemented it this way?
(2),
Second thing is that whenever I want to refer to an existing model in mongoDb (assume that in db, a table called people exists). Then in my nodejs code, I still have to define a Schema in order to create a model that refer to the table.
personSchema = new mongoose.Schema({});
mongoose.model('person',personSchema);
The unusual thing is, it does not seem to matter how I define the schema, it can just be empty like above, or fill with random attribute, yet the model will always get the right table and CRUD operations performs normally.
Then what is the usage of Schema other than defining table structure for creating new table?
Many thanks,
Actually two questions, you usually do better asking one, just for future reference.
1. Pluralization
Short form is that it is good practice. In more detail, this is generally logical as what you are referring to is a "collection" of items or objects rather. So the general inference in a "collection" is "many" and therefore a plural form of what the "object" itself is named.
So a "people" collection implies that it is in fact made up of many "person" objects, just as "dogs" to "dog" or "cats" to "cat". Not necessarily "bovines" to "cow", but generally speaking mongoose does not really deal with Polymorphic entities, so there would not be "bull" or "bison" objects in there unless just specified by some other property to "cow".
You can of course change this if you want in either of these forms and specify your own name:
var personSchema = new Schema({ ... },{ "collection": "person" });
mongoose.model( "Person", personSchema, "person" );
But a model is general a "singular" model name and the "collection" is the plural form of good practice when there are many. Besides, every SQL database ORM I can think of also does it this way. So really this is just following the practice that most people are already used to.
2. Why Schema?
MongoDB is actually "schemaless", so it does not have any internal concept of "schema", which is one big difference from SQL based relational databases which hold their own definition of "schema" in a "table" definition.
While this is often actually a "strength" of MongoDB in that data is not tied to a certain layout, some people actually like it that way, or generally want to otherwise encapsulate logic that governs how data is stored.
For these reasons, mongoose supports the concept of defining a "Schema". This allows you to say "which fields" are "allowed" in the collection (model) this is "tied" to, and which "type" of data may be contained.
You can of course have a "schemaless" approach, but the schema object you "tie" to your model still must be defined, just not "strictly":
var personSchema = new Schema({ },{ "strict": false });
mongoose.model( "Person", personSchema );
Then you can pretty much add whatever you want as data without any restriction.
The reverse case though is that people "usually" do want some type of rules enforced, such as which fields and what types. This means that only the "defined" things can happen:
var personSchema = new Schema({
name: { type: String, required: true },
age: Number,
sex: { type: String, enum: ["M","F"] },
children: [{ type: Schema.Types.ObjectId, ref: "Person" }],
country: { type: String, default: "Australia" }
});
So the rules there break down to:
"name" must have "String" data in it only. Bit of a JavaScript idiom here as everything in JavaScript will actually stringify. The other thing on here is "required", so that if this field is not present in the object sent to .save() it will throw a validation error.
"age" must be numeric. If you try to .save() this object with data other than numeric supplied in this field then you will throw a validation error.
"sex" must be a string again, but this time we are adding a "constraint" to say what the valid value are. In the same way this also can throw a validation error if you do not supply the correct data.
"children" actually an Array of items, but these are just "reference" ObjectId values that point to different items in another model. Or in this case this one. So this will keep that ObjectId reference in there when you add to "children". Mongoose can actually .populate() these later with their actual "Person" objects when requested to do so. This emulates a form of "embedding" in MongoDB, but used when you actually want to store the object separately without "embedding" every time.
"country" is again just a String and requires nothing special, but we give it a default value to fill in if no other is supplied explicitly.
There are many other things you can do, I would suggest really reading through the documentation. Everything is explained in a lot of detail there, and if you have specific questions then you can always ask, "here" (for example).
So MongoDB does things differently to how SQL databases work, and throws out some of the things that are generally held in "opinion" to be better implemented at the application business logic layer anyway.
Hence in Mongoose, it tries to "put back" some of the good things people like about working with traditional relational databases, and allow some rules and good practices to be easily encapsulated without writing other code.
There is also some logic there that helps in "emulating" ( cannot stress enough ) "joins", as there are methods that "help" you in being able to retrieve "related" data from other sources, by essentially providing definitions where which "model" that data resides in within the "Schema" definition.
Did I also not mention that "Schema" definitions are again just objects and re-usable? Well yes they are an can in fact be tied to "many" models, which may or may not reside on the same database.
Everything here has a lot more function and purpose than you are currently aware of, the good advice here it to head forth and "learn". That is the usual path to the realization ... "Oh, now I see, that's what they do it that way".

Filter nested documents in meteorjs

I am having some trouble when trying to filter nested documents in Meteor, and I don't want to use MongoDB Aggregation to unwind my documents.
An example of my Users document
{
Publications:[{
type:1
},{
type:2
}]
}
When I do a find to get only type 1, I get the expected result - they return only the User documents who have a type 1 publication, but they also return the publications with type 2 too, because they are in the publications array of that User.
I can make a loop in the results to remove these publications manually before publishing, but I don't think this is the right approach.
Can someone please help guide me to a better solution?
Edited
The Sean answer is good but not the right yet, the projection dont work on meteor. That work on robomongo, but in the project still returning the 2 publications types of the user.
Give this query a shot.
Meteor.users.find(
{ 'Publications.type': 1 },
{ fields: { 'Publications.$': 1 } }
);
This should find users with publications of type 1 and will only include those matching publications in the output. You can include other fields in the output as well by including them in the projection parameter (e.g., { 'Publications.$': 1, profile: 1 }).
One thing to be aware of though. I think queries that use the { 'Publications.$': 1 } projection can only be done on the server side. The minimongo implementation used on the client does not support it.
More info about the $ projection can be found here.

Categories

Resources