How to sum a virtual column from children association ? (Sequelize) - javascript

I have 3 objects (A, B and C) linked together. I can have multiple B associated to A and multiple C associated to B. Like for example:
A
└───B
│ └───C
│ └───C
└───B
└───C
In C I have 2 duration (xDuration and yDuration) and I created a virtual column totalDuration.
I want to have in each object the total duration of all children. For example if B has two children C with respectively 20 and 40 min, my total duration in B will be 60 and so on.
Here are my objects definitions:
A.model.js:
module.exports = function(sequelize, DataTypes) {
const A = sequelize.define("A", {
id: {
type: DataTypes.STRING,
primaryKey: true,
allowNull: false,
unique: true,
},
});
A.associate = function(models) {
A.hasMany(models.B, {
foreignKey: "AId",
sourceKey: "id",
});
};
return A;
};
B.model.js
module.exports = function(sequelize, DataTypes) {
const B = sequelize.define("B", {
id: {
type: DataTypes.STRING,
primaryKey: true,
allowNull: false,
unique: true,
},
AId: {
type: DataTypes.STRING,
allowNull: false,
},
});
B.associate = function(models) {
B.belongsTo(models.A, {
foreignKey: "AId",
onDelete: "CASCADE",
});
B.hasMany(models.C, {
foreignKey: "BId",
});
};
return B;
};
C.model.js:
module.exports = function(sequelize, DataTypes) {
const C = sequelize.define("C", {
id: {
type: DataTypes.STRING,
primaryKey: true,
allowNull: false,
unique: true,
},
BId: {
type: DataTypes.STRING,
allowNull: false,
},
xDuration: {
type: DataTypes.INTEGER,
defaultValue: () => 0,
},
yDuration: {
type: DataTypes.INTEGER,
defaultValue: () => 0,
},
totalDuration: {
type: DataTypes.VIRTUAL,
get: function() {
return this.get("xDuration") + this.get("yDuration");
},
},
});
C.associate = function(models) {
C.belongsTo(models.B, {
foreignKey: "BId",
onDelete: "CASCADE",
});
};
return C;
};
NPM modules versions:
Sequelize: 6.21.3
pg: 8.7.3
I tried create a virtual column for each in the definition but I can't access the children from there.
The almost working solution I tried was to add an attribute in my findAll() with something like this :
B.findAll({
attributes: [
[Sequelize.fn('SUM', Sequelize.col('C.totalDuration')), 'totalDuration'],
],
include: [
{
model: C,
attributes: ["totalDuration"]
}
],
group: ['C.id'],
});
But since totalDuration does not exist, it's only a virtual column, I got an error.

You can use a plain SQL subquery to calculate the sum for all children like this:
B.findAll({
attributes: [
[Sequelize.literal('(SELECT SUM(C.xDuration + C.yDuration) FROM C WHERE C.BId=B.id)'), 'totalDuration'],
],
});

Related

Column specified twice error in sequelize

I have defined two table with many-to-many association between them.
create-image-migration.js
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Images', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
...
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Images');
}
};
create-category-migration.js
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Categories', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
...
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Categories');
}
};
Now JOIN table is defined as follows
create-image-category-migration.js
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('ImageCategories', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
imageId: {
type: Sequelize.INTEGER,
allowNull: false,
references: { model: 'Images', key: 'id' }
},
categoryId: {
type: Sequelize.INTEGER,
allowNull: false,
references: { model: 'Categories', key: 'id' }
},
...
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('ImageCategories');
}
};
image-category-model.js
'use strict';
module.exports = (sequelize, DataTypes) => {
const ImageCategory = sequelize.define('ImageCategory', {
imageId: {
type: DataTypes.INTEGER,
allowNull: false,
references: { model: 'Image', key: 'id' },
},
categoryId: {
type: DataTypes.INTEGER,
allowNull: false,
references: { model: 'Category', key: 'id' },
},
...
}, {});
ImageCategory.associate = function(models) {
models.Image.belongsToMany(models.Category, { through: ImageCategory });
models.Category.belongsToMany(models.Image, { through: ImageCategory });
};
return ImageCategory;
};
Now when I run the migration the join table is created with respective column name as specified in migration file i.e. in camel case.
But when I run the following bulkCreate command in sequelize to insert data
await db.ImageCategory.bulkCreate([
{ imageId: 'someId', categoryId: topicId, categoryType: 'topic' },
{ imageId: 'someId', categoryId: styleId, categoryType: 'style' },
]);
I am get the following error:
sqlMessage: "Column 'imageId' specified twice",
sql: "INSERT INTO `ImageCategories` (`imageId`,`categoryId`,`categoryType`,`createdAt`,`updatedAt`,`ImageId`) VALUES (5,'22','topic','2022-11-26 08:11:41','2022-11-26 08:11:41',NULL),(5,'27','style','2022-11-26 08:11:41','2022-11-26 08:11:41',NULL);"
},
As we can see here "ImageId" is automatically added by sequelize. So my question is if there is a convention followed by sequelize to name the column name while creating join table since it is not mention anywhere on its documentation.
By default Sequelize generates foreign key names in the pascal case. You do have foreign keys in the junction table that differ with the letter case.
So you just need to indicate foreign keys explicitly in both associations:
ImageCategory.associate = function(models) {
models.Image.belongsToMany(models.Category, { through: ImageCategory, foreignKey: 'imageId' });
models.Category.belongsToMany(models.Image, { through: ImageCategory, foreignKey: 'categoryId' });
};

