sequelize hasMany, how to use customized foreginKey - javascript

Model EmployeeView
module.exports = function(sequelize, DataTypes) {
var _this = sequelize.define('EmployeeView', {
employeeId: {
type: DataTypes.INTEGER,
field: 'code'
},
username: DataTypes.STRING,
email: {
type: DataTypes.STRING,
field: 'emailaddress'
},
department: {
type: DataTypes.STRING,
field: 'department_name'
},
departmentId: {
type: DataTypes.STRING,
field: 'departments_id'
}
}, {
timestamps: false,
freezeTableName: true,
tableName: 'employees_view',
classMethods: {
associate: function(models) {
_this.belongsTo(models.EmployeeCategory, {
foreignKey: {
name: 'employeecategories_id'
}
});
_this.hasMany(models.EmployeeFile, {
foreignKey: 'employees_code'
});
}
}
});
return _this;
};
Model EmployeeFile
module.exports = function(sequelize, DataTypes) {
var _this = sequelize.define("EmployeeFile", {
employeeId: {
type: DataTypes.INTEGER,
field: 'employees_code'
},
filename: {
type: DataTypes.STRING,
filed: 'filename'
},
employeeFileTypeId: {
type: DataTypes.INTEGER,
field: 'employee_file_types_id'
}
}, {
timestamps: false,
freezeTableName: true,
tableName: 'employee_files'
});
return _this;
};
Router
router.get('/employee', function(req, res) {
models.EmployeeView.findAll({
where: {
active: req.query.active
}
include: [{
model: models.EmployeeCategory
}, {
model: models.EmployeeFile,
}]
}).then(function(employee) {
res.json(employee);
});
});
What do I expect to happen?
I have two tables 'employee_view' (it is a view) and 'employee_files' which map to the 'EmployeeView' and 'EmployeeFile'. 'employee_view' has 'id' field as the primary key and 'code' field as the employee number.'employee_files' has 'employees_code' as its primary key and foreignKey which bindings with the 'code' field. So I want to get 'employee_files' data through this relation.
What is actually happening?
Actually,I got nothing. Because the sequelize will execute "EmployeeView.id == EmployeeFile.employees_code". But I want the sequelize to execute "EmployeeView.code == EmployeeFile.employees_code" .What should I do?

just add primaryKey: true to your employeeId field to link the EmployeeView to EmployeeFiles since the belongsToMany relationship will only link to the primary key of it's parent
employeeId: {
type: sequelize.INTEGER,
field: 'code',
primaryKey: true
},

Related

How to find several fields of a foreign key in a join table in Node.js Sequelize

I have a Node.js application with Express, Sequelize as ORM and PostgreSQL for the database. In this app I have candidate model and mission model as below.
'use strict';
const {
Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class candidat extends Model {
static associate(models) {
this.belongsToMany(models.mission, {
through: "candidat_mission",
foreignKey: "candidatId",
otherKey: "idMission",
});
}
}
candidat.init({
candidatId: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
lastName: {
type: DataTypes.STRING,
allowNull: false,
},
firstName: {
type: DataTypes.STRING,
allowNull: false,
},
email: {
isEmail: true,
allowNull: false,
type: DataTypes.STRING,
unique: true,
},
}, {
sequelize,
modelName: 'candidat',
tableName: 'candidat',
freezeTableName: true,
});
return candidat;
};
'use strict';
const {
Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class mission extends Model {
static associate(models) {
this.belongsToMany(models.candidat, {
through: "candidat_mission",
foreignKey: "idMission",
otherKey: "candidatId",
})
}
}
mission.init({
idMission: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
title: {
type: DataTypes.STRING,
allowNull: false
},
aliasTitle: {
type: DataTypes.STRING,
allowNull: true
},
description: {
type: DataTypes.TEXT,
allowNull: true
}
}, {
sequelize,
modelName: 'mission',
tableName: 'mission',
freezeTableName: true,
});
return mission;
};
These two models are linked in many-to-many by a candidate_mission join table. In this model, I added fields like a foreign key which points to another table, that of users.
'use strict';
const {
Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class candidat_mission extends Model {
static associate(models) {
this.belongsTo(models.user, { foreignKey: "fk_user" });
}
}
candidat_mission.init({
candidatMissionId: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
candidatId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: { tableName: 'candidat' },
key: "candidatId",
},
},
idMission: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: { tableName: 'mission' },
key: "idMission",
},
},
fk_user: {
type: DataTypes.INTEGER,
allowNull: true,
references: {
model: { tableName: 'user' },
key: "userId",
},
},
}, {
sequelize,
modelName: 'candidat_mission',
tableName: 'candidat_mission',
timestamps: true,
freezeTableName: true,
});
return candidat_mission;
};
When I make a "GET" request, I do have the information from the candidate_mission table (if a candidate is linked to this mission), but for the user it only returns the ID and I would like it to return all the fields present in the Users model, what can I do?
Here, my function in the mission controller which allows to add a candidate to this mission :
const addCandidats = async (req, res) => {
try {
const mission = await Mission.findByPk(req.body.idMission);
if (mission) {
const candidat = await Candidat.findByPk(req.body.candidatId);
if (candidat) {
mission.addCandidat(candidat,
{through: {
fk_user: req.body.fk_user && req.body.fk_user
}});
return res.status(200).send(mission);
} else {
console.log("Candidat non trouvé");
return null;
}
} else {
console.log("Mission non trouvée!")
return null;
}
} catch (error) {
console.log(error);
}
};
Currently, my query returns me this :
"candidat_mission":
{
"candidatMissionId": 2,
"candidatId": 1,
"idMission": 7,
"fk_user": 1,
"createdAt": "2023-02-14T10:34:08.302Z",
"updatedAt": "2023-02-14T15:06:10.232Z"
},
And i want it to come back to me :
"candidat_mission":
{
"candidatMissionId": 2,
"candidatId": 1,
"idMission": 7,
"fk_user": {
"userId": 1,
"email": "blabla#gmail.com",
"name": "blabla"
},
"createdAt": "2023-02-14T10:34:08.302Z",
"updatedAt": "2023-02-14T15:06:10.232Z"
},
After associating 2 models, we have to query again to get the object along with the relationship.
await mission.addCandidat(candidat,
{through: {
fk_user: req.body.fk_user && req.body.fk_user
}});
const result = await CandidatMission.findOne({
where: { candidatId: req.body.candidatId, idMission: req.body.idMission },
include: models.user,
})
return res.status(200).send(result);

