Sequelize where scope with an joined table - node.js

I have a belongs to many relationship with a joined table
courseTree.associate = function (models) {
models.courses.belongsToMany(models.courses, {
through: 'course_tree',
as: 'children',
foreignKey: 'parent_id',
otherKey: 'child_id'
});
};
Currently when I run a find I get all my courses back, even the courses that are children, this is expected behavior but I want to have another scope where I can request only the courses with children.
In my scope I have the following where:
scopes: {
parents: {
where: {
children: { [Op.not] : null }
}
}
}
But the Rest Api gives me the following output
"name": "GeneralError",
"message": "column courses.children does not exist",
"code": 500
In the documentation I can't find any way to do this, I tried sequelize.literal and the in operator but without success. Does anyone knows how this is done, I'm sure I'm missing something.

Solved this by using Sequelize.literal
courses.addScope('getParents', {
where: {
[Op.and]: Sequelize.literal('"courses"."id" NOT IN (SELECT "course_tree"."child_id" FROM "course_tree")')
}
})
Another problem I ran into was that the child was the same model as the parent, in which case the where is also applied on the child. I solved this by adding where: false in the include.

Related

TypeORM how to do deep-relational queries with relations properties / property?

Atm I'm diving into Nest.js using TypeORM and its query builder.
I've discovered the relations property which can be used and I'm curious if it is possible to achieve the same result from the 2nd return statement in the snippet below by using the "relations syntax" in the first result statement.
It works for just one relation, but if I wanna combine more relations, I don't know how to do this.
I switched to try-hard mode and tried wrapping two objects like this: { ChildEntity_name, ParentEntity.ChildEntity_name }, since I could not spot this subject in the TypeORM docs, but (of course) it resulted in an error.
Any help and resource woulda be highly appreciated.
async getAllDeep(): Promise<Order[]> {
return this.orderRepository.find({
relations: [
'Orderposition',
'Order.Orderposition,
],
});
return this.orderRepository
.createQueryBuilder('order')
.leftJoinAndSelect(
'Order.Orderposition',
'Orderposition',
)
.leftJoinAndSelect(
'Orderposition.Article',
'Article',
)
.leftJoinAndSelect(
'Article.Supplier',
'Supplier',
)
.getMany();
}
}
}
Yes, you can do this. You need to pass relations as a nested object like this:
this.orderRepository.find({
relations: {
Orderposition: {
Article: {
Supplier: true
}
}
},
});
This will result in this query:
SELECT * FROM "Order"
LEFT JOIN "Orderposition" ON "Orderposition"."order_id" = "Order"."id"
LEFT JOIN "Article" ON "Article"."Orderposition_id" = "Orderposition"."id"
LEFT JOIN "Supplier" ON "Supplier"."Article_id" = "Article"."id"

SequelizeEagerLoadingError when relationship between models has already been defined

I have an exports file that includes all the sequelize-models and then defines the relationship among the models. It looks something like:
// Snippet from the global init file
for (let modelFile of modelFileList) {
// ... Some code ...
// Require the file
appliedModels[modelName] = require(`../${MODEL_DIR}/${modelFile}`).call(null, _mysql);
}
//Define the relationship between the sql models
_defineRelationship(appliedModels);
function _defineRelationship(models) {
models._planAllocationModel.belongsTo(models._subscriptionModel, {
foreignKey: 'subscription_id',
targetKey: 'subscription_id'
});
}
But when I try to include the model like:
_subscriptionModel.findAll({
where: {
start_date: {
_lte: today // Get all subscriptions where start_date <= today
}
},
limit,
include: [
{
model: _planAllocationModel
}
]
});
There is an error thrown by sequelize: SequelizeEagerLoadingError: tbl_plan_allocation is not associated to tbl_subscription_info! What could be the reason for this? I have already initialized the relationshipt between the 2 models.
I was able to solve the problem. The relationship was defined as belongsTo which had to be changed to hasOne because of the type of join applied in the findAll query.