Sequelize: Foreign key declared, but does not appear in the table

I'm new to using Sequelize and for that I strictly follow the documentation here. It is written that we must use hasOne(), hasMany(), belongsTo() in order to add automatically the foreign keys. In my situation: I have a Category and a FAQ model, defined so:
Category.js
const { Sequelize } = require('sequelize');
const sequelize = require('../database/connection');
const Category = sequelize.define('Category', {
id: {
type: Sequelize.DataTypes.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
categoryShop_id: {
type: Sequelize.DataTypes.UUID,
allowNull: true
},
name: {
type: Sequelize.DataTypes.STRING,
allowNull: false
},
active: {
type: Sequelize.DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true
},
parent_id: {
type: Sequelize.DataTypes.INTEGER,
allowNull: true
}
});
Category.associate = (models) => {
Category.hasMany(models.faqs, {
onDelete:'CASCADE',
onUpdate:'CASCADE'
});
};
module.exports = Category;
Faq.js
const { Sequelize } = require('sequelize');
const sequelize = require('../database/connection');
const Faq = sequelize.define('Faq', {
id: {
type: Sequelize.DataTypes.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
question: {
type: Sequelize.DataTypes.TEXT,
allowNull: false
},
answer: {
type: Sequelize.DataTypes.TEXT,
allowNull: false
},
product_id: {
type: Sequelize.DataTypes.STRING,
allowNull: true
},
active: {
type: Sequelize.DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true
}
});
Faq.associate = (models) => {
Faq.belongsTo(models.categories, {
foreignKey: {
name: 'category_id',
allowNull: true
}
});
};
module.exports = Faq;
The migrations run without any errors, but I don't see the added columns to the table. What is the reason for this?
Your confusing model and migration code. Your migrations are ok, but lack the right foreign key columns for your associations.
Your association code looks ok but it belongs in your model.
Look at https://sequelizeui.app/ for some example code.

Sequelize: A is not associated to B" nodejs

I have two models in which one is Question and other is Answer, each answer has one question_id and question can have more then one answers.
I want to include all the answers of each question in my json response but I am keep getting an error
"message": "answer is not associated to question!"*
Below is the Question model:-
module.exports = (sequelize, Sequelize) => {
const Question = sequelize.define("question", {
question_id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
question_no: {
type: Sequelize.DOUBLE,
allowNull: false
},
question_text: {
type: Sequelize.STRING,
allowNull: false
},
question_text: {
type: Sequelize.STRING,
allowNull: false
},
question_required: {
type: Sequelize.BOOLEAN,
},
formpage_no: {
type: Sequelize.DOUBLE,
allowNull: false
},
med_id: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'medforms',
key: 'med_id'
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE'
},
med_name: {
type: Sequelize.STRING,
allowNull: true
},
version_no: {
type: Sequelize.STRING,
allowNull: false
}
}, {
freezeTableName: false, // true: if we want to make table name as we want else sequelize will make them prural
underscored: true // underscored: true indicates the the column names of the database tables are snake_case rather than camelCase
});
Question.associate = function (models) {
Question.hasMany(models.answer, {
foreignKey: 'question_id',
as: 'answers'
});
Question.hasMany(models.helpbox, {
foreignKey: 'question_id',
as: 'helpboxes'
});
// in future each question could have more than one document text
};
return Question;
};
And below is my answer model:-
module.exports = (sequelize, Sequelize) => {
const Answer = sequelize.define("answer", {
answer_id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
question_id: { // each answer has one questionId
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'questions',
key: 'question_id'
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE',
},
question_no: {
type: Sequelize.DOUBLE,
allowNull: true
},
answer_no: {
type: Sequelize.DOUBLE,
allowNull: true
},
answer_text: {
type: Sequelize.STRING,
allowNull: false
},
answer_icon: {
type: Sequelize.STRING,
allowNull: true
},
answer_reply: {
type: Sequelize.STRING,
allowNull: true
},
answer_logic: {
type: Sequelize.STRING,
allowNull: true
},
med_name: {
type: Sequelize.STRING,
allowNull: true
},
version_no: {
type: Sequelize.STRING,
allowNull: false
},
}, {
freezeTableName: false, // true: if we want to make table name as we want else sequelize will make them prural
underscored: true // underscored: true indicates the the column names of the database tables are snake_case rather than camelCase
});
Answer.associate = function (models) {
Answer.belongsTo(models.question, {
as: 'questions'
});
// in future each question could have more than one document text
};
return Answer;
};
below is the index.js class:-
var Sequelize = require('sequelize');
var env = process.env.NODE_ENV || 'development';
var config = require("../config/config.json")[env];
var db = {};
if (config.use_env_variable) {
var sequelize = new Sequelize(process.env[config.use_env_variable]);
} else {
var sequelize = new Sequelize(config.database, config.username, config.password, config);
}
db.Sequelize = Sequelize;
db.sequelize = sequelize;
// Models
db.medform = require("./medform.model.js")(sequelize, Sequelize);
db.version = require("./version.model.js")(sequelize, Sequelize);
db.question = require("./question.model.js")(sequelize, Sequelize);
db.helpbox = require("./helpbox.model.js")(sequelize, Sequelize);
db.answer = require("./answer.model.js")(sequelize, Sequelize);
db.document = require("./document.model.js")(sequelize, Sequelize);
module.exports = db;
This is my question controller
// Retrieve Question including answers from the database:
exports.getAllQuesData = (req, res) => {
const version_no = req.query.version_no;
const med_id = req.query.med_id;
var condition = [{ "version_no": version_no }, { "med_id": med_id }];
Question.findAll({
include: [
{
model: answer,
as: 'answers'
}
],
where: condition
})
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while retrieving questions."
});
});
};
Please help me what I am doing wrong why my associations are not working
You didn't register model associations. See my answer how to do it
Object.keys(db).forEach(function (modelName) {
if (db[modelName].associate) {
db[modelName].associate(db)
}
})

