I come from this topic:
NodeJS & Sequelize: How can I join 3 models?
I come to you now, because I need someone who can teach me about how to get started on junction models with NodeJS and Sequelize.
What I'm trying to do is, having 2 main models, for example, Employee and Office, a 3rd model called EmployeeOffice enters and connects the 2 main models, including its own field called "employee_chair". When i call a method, for example, Office.findAll, this is what I would get:
{
"id": 1,
"office_name": "Mars"
"office_color": "Red",
"createdAt": "2021-09-30T18:53:38.000Z",
"updatedAt": "2021-09-30T18:53:38.000Z",
"employees": [
{
"id": 1,
"employee_name": "Albert",
"employee_mail": "qalbert443#gmail.com",
"createdAt": "2021-09-30T18:53:45.000Z",
"updatedAt": "2021-09-30T18:53:45.000Z",
"employee_office": {
"createdAt": "2021-09-30T18:53:45.000Z",
"updatedAt": "2021-09-30T18:53:45.000Z"
}
}
]
}
NOW. What I need, is to have the model called instead of the junction table that is automatically created. Because I can join the 3 tables, but the 3rd table has the field "employee_chair", that I mentioned earlier. The desired response would look like this:
{
"id": 1,
"office_name": "Mars"
"office_color": "Red",
"createdAt": "2021-09-30T18:53:38.000Z",
"updatedAt": "2021-09-30T18:53:38.000Z",
"employees": [
{
"id": 1,
"employee_name": "Albert",
"employee_mail": "qalbert443#gmail.com",
"createdAt": "2021-09-30T18:53:45.000Z",
"updatedAt": "2021-09-30T18:53:45.000Z",
"employee_office": {
"employee_chair": 3,
"createdAt": "2021-09-30T18:53:45.000Z",
"updatedAt": "2021-09-30T18:53:45.000Z"
}
}
]
}
How can I do to make (or force) sequelize to make the relations through the model and not through the automatically created table?
Hope you guys can help me, I'm stuck and I don't know where to ask
#cupid22 Here is my index.js, userproject model and the controller function im calling:
index.js:
const dbConfig = require("../config/db.config.js");
const Sequelize = require("sequelize");
const sequelize = new Sequelize(dbConfig.DB, dbConfig.USER, dbConfig.PASSWORD, {
host: dbConfig.HOST,
dialect: dbConfig.dialect,
operatorsAliases: false,
pool: {
max: dbConfig.pool.max,
min: dbConfig.pool.min,
acquire: dbConfig.pool.acquire,
idle: dbConfig.pool.idle
}
});
const db = {};
db.Sequelize = Sequelize;
db.sequelize = sequelize;
db.projects = require("./project.model.js")(sequelize, Sequelize);
db.users = require("./user.model.js")(sequelize, Sequelize);
db.projects.belongsToMany(db.users, {
through: "users_projects",
as: "users",
foreignKey: "user_id",
});
db.users.belongsToMany(db.projects, {
through: "users_projects",
as: "projects",
foreignKey: "project_id",
});
module.exports = db;
Controller function:
// Retrieve all Projects from the database.
exports.findAll = (req, res) => {
const title = req.query.title;
var condition = title ? { title: { [Op.like]: `%${title}%` } } : null;
Project.findAll({
include: [
{
model: User,
as: "users",
}]
})
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while retrieving projects."
});
});
};
UserProject Model:
module.exports = (sequelize, Sequelize) => {
const UserProject = sequelize.define('user_project', {
user_id: {
type: Sequelize.INTEGER,
references: {
model: 'user',
key: 'id'
}
},
project_id: {
type: Sequelize.INTEGER,
references: {
model: 'project',
key: 'id'
}
},
user_type: {
type: Sequelize.INTEGER,
}
}, {
timestamps: false,
tableName: 'users_projects'
});
db.users.belongsToMany(db.projects, { through: User_Profile });
db.projects.belongsToMany(db.users, { through: User_Profile });
return UserProject;
};
Related
i use Sequelize
what i want search userId from another model
when i add "task_customer_order.userId" i got error but when i remove it only userId it's work but foreign key of userId in Comment model not need it i want in task_customer_order model
my code :
count_comment:async(req,res)=>{
var Sequelize = require('sequelize');
const id = req.params.id;
await Comment.findAll({
attributes: {
include: [[Sequelize.fn("COUNT", Sequelize.col('comment.publish')), "count_Comment"]],
},
include: ['task_customer_order'],
where:[{"publish":1,"task_customer_order.userId":{[Op.like]:id}}],
}).then(data => {
res.send(data)
})
},
error:
UnhandledPromiseRejectionWarning: SequelizeDatabaseError: Unknown column 'comment.task_customer_order.userId' in 'where clause'
output without userId:
{
"id": 1,
"title_body": "1",
"publish": true,
"createdAt": "2021-01-04T17:34:46.000Z",
"updatedAt": "2021-01-04T17:57:52.000Z",
"taskCustomerOrderId": 1,
"userId": 2, <============= i don't need it
"count_Comment": 6,
"task_customer_order": {
"id": 1,
"title_task": "yy",
"description": "",
"endAt": "2021-02-12T21:00:00.000Z",
"duration": "09-02",
"publish": true,
"createdAt": "2021-01-04T17:32:47.000Z",
"updatedAt": "2021-01-04T21:49:15.000Z",
"customerOrderId": 1,
"userId": 1 <==================== i want this
}
}
If you would like to filter by userId in joined task_customer_order - you need to put condition inside include. Try the next:
await Comment.findAll({
attributes: {
include: [[Sequelize.fn("COUNT", Sequelize.col('comment.publish')), "count_Comment"]],
},
include: [{ model: TaskCustomerOrder, where:{userId: {[Op.like]:id}}}],
where:{"publishâ:1},
}).then(data => {
res.send(data)
})
here Code After solving :
count_comment:async(req,res)=>{
var Sequelize = require('sequelize');
const id = req.params.id;
await Comment.findAll({
attributes: {
include: [[Sequelize.fn("COUNT", Sequelize.col('comment.publish')), "count_Comment"]],
},
include: [{ model: TaskCustomerOrder,as:'task_customer_order', where:{'userId': {[Op.like]:id}}}],
where:{publish:1},
}).then(data => {
res.send(data)
})
},
I have created two associated tables (Account, AccountCLI) using SEQUELIZE in Nodejs. Account table has many association with AccountCLI table. Account table has column(userid, eppusername, username, vendorsparameter, projectid). AccountCLI table has column(phonenumber, userid(ForeignKey)). When a user enter a phonenumber, it will return the corresponding userdata. In my code, it returns all the user data rather than specific user with the phone number. You can find the code below. Please give some suggestion?
The tables entries are shown as:
'use strict';
module.exports = (sequelize, DataTypes) => {
const Account = sequelize.define('Account', {
epprojectname: DataTypes.STRING,
username: DataTypes.STRING,
projectid: DataTypes.STRING,
vendorparameters: DataTypes.STRING,
credentials: DataTypes.STRING
}, {});
Account.associate = function(models) {
Account.hasMany(models.AccountCLI, {
as: 'accountcli',
foreignKey: 'userid'
});
};
return Account;
};
'use strict';
module.exports = (sequelize, DataTypes) => {
const AccountCLI = sequelize.define('AccountCLI', {
phonenumber: DataTypes.STRING,
userid: DataTypes.INTEGER
}, {});
AccountCLI.associate = function(models) {
AccountCLI.belongsTo(models.Account, {
as: "account",
foreignKey: 'userid'
});
};
return AccountCLI;
};
The code (that suppose to retrieve specific user data) retrieving all the user data when entered a phone number value are shown as (This is the code after the suggestions):
// find account with specific number
exports.findSpecificUser = async (req, res) => {
var whereStatement = {};
if(req.body.phonenumber)
whereStatement['$accountcli.phonenumber$'] = {$like: '%' + req.body.phonenumber + '%'};
Account.findAll({ include: { model: AccountCLI, as: "accountcli", where: whereStatement } })
.then((data) => {
res.send(data);
})
.catch((err) => {
res.status(500).send({
message:
err.message || "Some error occurred while retrieving account with the specific phonenumber.",
});
});
};
This is the JSON file which I retrieved using POSTMAN. Here I put (phonenumber, 3334) which suppose to retrieve ("epprojectname": "DFSumitayayabot") userdata, but it retrieves both of the user info.
[
{
"id": 68,
"epprojectname": "DFSumitayayabot",
"username": "Sumit",
"projectid": "ayayabot",
"vendorparameters": "newparameters",
"credentials": "1589956379476-tslint.json",
"createdAt": "2020-05-19T15:23:00.440Z",
"updatedAt": "2020-05-20T06:36:05.903Z",
"accountcli": [
{
"id": 227,
"phonenumber": "33344",
"userid": 68,
"createdAt": "2020-05-20T06:36:05.997Z",
"updatedAt": "2020-05-20T06:36:05.997Z"
},
{
"id": 228,
"phonenumber": " 447467",
"userid": 68,
"createdAt": "2020-05-20T06:36:05.997Z",
"updatedAt": "2020-05-20T06:36:05.997Z"
}
]
},
{
"id": 67,
"epprojectname": "DFZeyadavayachatbot",
"username": "Zeyad",
"projectid": "avayachatbot",
"vendorparameters": "{\"synth_speech_cfg\": { \"voice\": { \"name\": \"en-AU-Wavenet-C\"}}}",
"credentials": "1589958578216-AppointmentType.json",
"createdAt": "2020-05-19T15:17:43.399Z",
"updatedAt": "2020-05-20T07:09:38.228Z",
"accountcli": [
{
"id": 249,
"phonenumber": "44433",
"userid": 67,
"createdAt": "2020-05-20T07:09:38.332Z",
"updatedAt": "2020-05-20T07:09:38.332Z"
},
{
"id": 250,
"phonenumber": " 5566",
"userid": 67,
"createdAt": "2020-05-20T07:09:38.332Z",
"updatedAt": "2020-05-20T07:09:38.332Z"
}
]
}
]
The phonenumber field is in AccountCLI model but you added a condition with the field in the Account query.
Try something like this:
if(req.body.phonenumber)
whereStatement['$accountcli.phonenumber$'] = {$like: '%' + req.body.phonenumber + '%'};
I have 3 Tables User, Cars and UserCars
User{id, name, phone, email}
Cars{id, name, manufacturer}
UserCars{id, car_id, user_id, role}
User have many cars(through UserCars)
Cars have many users(through UserCars)
I am using express js
router.get('/', async (req, res) => {
try {
let car = await Car.findOne({
where: {
id: req.car_id
}});
let users = await car.getUsers({joinTableAttributes: ['role']})
res.send(users)
} catch (e) {
console.log(e)
res.status(400).send(e)
}
})
and this my response
[
{
"id": 1,
"name": "test",
"email": null,
"phone": null,
"createdAt": "2019-07-09T09:38:11.859Z",
"updatedAt": "2019-07-12T04:34:20.922Z",
"User_car": {
"role": "driver"
}
}
]
but any idea how to include role in the user object, rather then specifying it separately in User_car table,
Is there a way where i can get the below output
[
{
"id": 1,
"name": "test",
"email": null,
"phone": null,
"role": 'driver'
"createdAt": "2019-07-09T09:38:11.859Z",
"updatedAt": "2019-07-12T04:34:20.922Z"
}
]
You can use sequelize.literal to get that field when getting your attributes.
attributtes: [
// define your other fields
[sequelize.literal('`users->User_car`.`role`'), 'role'],
]
Now, I not sure if that is going to work with car.getUsers. I usually do a single query with include and also define the "join" table so I can know how is name it on sequelize. Let me show you an example.
module.exports = (sequelize, DataTypes) => {
const UserCar = sequelize.define('UserCar', {
// id you don't need and id field because this is a N:M relation
role: {
type: DataTypes.STRING
},
carId: {
field: 'car_id',
type: DataTypes.INTEGER
},
userId: {
field: 'user_id',
type: DataTypes.INTEGER
},
}, {
tableName: 'User_car',
underscored: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
});
UserCar.associate = (models) => {
models.user.belongsToMany(models.car, { as: 'cars', through: User_car, foreignKey: 'user_id' });
models.car.belongsToMany(models.user, { as: 'users', through: User_car, foreignKey: 'car_id' });
};
return UserCar;
};
router.get('/', async (req, res) => {
try {
const users = await User.findAll({
include: [{
model: Car,
as: 'cars',
where: { id: req.car_id }
}],
attributtes: [
'id',
'name',
'email',
'phone',
[sequelize.literal('`cars->User_car`.`role`'), 'role'],
]
})
res.send(users)
} catch (e) {
console.log(e)
res.status(400).send(e)
}
});
I want this result, but, is so complicated do to this in this way. There is a better way to create this result using Sequelize. Using the querys tools of sequelize to aggregate results from diferent tables, on JPA the only thing i do is annotate with join table and pass the columns ad invese columns values.
[
{
"id": 1,
"codemp": "999",
"nome": "A3 Infortech",
"limiteInstancias": "10",
"instancias": []
},
{
"id": 2,
"codemp": "92",
"nome": "Endovideo",
"limiteInstancias": "20",
"instancias": [
{
"id": 198211,
"ipLocal": "40.0.10.11",
"ipExterno": "187.33.230.106",
"hostname": "FATURAMENTO-PC",
"dataCriacao": "2019-07-01T21:40:29.000Z"
}
]
},
{
"id": 6,
"codemp": "103",
"nome": "SOS Otorrino",
"limiteInstancias": "999",
"instancias": [
{
"id": 199127,
"ipLocal": "192.168.11.101",
"ipExterno": "000.000.000.000",
"hostname": "Guiche3-PC",
"dataCriacao": "2019-07-01T21:40:32.000Z"
},
{
"id": 199164,
"ipLocal": "192.168.25.209",
"ipExterno": "000.000.000.000",
"hostname": "Consultorio06",
"dataCriacao": "2019-07-01T21:40:29.000Z"
}
]
},
{
"id": 15,
"codemp": "162",
"nome": "Clinica Vida e Saude",
"limiteInstancias": "10",
"instancias": [
{
"id": 199774,
"ipLocal": "192.168.56.1",
"ipExterno": "000.000.000.000",
"hostname": "ALEXANDRELEAL",
"dataCriacao": "2019-07-01T21:40:28.000Z"
}
]
}
]
I have this codes:
Empresa Model
module.exports = (sequelize, DataTypes) => {
const empresa = sequelize.define("empresa", {
id: {
type: DataTypes.BIGINT(20),
primaryKey: true,
field: "id"
},
codemp: {
type: DataTypes.INTEGER,
field: "codemp"
},
nome: {
type: DataTypes.STRING,
field: "nome"
},
limiteInstancias: {
type: DataTypes.INTEGER,
field: "limite_instancias"
}
}, {
timestamps: false,
freezeTableName: true,
tableName: "empresa"
});
empresa.associate = (db) => {
console.log(db);
empresa.hasMany(db.instanciaEmpresa, {foreignKey: "id_empresa"});
};
return empresa;
};
Instancia Model
module.exports = (sequelize, DataTypes) => {
const instancia = sequelize.define("instancia", {
id: {
type: DataTypes.BIGINT(20),
primaryKey: true,
field: "id"
},
ipLocal: {
type: DataTypes.STRING,
field: "ip_local"
},
ipExterno: {
type: DataTypes.STRING,
field: "ip_externo"
},
hostname: {
type: DataTypes.STRING,
field: "hostname"
},
dataCriacao: {
type: DataTypes.DATE,
field: "data_criacao"
},
}, {
timestamps: false,
freezeTableName: true,
tableName: "instancia"
});
instancia.associate = (db) => {
console.log(db);
instancia.belongsTo(db.empresa, {foreignKey: "id_instancia"});
};
return instancia;
};
InstanciaEmpresa Model
module.exports = (sequelize, DataTypes) => {
const instanciaEmpresa = sequelize.define("instancia_empresa", {
idEmpresa: {
type: DataTypes.BIGINT(20),
primaryKey: true,
field: "id_empresa"
},
idInstancia: {
type: DataTypes.BIGINT(20),
primaryKey: true,
field: "id_instancia"
},
}, {
timestamps: false,
freezeTableName: true,
tableName: "instancia_empresa"
});
return instanciaEmpresa;
};
My Database diagram.
A picture of my database diagram
The code of my response
const db = require("../config/db.config");
const empresa = db.empresa;
const instancia = db.instancia;
const instanciaEmpresa = db.instanciaEmpresa;
const empressaResult = [];
module.exports = {
async getAll(req, res) {
return res.send(await getAllEmpresa());
}
};
async function getAllEmpresa() {
//Recover all companies from the table
let empresaList = await empresa.findAll({raw: true});
//I browse the array of companies to retrieve the instances associated with the company
for(let i = 0; i < empresaList.length; i++){
//Create the atribute Instancias[]
empresaList[i].instancias = [];
//I retrieve the list of associated instances in the InstanciaEmpresa table
let instanciasEmpresa = await instanciaEmpresa.findAll({where: {"id_empresa": empresaList[i].id}, raw: true});
//Verify if existes any item of InstanciaEmpresa
if(instanciasEmpresa.length > 0){
//If there is a run through list of instances
for(let j = 0; j < instanciasEmpresa.length; j++){
//I retrieve the Instancia in the database and add it to the company Instancias list
let inst = await instancia.findByPk(instanciasEmpresa[j].idInstancia, {raw: true});
empresaList[i].instancias.push(inst);
}
}
//I add the company with the instances in a result list;
empressaResult.push(empresaList[i]);
}
return empressaResult;
}
You can use include option to operate join on your tables.
Then your code would look like,
const empresaList = await empresa.findAll({
raw: true,
include: [
{
model: instancias,
required: false, // left join, `true` means inner join.
}
]
});
As you can see, you can pass array of { model, required } into include option.
You can set required to true if you want to operate inner join else it would operate left join.
--- ADDED ---
SequelizeEagerLoadingError: instancia is not associated to empresa means you're not calling associate function on db initialization.
You can write helper function like below in your db.js.
addAssociations(name) {
if (this[name].associate) {
this[name].associate(this);
}
}
}
and use it like
/*
* this.models = [
* {
* name: 'instancias',
* model: instancias,
* },
* ... and many more
* ]
*/
this.models.forEach((value) => {
this.addAssociations(value.name);
});
If you have the following entities: Users, Roles, Organizations.
You want to setup the relationships so that each user has an organization-role.
In simple each user can belong to multiple organizations and the user has a specific role in each organization.
How would you model this with Sequelize?
I have tried by creating a junction table called organisation_users and then in that table adding a organisationUsers.belongsTo(role);
From I have read Sequelize doesnt support associations on junction tables and so that solution doesn't work.
Regards,
Emir
Sequelize does supports associations on join table. You should look in the "through" options here >
Example :
const Asso_Organization_User = sequelize.define('Asso_Organization_User', {
id: DataTypes.STRING,
userId: DataTypes.STRING,
organizationId: DataTypes.STRING
});
User.Organizations = User.belongsToMany(Organization, {
through: Asso_Organization_User,
foreignKey: 'userId',
otherKey: 'organizationId',
as: 'organizations'
})
But your case is a bit special, I dont see a way using sequelize to get a user, all of its organizations, and his role for each organization in the same query.
It looks like there is no solution yet, based on this issue: https://github.com/sequelize/sequelize/issues/6671
Maybe you could do it in two queries : get all organizations and all roles, and then merging them with code.
Actually, I see another solution:
const Asso_Organization_User = sequelize.define('Asso_Organization_User', {
id: DataTypes.STRING,
userId: DataTypes.STRING,
organizationId: DataTypes.STRING
});
User.Organizations = User.belongsToMany(Organization, {
through: Asso_Organization_User,
foreignKey: 'userId',
otherKey: 'organizationId',
as: 'organizations'
})
The model would look like the following :
User: id
Role: id, userId, organizationId
Organizations: id
Asso_Organization_User: id, userId, organizationId
Then :
User.Organizations = User.belongsToMany(Organization, {
through: Asso_Organization_User
})
Organization.Roles = Organization.haMany(Role, {
foreignKey: 'organizationId'
})
And then you should be able to query :
User.findAll({
include: [ {
model: Organization,
include: {
model: Role
}
} ]
where: {
'role.userId': Sequelize.col("User.id")
}
});
I'm not totally sure of the exact syntax, but combining Sequelize.col with the hasMany should work. But be careful, if you dont add this where clause, this would return all roles for each organization (for every user having a role in this organization).
It may vary upon your requirements of fetching the data from these 3 tables
Consider this Example :
Tables : login, userProfile, farmer
Requirements : Farmer has one user Profile , User Profile has one Login.
Along with these tables we can register a user as a Farmer.
--------------------------------------------------------------------------
farmer.js
module.exports = (Sequelize, DataTypes) => {
const Farmer = Sequelize.define("farmer", { /*attributes*/});
return Farmer;
};
--------------------------------------------------------------------------
login.js
module.exports = (Sequelize, DataTypes) => {
const Login = Sequelize.define("login", {*attributes*/});
return Login;
};
--------------------------------------------------------------------------
userProfile.js
module.exports = (Sequelize, DataTypes) => {
const UserProfile = Sequelize.define("userProfile", {*attributes*/});
return UserProfile;
};
--------------------------------------------------------------------------
index.js
const dbConfig = require("../config/dbConfig"); // your config file
const { Sequelize, DataTypes } = require("sequelize");
//object initilize. (pass parameter to constructor)
const sequelize = new Sequelize(dbConfig.DB, dbConfig.USER, dbConfig.PASSWORD, {
host: dbConfig.HOST,
dialect: dbConfig.dialect,
operatorsAliases: false, //hide errors
pool: {
max: dbConfig.pool.max,
min: dbConfig.pool.min,
acquire: dbConfig.pool.acquire,
idle: dbConfig.pool.idle,
},
});
sequelize
.authenticate()
.then(() => {
console.log("DB connected!");
})
.catch((err) => {
console.log("Error " + err);
});
const db = {}; // Empty object
db.Sequelize = Sequelize;
db.sequelize = sequelize;
db.login = require("./login.js")(sequelize, DataTypes);
db.userProfile = require("./userProfile.js")(sequelize, DataTypes);
db.farmer = require("./farmer.js")(sequelize, DataTypes);
//relations
db.farmer.userProfile = db.farmer.belongsTo(db.userProfile);
db.userProfile.login = db.userProfile.belongsTo(db.login);
db.sequelize
.sync({ force: false }) //force :true - drop all tables before start
.then(() => {
console.log("yes-sync done!");
});
module.exports = db;
//Declare follwing things in a separate location (may be controllers__.js
-----------INSERT DATA (CREATE)--------------------------
const saved = await Farmer.create(
{
supplierCode: "SUP0001",
userProfile: {
firstName: "ssss",
middleName: "ssss",
lastName: "ssss",
address: "ssss",
login: {
name: "ssss",
email: "ssss",
password: "ssss",
role: "ssss",
lastLogin: null,
avatar: "ssss",
status: "ssss",
},
},
},
{
include: [
{
association: Farmer.userProfile,
include: [Login],
},
],
}
);
Observe the usage of the include option in the Farmer.create call. That is necessary for Sequelize to understand what you are trying to create along with the association.
Note: here, our user model is called farmer, with a lowercase f - This means that the property in the object should also be farmer. If the name given to sequelize.define was Farmer, the key in the object should also be Farmer.
-----------FETCH DATA (SELECT)--------------------------
const users = await Farmer.findAll({
include: [
{
association: Farmer.userProfile,
include: [Login],
},
],
});
Output:
[
{
"id": 1,
"supplierCode": "SUP0001",
"createdAt": "2021-11-17T07:39:13.000Z",
"updatedAt": "2021-11-17T07:39:13.000Z",
"userProfileId": 1,
"userProfile": {
"id": 1,
"firstName": "ssss",
"middleName": "ssss",
"lastName": "ssss",
"address": "ssss",
"createdAt": "2021-11-17T07:39:13.000Z",
"updatedAt": "2021-11-17T07:39:13.000Z",
"loginId": 1,
"login": {
"id": 1,
"name": "ssss",
"email": "ssss",
"password": "ssss",
"role": "ssss",
"lastLogin": null,
"avatar": "ssss",
"status": "ssss",
"createdAt": "2021-11-17T07:39:13.000Z",
"updatedAt": "2021-11-17T07:39:13.000Z"
}
}
}
]
Assume you want to add another user Role/Type (we have Farmer already). then you can make coordinater.js (example user role/type) and defind attributes
in above index.js you can add this relation
//Coordinator relation
db.coordinator.userProfile = db.coordinator.belongsTo(db.userProfile, {
onDelete: "CASCADE",
onUpdate: "CASCADE",
});
Now you can register users with different users Roles/Types :
Coordinator.create.... give correct associations