Why is Sequelize only returning one object using findAll()?

My goal is to be able to find all products by their brand name and model name. However, Sequelize is only returning one record out of many other similar records. If it does return more than one record, other records with identical attributes as the first record found will be null. For example, the first record in the array will have the attribute name: iPhone, the second record which has the exact same attribute will be shown as name: null when it should be name: iPhone.
In my database, I have the following tables:
Products, Brands, Models, and Suppliers. The Products table contains foreign keys such as brand_id, model_id, etc.. Brands, Models, and Suppliers have the attribute: id.
I have set the relationship up as the following:
Products.hasOne(Brands, { foreignKey: 'id' });
Products.hasOne(Models, { foreignKey: 'id' });
Products.hasOne(Suppliers, { foreignKey: 'id' });
Brands.belongsTo(Products);
Models.belongsTo(Products);
Suppliers.belongsTo(Products);
In my search function, I attempt to find all products by brand and model name that match my query.
const getSearch = (req, res) => {
const { query: { query } } = req;
Products.findAll({
where: Sequelize.where(Sequelize.fn('concat', Sequelize.col('Brand.name'), ' ', Sequelize.col('Model.name')), {
[Op.substring]: query
}),
include: [
{ model: Brands, attributes: ['name'] },
{ model: Models, attributes: ['name'] },
{ model: Suppliers, attributes: ['name'] },
],
attributes: ['id', 'price']
})
.then((data) => {
console.log(data);
res.send(data);
})
.catch((err) => (console.log(err)));
};
In my database, I have two product rows with the exact same data but different ids. When calling getSearch I expect to see two objects in the array as they have the same brand name and model name. Instead I see one.
Here's what my models look like:
Products
class Products extends Model {
static init(sequelize, DataTypes) {
return super.init(
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
url_id: {
type: DataTypes.INTEGER,
allowNull: true
},
brand_id: {
type: DataTypes.INTEGER,
allowNull: true
},
model_id: {
type: DataTypes.INTEGER,
allowNull: true
},
supplier_id: {
type: DataTypes.INTEGER,
allowNull: true
},
image: {
type: DataTypes.STRING,
allowNull: true
},
description: {
type: DataTypes.STRING,
allowNull: true
},
price: {
type: DataTypes.DOUBLE,
allowNull: true
}
},
{
modelName: 'Products',
timestamps: false,
sequelize
}
);
}
}
Models
class Models extends Model {
static init(sequelize, DataTypes) {
return super.init(
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING,
allowNull: true
},
colour_id: {
type: DataTypes.INTEGER,
allowNull: true
},
storage_capacity_id: {
type: DataTypes.INTEGER,
allowNull: true
}
},
{
modelName: 'Models',
timestamps: false,
sequelize
}
);
}
}
Brands
class Brands extends Model {
static init(sequelize, DataTypes) {
return super.init(
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING,
allowNull: true
}
},
{
modelName: 'Brands',
timestamps: false,
sequelize
}
);
}
}
Suppliers
class Suppliers extends Model {
static init(sequelize, DataTypes) {
return super.init(
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING,
allowNull: true
}
},
{
modelName: 'Suppliers',
timestamps: false,
sequelize
}
);
}
}
What am I doing wrong here?
You have an error in associations. Just change hasOne to hasMany and you are done.