Sequelize association not syncing with model definition

'use strict';
module.exports = (sequelize, type) => {
const article_comment = sequelize.define('article_comments', {
// attributes
id: {
type: type.INTEGER,
primaryKey: true,
autoIncrement: true
},
positive_rating:{
type: type.INTEGER,
allowNull: false
},
negative_rating:{
type: type.INTEGER,
allowNull: false
},
comment:{
type: type.STRING,
allowNull: false
},
updatedAt:{
type: type.STRING,
allowNull: false
},
createdAt:{
type: type.STRING,
allowNull: false
},
}, {});
article_comment.associate = function(models) {
// associations can be defined here
article_comment.hasMany(models.article_comments_user_ratings,{foreignKey:'comment_id'});
article_comment.hasMany(models.article_replies,{foreignKey:'comment_id'});
};
return article_comment;
};
And my rating for comments
'use strict';
module.exports = (sequelize, type) => {
const article_comments_user_ratings = sequelize.define('article_comments_user_ratings', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: type.INTEGER
},
rating:{
type: type.BOOLEAN,
allowNull: false
},
createdAt: {
allowNull: false,
type: type.DATE
},
updatedAt: {
allowNull: false,
type: type.DATE
}
}, {});
article_comments_user_ratings.associate = function(models) {
// associations can be defined here
article_comments_user_ratings.belongsTo(models.article_comments)
};
return article_comments_user_ratings;
};
However, when I use the findOrCreate method, it only does INSERT INTO "article_comments_user_ratings" ("id","rating","createdAt","updatedAt"). Which obviously is failing because In the database I also have the additional columns of user_id and comment_id for the article_comments_user_ratings table.
This isn't making any sense because with the sync() function, prior to moving to migrations, it was working.
I don't know what to do?
I fixed this issue by defining the associations after defining the model.
Example:
const User = UserModel(sequelize, Sequelize)
const Comment = CommentsModel(sequelize, Sequelize)
User.hasMany(UserCommentRating,{foreignKey:'user_id'})
Comment.hasMany(UserCommentRating,{foreignKey:'comment_id'})
It seems that the assocations inside the model is deprecated.