Sequelize one-to-many query (with include) produces Y is not associated to X

so i'm having problems with sequelize's one to many relationship, my associations are defined like this:
X.hasMany(Y, { as: 'Ys' });
Y.belongsTo(X, { as: 'X' });
and my findAll is here:
return X.findAll(
{
where: {
something: something,
},
include: [{ model: db.Y, as: 'Ys' }]
}
);
and this is producing the error:
"error": "Y (Ys) is not associated to X!"
Not quite sure what i'm doing wrong here :/
There's some confusion on your associations
logically 'X' has many 'Ys', association should be X.hasMany(Y, {as: 'Ys'});
'Y' belongs to 'X' should be Y.hasMany(X, {as: 'X'});

Sequelize belongsTo and include

I am having a hard time making use of BelongsTo relationship in a query.
Here is the relationship:
models.Area.hasMany(models.Airport);
models.Airport.belongsTo(models.Area, {foreignKey: 'areaId', as: 'area'});
Now I try to run a query:
models.Airport
.findAll( {
where: { active: 1 },
include: { model: models.Area }
})
I get an error:
Error: area is not associated to airport!
What am I doing wrong?
I figured it out. I can't say I fully understand the problem, but here it is.
When creating the relationship, I use as: 'area'.
The result is, I need to add as: 'area' to the include statement:
models.Airport
.findAll( {
where: { active: 1 },
include: { model: models.Area, as: 'area' }
})
Once the two match, things go better.

Sails.js - Get an object (model) using multiple join