fliped foreign key relations?

i have a strange effekt at a m:n relation..
this are the model definitions:
Role Model:
'use strict';
module.exports = (sequelize, DataTypes) => {
const Role = sequelize.define('Role', {
uuid: {
allowNull: false,
primaryKey: true,
unique: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
....
}, {});
/** #param models.User */
Role.associate = function(models) {
Role.belongsToMany(
models.User, {
through: 'user_role',
foreignKey: 'userId',
}
);
};
return Role;
};
User Model:
'use strict';
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
uuid: {
allowNull: false,
primaryKey: true,
unique: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
....
}, {});
/** #param models.Role */
User.associate = function(models) {
User.belongsToMany(
models.Role, {
through: 'user_role',
foreignKey: 'roleId',
}
);
};
return User;
};
the migration is the following:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('user', {
uuid: {
allowNull: false,
unique: true,
primaryKey: true,
type: Sequelize.UUIDV4,
defaultValue: Sequelize.UUIDV4,
},
....
}).then(() => {
queryInterface.createTable('role', {
uuid: {
allowNull: false,
unique: true,
primaryKey: true,
type: Sequelize.UUIDV4,
defaultValue: Sequelize.UUIDV4,
},
....
});
}).then(() => {
queryInterface.createTable('user_role', {
userId: {
type: Sequelize.UUIDV4,
references: {
model: 'User',
key: 'uuid',
},
allowNull: false,
},
roleId: {
type: Sequelize.UUIDV4,
references: {
model: 'Role',
key: 'uuid',
},
allowNull: false,
},
....
});
}).then(() => {
return queryInterface.addConstraint('user_role', ['UserId', 'RoleId'], {
unique: true,
type: 'primary key',
name: 'userrole_pkey',
});
});
},
down: (queryInterface, Sequelize) => {
....
},
};
if i try to insert now a user with a new role:
let models = require('../models');
models.Role.create({
role: 'Administrator',
description: 'Administrator Gruppe',
}).then(role => {
models.User.create({
login: 'admin',
password: '123',
nick: 'Admini',
mail: 'admin#localhost.com',
}).then(user => {
user.addRole(role);
user.save().then(() => {
console.log('admin created');
}).catch(err => {
console.log(err);
});
}).catch(err => {
console.log(err);
});
}).catch(err => {
console.log(err);
});
it tries to add the role uuid in the userid and the user uuid in the roleid.. and for that the constraint fails...
any hints or tips where i made a mistake?
found the mistake myself (with help of a college)
at
models.User, {
through: 'user_role',
foreignKey: 'userId',
}
i set the wrong foreign key, it's not the field in the helper table, it's needed to be the source table (in this case uuid of user model) or leave it blank for sequelize's default behaviour to use the primary key.

How can I achieve this SQL QUERY in sequelize (multiple joins and multiple AND/OR)