Associate in Sequelize not working as intended

I am trying to associate two tables in Sequelize but I am getting the SequelizeEagerLoadingError that one table is not associated to another despite trying all the available fixes on this platform.
I have two tables, User and Item.
User (user.js)
const User = dbconnection.sequelize.define('users', {
id: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true},
name: {
type: Sequelize.STRING(80),
allowNull: false
},
email: {
type: Sequelize.STRING(120),
allowNull: false,
unique: true
},
dob: {
type: Sequelize.DATEONLY,
allowNull: false
},
password: {
type: Sequelize.STRING(256),
allowNull: false
}
});
User.associate = models => {
User.hasMany(models.Item, { as: 'items',foreignKey: 'user_id' })
}
dbconnection.sequelize.sync({ force: false })
.then(() => {
//console.log('Table created!')
});
module.exports = {
User
};
Item (item.js)
const Item = dbconnection.sequelize.define('items', {
id: { type: Sequelize.INTEGER, unique: true, autoIncrement: true, primaryKey: true},
item: {
type: Sequelize.STRING(80),
allowNull: true
},
item_type: {
type: Sequelize.STRING(10),
allowNull: false
},
comment: {
type: Sequelize.STRING(1000),
allowNull: true
},
user_id: {
type: Sequelize.INTEGER,
allowNull: false,
references: { model: 'users', key: 'id' }
},
});
Item.associate = models => {
Item.belongsTo(models.User, { as: 'users',foreignKey: 'user_id' })
}
dbconnection.sequelize.sync({ force: false })
.then(() => {
// console.log('Table created!')
})
});
module.exports = {
Item
};
User hasMany(Item) while Item belongsTo(User) as shown above.
However, when I make a query to the Item table (as below),
const usersdb = require('./userdb')
const itemsdb = require('./itemdb')
class ItemsController {
static async getAllItems(req, res, next) {
try{
let allitems = await itemsdb.Item.findAll({
include: [{
model: usersdb.User
}]
})
return {items: allitems, status: true}
}
catch (e) {
return {items: e, status: false}
}
}
}
module.exports = ItemsController;
I get the SequelizeEagerLoadingError that "users is not associated to items!"
I have tried all the available fixes including this and this among others but to no success.
I have finally found a workaround. First, I dropped the tables and discarded the model definitions. Second, I generated migrations and models using the sequelize model:create --name ModelName --attributes columnName:columnType command. I then used the generated models to associate the two tables just as I had done earlier. Lastly, I ran the sequelize db:migrate command to create the tables and on running the query, it worked!
Earlier, I was creating the models manually. I was also creating the tables using the sequelize.sync({force: false/true}) command after loading the models.
User Model (user.js)
'use strict';
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
email: {
type: DataTypes(120),
allowNull: false,
unique: true
},
dob: {
type: DataTypes.DATEONLY,
allowNull: false
},
password: {
type: DataTypes.STRING(256),
allowNull: false
}
}, {});
User.associate = function(models) {
User.hasMany(models.Item, {as: 'Item', foreignKey: 'user_id'})
};
return User;
};
Item model (item.js)
'use strict';
module.exports = (sequelize, DataTypes) => {
const Item = sequelize.define('Item', {
item: {
type: DataTypes.STRING(80),
allowNull: true
},
item_type: {
type: DataTypes.STRING(10),
allowNull: false
},
comment: {
type: DataTypes.STRING(1000),
allowNull: true
},
user_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: { model: 'User', key: 'id' }
}
}, {});
Item.associate = function(models) {
Item.belongsTo(models.User, { as: 'User',foreignKey: 'user_id' })
};
return Item;
};
Query (queryitem.js)
const Item = require('../models').Item
const User = require('../models').User
class ItemsController {
static async getAllItems() {
try{
let allitems = await Item.findAll({
include: [{
model: User,
as: 'User'
}]
})
return {items: allitems, status: true}
}
catch (e) {
return {items: e, status: false}
}
}
}
module.exports = ItemsController;

Categories