I am new to node.js and newer to Sails.js framework.
I am currently trying to work with my database, I don't understand all the things with Sails.js but I manage to do what I want step by step. (I am used to some PHP MVC frameworks so it is not too difficult to understand the structure.)
Here I am trying to get a row from my database, using 2 JOIN clause. I managed to do this using SQL and the Model.query() function, but I would like to do this in a "cleaner" way.
So I have 3 tables in my database: meta, lang and meta_lang. It's quite simple and a picture being better than words, here are some screenshots.
meta
lang
meta_lang
What I want to do is to get the row in meta_table that match with 'default' meta and 'en' lang (for example).
Here are Meta and Lang models (I created them with sails generate model command and edited them with what I needed):
Meta
module.exports = {
attributes: {
code : { type: 'string' },
metaLangs:{
collection: 'MetaLang',
via: 'meta'
}
}
};
Lang
module.exports = {
attributes: {
code : { type: 'string' },
metaLangs:{
collection: 'MetaLang',
via: 'lang'
}
}
};
And here is my MetaLang model, with 3 functions I created to test several methods. The first function, findCurrent, works perfectly, but as you can see I had to write SQL. That is what I want to avoid if it is possible, I find it more clean (and I would like to use Sails.js tools as often as I can).
module.exports = {
tableName: 'meta_lang',
attributes: {
title : { type: 'string' },
description : { type: 'text' },
keywords : { type: 'string' },
meta:{
model:'Meta',
columnName: 'meta_id'
},
lang:{
model:'Lang',
columnName: 'lang_id'
}
},
findCurrent: function (metaCode, langCode) {
var query = 'SELECT ml.* FROM meta_lang ml INNER JOIN meta m ON m.id = ml.meta_id INNER JOIN lang l ON l.id = ml.lang_id WHERE m.code = ? AND l.code = ?';
MetaLang.query(query, [metaCode, langCode], function(err, metaLang) {
console.log('findCurrent');
if (err) return console.log(err);
console.log(metaLang);
// OK this works exactly as I want (I would have prefered a 'findOne' result, only 1 object instead of an array with 1 object in it, but I can do with it.)
});
},
findCurrentTest: function (metaCode, langCode) {
Meta.findByCode(metaCode).populate('metaLangs').exec(function(err, metaLang) {
console.log('findCurrentTest');
if (err) return console.log(err);
console.log(metaLang);
// I get what I expected (though not what I want): my meta + all metaLangs related to meta with code "default".
// What I want is to get ONE metaLang related to meta with code "default" AND lang with code "en".
});
},
findCurrentOthertest: function (metaCode, langCode) {
MetaLang.find().populate('meta', {where: {code:metaCode}}).populate('lang', {where: {code:langCode}}).exec(function(err, metaLang) {
console.log('findCurrentOthertest');
if (err) return console.log(err);
console.log(metaLang);
// Doesn't work as I wanted: it gets ALL the metaLang rows.
});
}
};
I also tried to first get my Meta by code, then my Lang by code, and MetaLang using Meta.id and Lang.id . But I would like to avoid 3 queries when I can have only one.
What I'm looking for would be something like MetaLang.find({meta.code:"default", lang.code:"en"}).
Hope you've got all needed details, just comment and ask for more if you don't.
Do you know what populate is for ? its for including the whole associated object when loading it from the database. Its practically the join you are trying to do, if all you need is row retrieval than quering the table without populate will make both functions you built work.
To me it looks like you are re-writing how Sails did the association. Id suggest giving the Associations docs another read in Sails documentation: Associations. As depending on your case you are just trying a one-to-many association with each table, you could avoid a middle table in my guess, but to decide better id need to understand your use-case.
When I saw the mySQL code it seemed to me you are still thinking in MySQL and PHP which takes time to convert from :) forcing the joins and middle tables yourself, redoing a lot of the stuff sails automated for you. I redone your example on 'disk' adapter and it worked perfectly. The whole point of WaterlineORM is to abstract the layer of going down to SQL unless absolutely necessary. Here is what I would do for your example, first without SQL just on a disk adapter id create the models :
// Lang.js
attributes: {
id :{ type: "Integer" , autoIncrement : true, primaryKey: true },
code :"string"
}
you see what i did redundantly here ? I did not really need the Id part as Sails does it for me. Just an example.
// Meta.js
attributes: {
code :"string"
}
better :) ?
// MetaLang.js
attributes:
{
title : "string",
desc : "string",
meta_id :
{
model : "meta",
},
lang_id :
{
model : "lang",
}
}
Now after simply creating the same values as your example i run sails console type :
MetaLang.find({meta_id : 1 ,lang_id:2}).exec(function(er,res){
console.log(res);
});
Output >>>
sails> [ { meta_id: 1,
lang_id: 2,
title: 'My blog',
id: 2 } ]
Now if you want to display what is meta with id 1 and what is lang with id 2, we use populate, but the referencing for join/search is just as simple as this.
sails> Meta_lang.find({meta_id : 1 ,lang_id:2}).populate('lang_id').populate('meta_id').exec(function(er,res){ console.log(res); });
undefined
sails> [ {
meta_id:
{ code: 'default',
id: 1 },
lang_id:
{ code: 'En',
id: 2 },
title: 'My blog',
id: 2 } ]
At this point, id switch adapters to MySQL and then create the MySQL tables with the same column names as above. Create the FK_constraints and voila.
Another strict policy you can add is to set up the 'via' and dominance on each model. you can read more about that in the Association documentation and it depends on the nature of association (many-to-many etc.)
To get the same result without knowing the Ids before-hand :
sails> Meta.findOne({code : "default"}).exec(function(err,needed_meta){
..... Lang.findOne({code : "En"}).exec(function(err_lang,needed_lang){
....... Meta_lang.find({meta_id : needed_meta.id , lang_id : needed_lang.id}).exec(function(err_all,result){
......... console.log(result);});
....... });
..... });
undefined
sails> [ { meta_id: 1,
lang_id: 2,
title: 'My blog',
id: 2 } ]
Have you tried:
findCurrentTest: function (metaCode, langCode) {
Meta.findByCode(metaCode).populate('metaLangs', {where: {code:langCode}}).exec(function(err, metaLang) {
console.log('findCurrentTest');
if (err) return console.log(err);
console.log(metaLang);
});
},

Resources