I have 2 situations. The main thing that connects them is that the primary keys on the tables are not autoincrement integers, which seems to cause sequelize to try to create additional association or field names.
Situation 1, I have 2 tables, I have the associations defined. When I try to query it, a random extra field gets inserted, throwing an error.
Model 1 file
const { DataTypes, Model } = require('sequelize');
const modelName = 'BusinessAccountSetting';
const tableName = 'BusinessAccountSettings';
class BusinessAccountSetting extends Model {
static doInit (sequelize) {
this.init({
_id: {
type: DataTypes.BIGINT.UNSIGNED,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
BusinessAccountId: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false
},
BusinessSettingKey: {
type: DataTypes.STRING(200),
allowNull: false,
defaultValue: true
},
value: {
type: DataTypes.JSON,
allowNull: false
}
}, {
sequelize,
modelName,
tableName,
timestamps: true,
paranoid: true
});
};
static associate (models) {
this.belongsTo(models.BusinessAccount, {
as: 'business',
foreignKey: 'BusinessAccountId',
targetKey: '_id'
});
this.belongsTo(models.BusinessSetting, {
as: 'setting',
foreignKey: 'BusinessSettingKey',
targetKey: 'BusinessSettingKey'
});
};
};
module.exports = {
modelName,
model: BusinessAccountSetting
};
Model 2 file
const { DataTypes, Model } = require('sequelize');
const modelName = 'BusinessSetting';
const tableName = 'BusinessSettings';
class BusinessSetting extends Model {
static doInit (sequelize) {
this.init({
BusinessSettingKey: {
type: DataTypes.STRING(200),
allowNull: false,
primaryKey: true,
unique: true
},
label: {
type: DataTypes.STRING(200),
allowNull: false
},
description: {
type: DataTypes.STRING(500),
allowNull: true
},
defaultValue: {
type: DataTypes.JSON,
allowNull: false
},
BusinessSettingGroupKey: {
type: DataTypes.STRING(200),
allowNull: false
},
order: {
type: DataTypes.SMALLINT.UNSIGNED,
allowNull: false
}
}, {
sequelize,
modelName,
tableName,
timestamps: true,
paranoid: true
});
};
static associate (models) {
this.belongsTo(models.BusinessSettingGroup, {
as: 'group',
foreignKey: 'BusinessSettingGroupKey',
targetKey: 'BusinessSettingGroupKey'
});
this.hasMany(models.BusinessAccountSetting, {
as: 'businessAccountSettings',
foreignKey: 'BusinessSettingKey',
sourceKey: 'BusinessSettingKey'
});
};
};
module.exports = {
modelName,
model: BusinessSetting
};
When I run this query
const settings = await sqldb.BusinessSetting.findAll({
include: [
{
model: sqldb.BusinessAccountSetting,
as: 'businessAccountSettings',
where: {
BusinessAccountId
},
required: false
}
]
});
It generates this sql
SELECT
`BusinessSetting`.`BusinessSettingKey`,
`BusinessSetting`.`label`,
`BusinessSetting`.`description`,
`BusinessSetting`.`defaultValue`,
`BusinessSetting`.`BusinessSettingGroupKey`,
`BusinessSetting`.`order`,
`BusinessSetting`.`createdAt`,
`BusinessSetting`.`updatedAt`,
`BusinessSetting`.`deletedAt`,
`businessAccountSettings`.`_id` AS `businessAccountSettings._id`,
`businessAccountSettings`.`BusinessAccountId` AS `businessAccountSettings.BusinessAccountId`,
`businessAccountSettings`.`BusinessSettingKey` AS `businessAccountSettings.BusinessSettingKey`,
`businessAccountSettings`.`value` AS `businessAccountSettings.value`,
`businessAccountSettings`.`createdAt` AS `businessAccountSettings.createdAt`,
`businessAccountSettings`.`updatedAt` AS `businessAccountSettings.updatedAt`,
`businessAccountSettings`.`deletedAt` AS `businessAccountSettings.deletedAt`,
`businessAccountSettings`.`BusinessSettingBusinessSettingKey` AS `businessAccountSettings.BusinessSettingBusinessSettingKey`
FROM
`BusinessSettings` AS `BusinessSetting` LEFT OUTER JOIN `BusinessAccountSettings` AS `businessAccountSettings` ON `BusinessSetting`.`BusinessSettingKey` = `businessAccountSettings`.`BusinessSettingKey`
AND (`businessAccountSettings`.`deletedAt` IS NULL AND `businessAccountSettings`.`BusinessAccountId` = 20)
WHERE (`BusinessSetting`.`deletedAt` IS NULL);
Which throws an error because of this:
`businessAccountSettings`.`BusinessSettingBusinessSettingKey` AS `businessAccountSettings.BusinessSettingBusinessSettingKey`
The associations are defined. The primary keys are defined. It should not be trying to add additional fields to fill in the blanks.
It's not an extra hook because it is trying to create a field for the reverse association which is already defined. It's not coming from another model association and I went through all of my files and remove the hooks: true flags just to be sure.
Problem #2, M:N associations with non-numeric keys
File #1
const { DataTypes, Model } = require('sequelize');
const modelName = 'BusinessRoleTemplate';
const tableName = 'BusinessRoleTemplates';
class BusinessRoleTemplate extends Model {
static doInit (sequelize) {
this.init({
BusinessRoleTemplateKey: {
type: DataTypes.STRING(100),
primaryKey: true,
allowNull: false,
unique: true
},
description: {
type: DataTypes.STRING(250),
allowNull: true
},
group: {
type: DataTypes.STRING(50),
allowNull: true
},
isCategoryTemplate: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
},
ranking: {
type: DataTypes.TINYINT.UNSIGNED,
allowNull: false
},
active: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true
}
}, {
sequelize,
modelName,
tableName,
timestamps: true,
paranoid: true
});
};
static associate (models) {
this.belongsToMany(models.BusinessPermission, {
as: 'permissions',
through: models.BusinessRoleTemplatePermission
});
};
};
module.exports = {
modelName,
model: BusinessRoleTemplate
};
File 2
const { DataTypes, Model } = require('sequelize');
const modelName = 'BusinessPermission';
const tableName = 'BusinessPermissions';
class BusinessPermission extends Model {
static doInit (sequelize) {
this.init({
BusinessPermissionKey: {
type: DataTypes.STRING(100),
allowNull: false,
primaryKey: true,
unique: true
},
plainText: {
type: DataTypes.STRING(100),
allowNull: false
},
description: {
type: DataTypes.STRING(250),
allowNull: true
},
requiresRank: {
type: DataTypes.INTEGER(2).UNSIGNED,
allowNull: false,
defaultValue: 10
},
BusinessPermissionGroupKey: {
type: DataTypes.STRING(100),
allowNull: false
},
isCategoryPermission: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
},
active: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true
}
}, {
sequelize,
modelName,
tableName,
timestamps: true,
paranoid: true
});
};
static associate (models) {
this.belongsTo(models.BusinessPermissionGroup, {
as: 'group',
foreignKey: 'BusinessPermissionGroupKey',
targetKey: 'BusinessPermissionGroupKey'
});
this.hasMany(models.BusinessPermissionAlternative, {
as: 'alternates',
foreignKey: 'AlternateBusinessPermissionKey',
sourceKey: 'BusinessPermissionKey'
});
this.belongsToMany(models.BusinessRoleTemplate, {
as: 'roleTemplates',
through: models.BusinessRoleTemplatePermission
});
this.belongsToMany(models.BusinessRole, {
as: 'roles',
through: models.BusinessRolePermission
});
};
};
module.exports = {
modelName,
model: BusinessPermission
};
Association table
const { DataTypes, Model } = require('sequelize');
const modelName = 'BusinessRoleTemplatePermission';
const tableName = 'BusinessRoleTemplatePermissions';
class BusinessRoleTemplatePermission extends Model {
static doInit (sequelize) {
this.init({
_id: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
BusinessPermissionKey: {
type: DataTypes.STRING(100),
allowNull: false
},
BusinessRoleTemplateKey: {
type: DataTypes.STRING(100),
allowNull: false
}
}, {
sequelize,
modelName,
tableName,
timestamps: true,
paranoid: false
});
};
static associate (models) {
this.belongsTo(models.BusinessPermission, {
as: 'permission',
foreignKey: 'BusinessPermissionKey',
targetKey: 'BusinessPermissionKey'
});
this.belongsTo(models.BusinessRoleTemplate, {
as: 'role',
foreignKey: 'BusinessRoleTemplateKey',
targetKey: 'BusinessRoleTemplateKey'
});
};
};
module.exports = {
modelName,
model: BusinessRoleTemplatePermission
};
BusinessRoleTemplate hasMany BusinessPermissions through BusinessRoleTemplatePermissions
BusinessRoleTemplatePermissions has the associations for both tables defined, so there shouldn't be a need for anything else.
However, when I run this query:
role = await sqldb.BusinessRoleTemplate.findOne({
where: {
BusinessRoleTemplateKey: data.role
},
attributes: ['BusinessRoleTemplateKey', 'description', 'isCategoryTemplate', 'ranking'],
include: [
{
model: sqldb.BusinessPermission,
as: 'permissions',
attributes: ['BusinessPermissionKey', 'isCategoryPermission']
}
]
});
I get this SQL:
SELECT
`BusinessRoleTemplate`.`BusinessRoleTemplateKey`,
`BusinessRoleTemplate`.`description`,
`BusinessRoleTemplate`.`isCategoryTemplate`,
`BusinessRoleTemplate`.`ranking`,
`permissions`.`BusinessPermissionKey` AS `permissions.BusinessPermissionKey`,
`permissions`.`isCategoryPermission` AS `permissions.isCategoryPermission`,
`permissions->BusinessRoleTemplatePermission`.`_id` AS `permissions.BusinessRoleTemplatePermission._id`,
`permissions->BusinessRoleTemplatePermission`.`BusinessPermissionKey` AS `permissions.BusinessRoleTemplatePermission.BusinessPermissionKey`,
`permissions->BusinessRoleTemplatePermission`.`BusinessRoleTemplateKey` AS `permissions.BusinessRoleTemplatePermission.BusinessRoleTemplateKey`,
`permissions->BusinessRoleTemplatePermission`.`createdAt` AS `permissions.BusinessRoleTemplatePermission.createdAt`,
`permissions->BusinessRoleTemplatePermission`.`updatedAt` AS `permissions.BusinessRoleTemplatePermission.updatedAt`,
`permissions->BusinessRoleTemplatePermission`.`BusinessPermissionBusinessPermissionKey` AS `permissions.BusinessRoleTemplatePermission.BusinessPermissionBusinessPermissionKey`,
`permissions->BusinessRoleTemplatePermission`.`BusinessRoleTemplateBusinessRoleTemplateKey` AS `permissions.BusinessRoleTemplatePermission.BusinessRoleTemplateBusinessRoleTemplateKey`
FROM `BusinessRoleTemplates` AS `BusinessRoleTemplate`
LEFT OUTER JOIN (
`BusinessRoleTemplatePermissions` AS `permissions->BusinessRoleTemplatePermission`
INNER JOIN `BusinessPermissions` AS `permissions`
ON `permissions`.`BusinessPermissionKey` = `permissions->BusinessRoleTemplatePermission`.`BusinessPermissionBusinessPermissionKey`)
ON `BusinessRoleTemplate`.`BusinessRoleTemplateKey` = `permissions->BusinessRoleTemplatePermission`.`BusinessRoleTemplateBusinessRoleTemplateKey`
AND (`permissions`.`deletedAt` IS NULL)
WHERE (`BusinessRoleTemplate`.`deletedAt` IS NULL AND `BusinessRoleTemplate`.`BusinessRoleTemplateKey` = 'Senior Manager');
With all sorts of stuff added:
added fields:
`permissions->BusinessRoleTemplatePermission`.`BusinessPermissionBusinessPermissionKey` AS `permissions.BusinessRoleTemplatePermission.BusinessPermissionBusinessPermissionKey`,
`permissions->BusinessRoleTemplatePermission`.`BusinessRoleTemplateBusinessRoleTemplateKey` AS `permissions.BusinessRoleTemplatePermission.BusinessRoleTemplateBusinessRoleTemplateKey`
Added associations:
ON `permissions`.`BusinessPermissionKey` = `permissions->BusinessRoleTemplatePermission`.`BusinessPermissionBusinessPermissionKey`)
If I change the association in BusinessRoleTemplate to this, it works:
this.belongsToMany(models.BusinessPermission, {
as: 'permissions',
through: models.BusinessRoleTemplatePermission,
foreignKey: 'BusinessRoleTemplateKey',
otherKey: 'BusinessPermissionKey'
});
I shouldn't need to add the foreignKey and otherKey because the associations are already defined in the through table, but sequelize isn't recognizing them, it is trying to create them.
When I try to update a recipe with associated tags using the through option, no record is updated in the joining table in the mysql database I'm connected to.
Here are my models definitions:
export const Recipe = sequelize.define('Recipe', {
// Model attributes are defined here
title: {
type: DataTypes.STRING,
allowNull: false
},
image: {
type: DataTypes.STRING,
allowNull: true
},
prepTime: {
type: DataTypes.DOUBLE,
allowNull: false
},
cookTime: {
type: DataTypes.DOUBLE,
allowNull: false
},
totalTime: {
type: DataTypes.DOUBLE,
allowNull: false
},
servings: {
type: DataTypes.INTEGER,
allowNull: false
},
rating: {
type: DataTypes.INTEGER,
allowNull: false
},
notes: {
type: DataTypes.STRING, allowNull: true
},
}, {
// Other model options go here
tableName: 'Recipes'
});
export const Tag = sequelize.define('Tag', {
// Model attributes are defined here
name: {
type: DataTypes.STRING,
allowNull: false
},
}, {
// Other model options go here
tableName: 'Tags'
});
export const RecipeTag = sequelize.define('RecipeTag', {
// Model attributes are defined here
}, {
// Other model options go here
timestamps: false,
tableName: 'RecipeTags'
});
Here are my associations:
Recipe.belongsToMany(Tag, {
through: RecipeTag,
foreignKey: 'recipeId',
as: 'tags'
})
Tag.belongsToMany(Recipe, {
through: RecipeTag,
foreignKey: 'tagId',
as: 'recipes'
})
Here is the update call:
Recipe.update(args, {
include: [{
model: Tag,
through: RecipeTag,
as: 'tags',
where: {
recipeId: args.id
}
}],
where: {
id: args.id
},
});
Only one update mysql call is executed on the Recipes table. Is it possible to update the RecipeTags through table record in the same update call?
Tagcategories Model
export const TagCategories = sequelize.define(
"tag_categories",
{
categoryId: {
type: DataTypes.INTEGER,
field: "category_id",
autoIncrement: true,
primaryKey: true,
},
title: {
type: DataTypes.STRING(50),
field: "title",
allowNull: false,
unique: true,
},
},
);
TagCategories.hasMany(TagGroups, {
foreignKey: "categoryId",
sourceKey: "categoryId",
});
export default TagCategories;
TagGroups Model
export const TagGroups = sequelize.define(
"tag_groups",
{
tagGroupId: {
type: DataTypes.INTEGER,
field: "tag_group_id",
autoIncrement: true,
primaryKey: true,
},
categoryId: {
type: DataTypes.INTEGER,
field: "category_id",
allowNull: false,
},
title: {
type: DataTypes.STRING(50),
field: "title",
allowNull: false,
unique: true,
},
},
);
In the above models I establish oneToMany relationship between the TagCategories and TagGroups
But I want to fetch the record from the TagGroup table with the TagCategories details.
Thanks in advance
Did you look at examples in official documentation?
Also, you need to add an association from TagGroups to TagCategories:
// there is no need to indicate `sourceKey` if this field is a primary key
TagGroups.belongsTo(TagCategories, {
foreignKey: "categoryId",
});
It's better to define all associations in static functions and call all of them separately after all models will be registered.
See the question and my answer here how to do it
In your case, the final request would be something like this
const tagGroup = await TagGroups.findOne({
where: {
tagGroupId: groupId
},
include: [{
model: TagCategories
}]
})
Using Sequelize with MySQL. I have three models. Consultant, FamilyMember and Appointments. Appointment refers to Consultant and FamilyMember.
I have defined the foreign keys in the Appointment model. When the DB is created - the foreign keys are visible - when I check through a MySQL client, on the appointment table. The table names are freeze - so there isn't any chance of pluralization of the table names.
Consultant Model:
module.exports = (sequelize, DataTypes) => {
const consultant = sequelize.define('consultant', {
ID: {
type: DataTypes.UUID,
primaryKey: true,
allowNull: false
},
FirstName: {
type: DataTypes.STRING,
allowNull: false
},
LastName: {
type: DataTypes.STRING,
allowNull: false
}
{
freezeTableName: true
}
);
return consultant;
};
Appointment Model:
module.exports = (sequelize, DataTypes) => {
const appointment = sequelize.define('appointment', {
// attributes
ID: {
type: DataTypes.UUID,
primaryKey: true,
allowNull: false
},
ConsultantID: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'consultant',
key: 'ID'
}
},
FamilyMemberID: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'familymember',
key: 'ID'
}
}
},
{
freezeTableName: true
}
);
appointment.associate = function (models) {
models.appointment.belongsTo(models.consultant, {
foreignKey: 'ConsultantID',
as: 'consultant',
});
models.appointment.belongsTo(models.familymember, {
foreignKey: 'FamilyMemberID',
as: 'familymember',
});
};
return appointment;
};
Family Member model:
module.exports = (sequelize, DataTypes) => {
const familymember = sequelize.define('familymember', {
// attributes
ID: {
primaryKey: true,
type: DataTypes.UUID,
allowNull: false
},
FamilyID: {
type: DataTypes.UUID,
allowNull: false
},
FirstName: {
type: DataTypes.STRING,
allowNull: false
},
LastName: {
type: DataTypes.STRING,
allowNull: false
}
},
{
freezeTableName: true
}
);
return familymember;
};
Then in the code I try to fetch appointment and get the related familymember and consultant like this
var appointments = await Appointment.findAll({
where: {
AppointmentDateConfirmed: {
$gte: moment().subtract(0, 'days').toDate()
}
}, include:[Consultant, FamilyMember]
}
)
However I get an error
UnhandledPromiseRejectionWarning: SequelizeEagerLoadingError: consultant is not associated to appointment!
I suppose you should register your associations after models registration like I pointed in this answer
I am creating a cinema application. I have modeled the database on mySql but I am having trouble migrating it to Sequelize. I have followed the documentation but I am getting a lot of different errors.
I have tried using associations and indexes (as it should be). This is the model I am trying to make.
OCCUPIED_SEATS is composed of only two foreign keys and both make a unique index.
OCCUPIED_SEATS:
const SEATS = require("./Seats");
const SCREENING = require("./Screening");
const OCCUPIED_SEATS = sequelize.define("OCCUPIED_SEATS", {
//SEATS_ID
//SCREENING_ID
},
{
indexes: [
{
unique: true,
fields: [SEAT_ID, SCREENING_ID]
}
],
underscored: true
}
);
module.exports = OCCUPIED_SEATS;
SEATS:
const OCCUPIED_SEATS = require("./Occupied_Seats");
const SEATS = sequelize.define("SEATS", {
SEATS_ID: {
type: Sequelize.INTEGER,
primaryKey: true,
allowNull: false,
autoIncrement: true
},
ROW: {
type: Sequelize.STRING,
allowNull: false,
},
COLUMN: {
type: Sequelize.INTEGER,
allowNull: false
},
},
{
underscored: true
}
);
SEATS.hasMany(OCCUPIED_SEATS, {foreignKey: 'SEAT_ID'})
module.exports = SEATS;
SCREENING:
const OCCUPIED_SEATS = require("./Occupied_Seats");
const SCREENING = sequelize.define("SCREENING", {
SCREENING_ID: {
type: Sequelize.INTEGER,
primaryKey: true,
allowNull: false,
autoIncrement: true
},
SCREENING_START_TIME: {
type: Sequelize.TIME,
allowNull: false,
},
DATE: {
type: Sequelize.DATE,
allowNull: false
}
},
{
underscored: true,
indexes: [
{
unique: true,
fields: [ROOM_ID, SCREENING_START_TIME, DATE]
}
]
}
);
SCREENING.hasMany(OCCUPIED_SEATS, {foreignKey: 'SCREENING_ID'});
module.exports = SCREENING;
The error I am getting when I try this is:
[💻] Error: SEATS.hasMany called with something that's not a subclass of Sequelize.Model
How should I code the model?
Looks like in the new version of Sequelize you have to define your models through Sequelize.Model type:
class Seats extends Sequelize.Model {}
Seats.init({
id: {
type: Sequelize.INTEGER,
primaryKey: true,
allowNull: false,
autoIncrement: true
},
row: {
type: Sequelize.STRING,
allowNull: false,
},
...
});
module.exports = Seats;
And then somewhere else:
Seats.hasMany(OccupiedSeatc, {foreignKey: 'SEAT_ID'})
See model definition docs and accociation docs.