I've been struggling to achieve this (below SQL statement) in sequelize for a while now with no luck. I initially had to make separate sequelize queries to get the data but that just posed many limitations.
`SELECT "Documents".* FROM "Documents"
INNER JOIN "AccessTypes"
ON "AccessTypes"."id" = "Documents"."accessTypeId"
INNER JOIN "Users"
ON "Users"."id" = "Documents"."userId"
INNER JOIN "Departments"
ON "Departments"."id" = "Users"."departmentId"
WHERE
(("AccessTypes".name != 'private'
AND "Departments"."id" = ${req.decoded.departmentId})
OR "Users".id = ${req.decoded.id})
AND ("Documents"."title" ILIKE '%${searchQuery}%'
OR "Documents"."content" ILIKE '%${searchQuery}%'`
This is as far as I got
var dbQuery = {
where: {
$or: [
{
title: {
$iLike: `%${searchQuery}%`
}
},
{
content: {
$iLike: `%${searchQuery}%`
}
}
]
},
include: [{
model: db.Users,
where: { departmentId: req.decoded.departmentId }
},
{
model: db.AccessTypes,
where: { name: { $ne: 'private'}}
}]
};
db.Documents.findAll(dbQuery)
I still need to fetch another set of documents based on the userId supplied. I feel the way to go will be to perform an 'Include' within an '$or' statement. however my research so far leads me to believe that's not possible.
Here are my models
Access Types
export default (sequelize, DataTypes) => {
const AccessTypes = sequelize.define('AccessTypes', {
name: {
type: DataTypes.STRING,
allowNull: false,
isUnique: true
}
}, {
classMethods: {
associate: (models) => {
// associations can be defined here
AccessTypes.hasMany(models.Documents, {
foreignKey: 'accessTypeId',
onDelete: 'CASCADE'
});
}
}
});
return AccessTypes;
};
Users
export default (sequelize, DataTypes) => {
const Users = sequelize.define('Users', {
username: {
type: DataTypes.STRING,
unique: true,
allowNull: false
},
firstname: {
type: DataTypes.STRING,
allowNull: false
},
lastname: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
unique: true,
allowNull: false,
validate: {
isEmail: true
}
},
password: {
type: DataTypes.STRING,
allowNull: false
},
roleId: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 3
},
departmentId: {
type: DataTypes.INTEGER,
allowNull: false
}
}, {
classMethods: {
associate: (models) => {
// associations defined here
Users.belongsTo(models.Roles, {
onDelete: 'CASCADE',
foreignKey: 'roleId'
});
Users.belongsTo(models.Departments, {
onDelete: 'CASCADE',
foreignKey: 'departmentId'
});
Users.hasMany(models.Documents, {
as: 'documents',
foreignKey: 'userId',
onDelete: 'CASCADE'
});
}
}, ...
Departments
export default (sequelize, DataTypes) => {
const Departments = sequelize.define('Departments', {
name: {
type: DataTypes.STRING,
allowNull: false,
isUnique: true
}
}, {
classMethods: {
associate: (models) => {
// associations can be defined here
Departments.hasMany(models.Users, {
foreignKey: 'departmentId',
onDelete: 'CASCADE'
});
}
}
});
return Departments;
};
and Documents
export default (sequelize, DataTypes) => {
const Documents = sequelize.define('Documents', {
title: {
type: DataTypes.STRING,
allowNull: false
},
content: {
type: DataTypes.TEXT,
allowNull: false
},
userId: {
type: DataTypes.INTEGER,
allowNull: false
},
accessTypeId: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 1
},
docTypeId: {
type: DataTypes.INTEGER,
allowNull: false
}
}, {
classMethods: {
associate: (models) => {
// associations can be defined here
Documents.belongsTo(models.Users, {
foreignKey: 'userId',
as: 'user',
onDelete: 'CASCADE'
});
Documents.belongsTo(models.DocumentTypes, {
foreignKey: 'docTypeId',
onDelete: 'CASCADE'
});
Documents.belongsTo(models.AccessTypes, {
foreignKey: 'accessTypeId',
onDelete: 'CASCADE'
});
}
}
});
return Documents;
};
Any Pointers Will be greatly appreciated
Thanks in Advance
This is quite complex query (in Sequelize way of course), so you need to build it differently than you did. You should use functions like sequelize.col(), sequelize.where(), sequelize.or() and sequelize.and(). Moreover, in order to include the Departments model in the query, you need to use nested include statement in the options object of the findAll query. You can nest the includes as much as you want.
where: sequelize.and(
sequelize.or(
sequelize.and(
sequelize.where(sequelize.col('AccessTypes.name'), '!=', 'private'),
sequelize.where(sequelize.col('Departments.id'), '=', req.decoded.departmentId)
),
sequelize.where(sequelize.col('Users.id'), '=', req.decoded.id)
),
sequelize.or(
{ title: { $iLike: `%${searchQuery}%` } },
{ content: { $iLike: `%{searchQuery}%` } }
)
),
include: [
{
model: db.Users,
include: [ db.Departments ]
},
{
model: db.AccessTypes
}
]
You need to briefly get through documentation of above mentioned functions. In a short, col() creates a proper column selection basing on model name and field, where() creates WHERE statement with use of three attributes - column, condition (comparator) and logic, or() creates OR statement and and() creates AND statement. Both or() and and() can obtain multiple arguments that allows you to create complex statements, like yours.

On sequelize, "include" of "findOne" not working

