I have two tables Employee and Department
Department
const Department = Sequelize.define(
"Department",
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
},
{
underscored: true,
timestamps: true,
paranoid: true,
modelName: "Department",
tableName: "departments",
},
);
Department.associate = function (models) {
// associations can be defined here
models.Department.hasMany(models.Employee, {
foreignKey: "department_id",
as: "employees",
});
};
return Department;
};
Employee
const Employee = Sequelize.define(
"Employee",
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
unique: true,
allowNull: false,
},
status: {
type: DataTypes.STRING,
defaultValue: "active",
},
departmentId: {
type: DataTypes.INTEGER,
},
},
{
underscored: true,
timestamps: true,
modelName: "Employee",
tableName: "employees",
},
);
Employee.associate = function (models) {
models.Employee.belongsTo(models.Department, {
foreignKey: "department_id",
as: "department",
});
};
return Employee;
};
Now I have to fetch the list of employees and putting a filter of department_id = 1
const { departmentId } = req.body;
const employees = await Employee.findAll({
include: [
{
model: Department,
where: {
id: departmentId,
},
},
],
});
I am getting the issue. Department is mapped by association "departments"
Cannot fetch the data.
I found the answer on sequelize docs
const employees = await Employee.findAll({
include: [
{
association: "department", // this is the place to change
where: {
id: departmentId,
},
},
],
});
Learnings:
We will not be able to put association and model together.
We will be able to use the Model if no association is there.
We will be able to use association if there is one.
References: https://sequelize.org/master/manual/eager-loading.html#:~:text=You%20can%20also%20include%20by%20alias%20name%20by%20specifying%20a%20string%20that%20matches%20the%20association%20alias
Related
I only found following in Sequelize documentation, but it's not possible to understand how to fetch associations properly.
Person.belongsToMany(Person, { as: 'Children', through: 'PersonChildren' })
// This will create the table PersonChildren which stores the ids of the objects.
This is the implementation.
const sequelizePsqlConfig = new Sequelize(
"postgres://Kavinda Vindika#localhost:5432/hrm_users"
);
export const User = sequelizePsqlConfig.define(
"users",
{
userId: {
field: "user_id",
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
userName: {
field: "user_name",
type: DataTypes.STRING,
},
email: { type: DataTypes.STRING, key: "email" },
department: { type: DataTypes.STRING, key: "department" },
designation: { type: DataTypes.STRING, key: "designation" },
},
{
timestamps: false,
}
);
export const User_Associations = sequelizePsqlConfig.define(
"user_associations",
{
id: {
field: "id",
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
managerId: {
field: "manager_id",
type: DataTypes.INTEGER,
},
subordinateId: {
field: "subordinate_id",
type: DataTypes.INTEGER,
},
},
{
modelName: "user_associations",
timestamps: false,
tableName: "user_associations",
}
);
User.belongsToMany(User, {
as: "manager",
through: "user_associations",
foreignKey: "manager_id",
otherKey: "subordinate_id",
});
User.belongsToMany(User, {
as: "subordinate",
through: "user_associations",
foreignKey: "subordinate_id",
otherKey: "manager_id",
});
Then I tried to fetch the data with manager and subordinate details.
public async getAllUsers() {
return User.findAll({
include: ["manager", "subordinate"],
});
}
But it failed with following logs. Please assist me on this issue.
[1] Executing (default): SELECT "users"."user_id" AS "userId", "users"."user_name" AS "userName", "users"."email", "users"."department", "users"."designation", "manager"."user_id" AS "manager.userId", "manager"."user_name" AS "manager.userName", "manager"."email" AS "manager.email", "manager"."department" AS "manager.department", "manager"."designation" AS "manager.designation", "manager->user_associations"."id" AS "manager.user_associations.id", "manager->user_associations"."manager_id" AS "manager.user_associations.managerId", "manager->user_associations"."subordinate_id" AS "manager.user_associations.subordinateId", "manager->user_associations"."manager_id" AS "manager.user_associations.manager_id", "manager->user_associations"."subordinate_id" AS "manager.user_associations.subordinate_id", "subordinate"."user_id" AS "subordinate.userId", "subordinate"."user_name" AS "subordinate.userName", "subordinate"."email" AS "subordinate.email", "subordinate"."department" AS "subordinate.department", "subordinate"."designation" AS "subordinate.designation", "subordinate->user_associations"."id" AS "subordinate.user_associations.id", "subordinate->user_associations"."manager_id" AS "subordinate.user_associations.managerId", "subordinate->user_associations"."subordinate_id" AS "subordinate.user_associations.subordinateId", "subordinate->user_associations"."manager_id" AS "subordinate.user_associations.manager_id", "subordinate->user_associations"."subordinate_id" AS "subordinate.user_associations.subordinate_id" FROM "users" AS "users" LEFT OUTER JOIN ( "user_associations" AS "manager->user_associations" INNER JOIN "users" AS "manager" ON "manager"."user_id" = "manager->user_associations"."subordinate_id") ON "users"."user_id" = "manager->user_associations"."manager_id" LEFT OUTER JOIN ( "user_associations" AS "subordinate->user_associations" INNER JOIN "users" AS "subordinate" ON "subordinate"."user_id" = "subordinate->user_associations"."manager_id") ON "users"."user_id" = "subordinate->user_associations"."subordinate_id";
[1] TypeError: Class constructor model cannot be invoked without 'new'
When I try to insert new category, I got this error:
error: column "image" does not exist
sql: 'INSERT INTO "Categories" ("id","createdAt","updatedAt") VALUES (DEFAULT,$1,$2) RETURNING "id","image","title","createdAt","updatedAt";'
The problem is that it doesn't insert name and other values and returns columns belong to post table.
My guesses are the problem of sequelize-cli and sequelize version or missing something in models or migrations.
I only insert values into name, createdAt and updatedAt column:
await Category.create({
name: req.body.name,
createdAt: new Date(),
updatedAt: new Date()
});
My category model:
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
class Category extends Model {
static associate(models) {
Category.hasMany(models.Post, { as: "posts", foreignKey: "categoryId" });
}
}
Category.init(
{
name: DataTypes.STRING
},
{
sequelize,
modelName: "Category"
}
);
return Category;
};
My Post Model:
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
class Post extends Model {
static associate(models) {
Post.belongsTo(models.Category, { foreignKey: "categoryId", onDelete: "CASCADE", as: "category" });
}
}
Post.init(
{
title: DataTypes.STRING,
image: DataTypes.STRING,
content: DataTypes.TEXT,
categoryId: DataTypes.INTEGER
},
{
sequelize,
modelName: "Post"
}
);
return Post;
};
Post migration:
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.createTable("Posts", {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
title: {
type: Sequelize.STRING
},
image: {
type: Sequelize.STRING
},
content: {
type: Sequelize.TEXT
},
categoryId: {
type: Sequelize.INTEGER,
allowNull: false,
onDelete: "CASCADE",
references: {
model: "Categories",
key: "id"
}
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
Category migration:
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.createTable("Categories", {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
I couldn't find solution for this, therefor I used sequelize.query
The problem:
Whenever I fetch a user, I always have to declare/include the association on the query to get its role:
const user = await DB.PORTALDB.models.AccessUser.findOne({
where: { email },
include: [ // EVERY QUERY, I HAVE TO INCLUDE THIS
{
model: DB.PORTALDB.models.AccessUserRoleLup,
as: 'role'
}
]
});
Now there are instance where I forget to include this association so I get a undefined role.
My question is, is there a way where I only set this association once so that I don't have to include this later on my queries?
This the model for my AccessUser table
const AccessUser = <AccessUserStatic>sequelize.define<AccessUserInstance>(
'AccessUser',
{
user_id: {
autoIncrement: true,
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
primaryKey: true
},
email: {
type: DataTypes.STRING(255),
allowNull: false
},
firstname: {
type: DataTypes.STRING(255),
allowNull: false
},
lastname: {
type: DataTypes.STRING(255),
allowNull: false
},
password: {
type: DataTypes.STRING(255),
allowNull: false
},
disable: {
type: DataTypes.TINYINT,
allowNull: false,
defaultValue: 0
},
role_id: {
type: DataTypes.SMALLINT,
allowNull: false,
defaultValue: 0
},
created_modified: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
}
}, {
tableName: 'access_user',
timestamps: false,
indexes: [
{
name: "PRIMARY",
unique: true,
using: "BTREE",
fields: [
{ name: "user_id" },
]
},
]
});
AccessUserRoleLup table
const AccessUserRoleLup = <AccessUserRoleLupStatic>sequelize.define<AccessUserRoleLupInstance>(
'AccessUserRoleLup',
{
role_id: {
autoIncrement: true,
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
primaryKey: true
},
role_name: {
type: DataTypes.STRING(50),
allowNull: false
},
role_code: {
type: DataTypes.CHAR(50),
allowNull: false,
defaultValue: ""
}
}, {
tableName: 'access_user_role_lup',
timestamps: false,
indexes: [
{
name: "PRIMARY",
unique: true,
using: "BTREE",
fields: [
{ name: "role_id" },
]
},
]
});
Association:
db.models.AccessUser.hasOne(db.models.AccessUserRoleLup, {
foreignKey: 'role_id',
as: 'role'
});
Use defaultScope for AccessUser. defaultScope is defined in a model definition and it is always applied (unless you removed inline).
const AccessUser = <AccessUserStatic>sequelize.define<AccessUserInstance>(
'AccessUser',
{
user_id: {
autoIncrement: true,
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
primaryKey: true
},
...
}, {
tableName: 'access_user',
timestamps: false,
defaultScope: { // Add this
include: [{
model: AccessUserRoleLup,
as: 'role'
}]
},
...
});
With this model definition, all queries will include AccessUserRoleLup.
If you would like to remove for a certain query, use .unscoped().
// These will automatically add eager loading for role
DB.PORTALDB.models.AccessUser.findAll()
DB.PORTALDB.models.AccessUser.findOne()
// These won't fetch role
DB.PORTALDB.models.AccessUser.unscoped().findAll()
DB.PORTALDB.models.AccessUser.unscoped().findOne()
More detail about scope: https://sequelize.org/master/manual/scopes.html
My initial workaround was to create a utility function for querying the user like so:
export const getAccessUser = (where: WhereOptions, include?: IncludeOptions) => {
return new Promise(async (resolve, reject) => {
try {
const user = await DB.PORTALDB.models.AccessUser.findOne({
where: where,
include: [
{
model: DB.PORTALDB.models.AccessUserRoleLup,
as: 'role'
},
...[include]
]
});
resolve(user);
} catch (err) {
reject(err);
}
});
}
I wonder if my question above can be done in much simpler way.
I am new with sequelize and i need to include multiple table, i try belove:
const result = await product.findAll({
where: {
isDeleted: false,
sellerId
},
attributes: ["name", "type", "isActive", "subCategoryIds"],
include: [
{
model: category,
as: 'categories',
attributes: ["name"],
},
{
model: category,
as: 'subCategories',
attributes: ["name"],
where: {
id: _.split(sequelize.col("subCategoryIds"), ",")
},
},
],
offset: (page - 1) * limit,
limit
});
But it's return error :
SequelizeDatabaseError: operator does not exist: character varying = integer
Product model is:
const product = sequelize.define("products",
{
id: {
type: DataTypes.BIGINT.UNSIGNED,
field: "id",
primaryKey: true,
autoIncrement: true,
index: true,
},
name: {
type: DataTypes.STRING,
field: "name",
allowNull: false,
},
type: {
type: DataTypes.STRING,
field: "type",
allowNull: false,
},
categoryId: {
type: DataTypes.NUMBER,
field: "categoryId",
references: {
key: "id",
model: category,
},
allowNull: false,
},
sellerId: {
type: DataTypes.NUMBER,
field: "sellerId",
references: {
key: "id",
model: user,
},
allowNull: false,
},
subCategoryIds: {
type: DataTypes.STRING,
allowNull: false,
get() {
const result = _.split(this.getDataValue("subCategoryIds"), ",").map(item => +item);
return result;
}
},
isDeleted: {
type: DataTypes.BOOLEAN,
field: "isDeleted",
defaultValue: false,
},
createdAt: {
type: DataTypes.DATE,
field: "createdAt",
defaultValue: DataTypes.NOW,
},
updatedAt: {
type: DataTypes.DATE,
field: "updatedAt",
defaultValue: DataTypes.NOW,
}
}, {
tableName: "products",
timestamps: false
}
);
product.belongsTo(category, { as: "categories", foreignKey: 'categoryId' });
category.hasMany(product, { as: "products", foreignKey: 'categoryId' });
product.belongsTo(category, { as: "subCategories", foreignKey: 'subCategoryIds' });
I am not getting what's going wrong
is there any solution for this
details in table like
categoryId => 11 subCategoryIds => 11, 12
if i remove
{
model: category,
as: 'subCategories',
attributes: ["name"],
where: {
id: _.split(sequelize.col("subCategoryIds"), ",")
},
},
then it's working fine, problem is sequelize.col("subCategoryIds") return string col("subCategoryIds") not the actual value of the subCategories ids 11, 12
I have a case where I am querying information from two tables that have a many-to-many relationship with a "through" table. When I make my query it appears that I am querying correctly by not using the "through" table as the table join reference and receiving the outputted records with both table attributes, but I am unable to access the field properties of the joined table. Here is the outputted values.
{"fullNameSlug":"Tester Test","email":"test#test.com","firstName":"Tester","lastName":"Test","teams":[{"teamName":"Sales","member":{"memberId":1,"memberEmail":"test#test.com","organizationId":1,"teamId":1,"userId":1,"created_at":"2016-08-21T21:15:19.000Z","updated_at":"2016-08-21T22:00:32.000Z","organization_id":1,"team_id":1,"user_id":1}}]}
Here is my query and how I am setting the data:
.get(function(req, res){
models.User.find({
where: {
organizationId: organization.organizationId
}, attributes: ['email', 'firstName', 'lastName'],
include: [{
model: models.Team,
attributes: ['teamName']
}]
});
}).then(function(currentUsers){
res.jsonp(currentUsers);
console.log(currentUsers);
});
Here is how I was trying to access the teamName in my view: {{currentUsers.teams.teamName}}, which is not returning a value, but {{currentUsers.email}} returns the right user email.
User Table:
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define('user', {
userId: {
type: DataTypes.INTEGER,
field:'user_id',
autoIncrement: true,
primaryKey: true
},
firstName: {
type: DataTypes.STRING,
field: 'first_name'
},
lastName: {
type: DataTypes.STRING,
field: 'last_name'
},
email: {
type: DataTypes.STRING,
isEmail: true,
unique: true,
set: function(val) {
this.setDataValue('email', val.toLowerCase());
}
},
password: DataTypes.STRING,
organizationId: {
type: DataTypes.INTEGER,
field: 'organization_id',
allowNull: true
}
}, {
underscored: true,
freezeTableName: true,
},
classMethods: {
associate: function(db) {
User.belongsToMany(db.Organization, { through: 'member', foreignKey: 'user_id'}),
User.belongsToMany(db.Team, { through: 'member', foreignKey: 'user_id'})
}
});
return User;
}
Team table:
module.exports = function(sequelize, DataTypes) {
var Team = sequelize.define('team', {
teamId: {
type: DataTypes.INTEGER,
field: 'team_id',
autoIncrement: true,
primaryKey: true,
notNull: true
},
teamName: {
type: DataTypes.STRING,
field: 'team_name'
},
organizationId: {
type: DataTypes.INTEGER,
field: 'organization_id'
},
},{
underscored: true,
freezeTableName: true,
classMethods: {
associate: function(db) {
Team.belongsToMany(db.User, { through: 'member', foreignKey: 'team_id' });
},
}
});
return Team;
}
Member Table:
module.exports = function(sequelize, DataTypes) {
var Member = sequelize.define('member', {
memberId: {
type: DataTypes.INTEGER,
field: 'member_id',
autoIncrement: true,
primaryKey: true
},
memberEmail: {
type: DataTypes.STRING,
field: 'member_email',
isEmail: true,
unique: true
},
organizationId: {
type: DataTypes.INTEGER,
field: 'organization_id',
allowNull: true
},
teamId: {
type: DataTypes.INTEGER,
field: 'team_id',
allowNull: true
},
userId: {
type: DataTypes.INTEGER,
field: 'user_id',
allowNull: true
}
},{
underscored: true,
freezeTableName: true,
});
return Member;
}
Outputted SQL:
SELECT `user`.*, `teams`.`team_id` AS `teams.teamId`, `teams`.`team_name` AS `teams.teamName`, `teams.member`.`member_id` AS `teams.member.memberId`, `teams.member`.`member_email` AS `teams.member.memberEmail`, `teams.member`.`organization_id` AS `teams.member.organizationId`, `teams.member`.`team_id` AS `teams.member.teamId`, `teams.member`.`user_id` AS `teams.member.userId`, `teams.member`.`created_at` AS `teams.member.created_at`, `teams.member`.`updated_at` AS `teams.member.updated_at`, `teams.member`.`organization_id` AS `teams.member.organization_id`, `teams.member`.`team_id` AS `teams.member.team_id`, `teams.member`.`user_id` AS `teams.member.user_id` FROM (SELECT `user`.`user_id` AS `userId`, `user`.`email`, `user`.`first_name` AS `firstName`, `user`.`last_name` AS `lastName` FROM `user` AS `user` WHERE `user`.`organization_id` = 1 LIMIT 1) AS `user` LEFT OUTER JOIN (`member` AS `teams.member` INNER JOIN `team` AS `teams` ON `teams`.`team_id` = `teams.member`.`team_id`) ON `user`.`userId` = `teams.member`.`user_id`;
Consider your relations, User has many Teams trough table Member and your query returns user with many teams(array of team objects) as expected. You should use user.teams[0].teamName to get specific team by key, or loop objects in this array