I made a simple test that is to search for an address (id = 4) and retrieve the user who is linked to that address.
Here are my Models:
user.js
module.exports = function(sequelize, DataTypes) {
return sequelize.define('User', {
id: {
type: DataTypes.INTEGER(10).UNSIGNED,
allowNull: false,
field: 'id',
//primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
field: 'name',
},
}, {
freezeTableName: true,
tableName: 'user',
createdAt: false,
updatedAt: false,
classMethods: {
associate: function(models) {
models.User.hasMany(models.UserAddress, { foreignKey: 'userId' });
},
},
});
};
user_address.js
module.exports = function(sequelize, DataTypes) {
return sequelize.define('UserAddress', {
id: {
type: DataTypes.INTEGER(10).UNSIGNED,
allowNull: false,
field: 'id',
},
userId: {
type: DataTypes.INTEGER(10).UNSIGNED,
allowNull: false,
field: 'user_id',
},
title: {
type: DataTypes.STRING,
allowNull: true,
field: 'title',
},
address: {
type: DataTypes.STRING,
allowNull: true,
field: 'address',
},
}, {
freezeTableName: true,
tableName: 'user_address',
createdAt: false,
updatedAt: false,
classMethods: {
associate: function(models) {
models.UserAddress.hasOne(models.User, { foreignKey: 'id' });
},
},
});
};
And here is my test file:
db.UserAddress.findOne({
where: { id: 4 },
include: [db.User],
}).then(function(address) {
console.log('------------------------------ Address by "include"');
console.log('Address title: '+address.title);
console.log('User id: '+address.userId);
if(address.User !== null) {
console.log('User name: '+address.User.name);
} else {
console.log('User name: NO USER');
}
console.log('');
address.getUser().then(function(user) {
console.log('------------------------------ Address by "getUser"');
console.log('Address title: '+address.title);
console.log('User id: '+address.userId);
if(user !== null) {
console.log('User name: '+address.user.name);
} else {
console.log('User name: NO USER');
}
console.log('');
});
});
I do a query with two tests:
The first aims to recover the user directly via the variable "user", so thanks to "include" of the request.
And the other also retrieve the user but this time via "getUser()".
Here is the result:
$ node test.js
Executing (default): SELECT `UserAddress`.`id`, `UserAddress`.`user_id` AS `userId`, `UserAddress`.`title`, `UserAddress`.`address`, `User`.`id` AS `User.id`, `User`.`name` AS `User.name` FROM `user_address` AS `UserAddress` LEFT OUTER JOIN `user` AS `User` ON `UserAddress`.`id` = `User`.`id` WHERE `UserAddress`.`id`=4;
------------------------------ Address by "include"
Address title: Test
User id: 3
User name: NO USER
Executing (default): SELECT `id`, `name` FROM `user` AS `User` WHERE (`User`.`id`=4);
------------------------------ Address by "getUser"
Address title: Test
User id: 3
User name: NO USER
One can observe that it is impossible to retrieve the result via "include" and "getUser()".
The error is visible in the log of SQL:
"include": LEFT OUTER JOIN `user` AS `User` ON `UserAddress`.`id` = `User`.`id`
and
"getUser()": SELECT `id`, `name` FROM `user` AS `User` WHERE (`User`.`id`=4);
While the correct answer should have been:
"include": LEFT OUTER JOIN `user` AS `User` ON `UserAddress`.`user_id` = `User`.`id`
and
"getUser()": SELECT `id`, `name` FROM `user` AS `User` WHERE (`User`.`id`=3);
So my question is, what is the configuration to put in my Model or my request for the result to be correct with "include" and "getUser()" ?
Thank you.
(Also posted on: https://github.com/sequelize/sequelize/issues/3182)
Answer from the github page - need to use belongsTo instead of hasOne.
user.js
module.exports = function(sequelize, DataTypes) {
return sequelize.define('User', {
id: {
type: DataTypes.INTEGER(10).UNSIGNED,
allowNull: false,
field: 'id',
//primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
field: 'name',
},
}, {
freezeTableName: true,
tableName: 'user',
createdAt: false,
updatedAt: false,
classMethods: {
associate: function(models) {
models.User.hasMany(models.UserAddress, { foreignKey: 'userId' });
},
},
});
};
user_address.js
module.exports = function(sequelize, DataTypes) {
return sequelize.define('UserAddress', {
id: {
type: DataTypes.INTEGER(10).UNSIGNED,
allowNull: false,
field: 'id',
},
userId: {
type: DataTypes.INTEGER(10).UNSIGNED,
allowNull: false,
field: 'user_id',
},
title: {
type: DataTypes.STRING,
allowNull: true,
field: 'title',
},
address: {
type: DataTypes.STRING,
allowNull: true,
field: 'address',
},
}, {
freezeTableName: true,
tableName: 'user_address',
createdAt: false,
updatedAt: false,
classMethods: {
associate: function(models) {
models.UserAddress.belongsTo(models.User, { foreignKey: 'userId' });
},
},
});
};

Categories

Resources