I have two models in Sequelize as below:
export const User = db.define('user', {
id: {
type: Sequelize.UUID,
primaryKey: true,
},
});
export const Class = db.define('class', {
id: {
type: Sequelize.UUID,
primaryKey: true,
},
students: Sequelize.ARRAY({
type: Sequelize.UUID,
references: { model: 'users', key: 'id' },
})
});
How can I define an association between my Class model and the user model?
I have tried the below but it gives me an error.
Class.hasMany(User, { foreignKey: 'students' });
User.belongsTo(Class);
DatabaseError [SequelizeDatabaseError]: column "class_id" does not exist
I think you are missing the syntax
students: Sequelize.ARRAY({
type: Sequelize.UUID,
references: { model: 'users', key: 'id' }, // this has no effect
})
Should be
students: {
type: DataTypes.ARRAY({ type: DataTypes.UUID }),
references: { model: 'users', key: 'id' }
}
This won't work either, because the data type of students (ARRAY) and id (UUID) of User does not match.
Also, with these associations, you are adding two columns on User referencing id of Class but you only need one
Class.hasMany(User, { foreignKey: 'students' }); //will add students attribute to User
User.belongsTo(Class); //will add classId attribute to User
if you want to name the foreign key column passe the same name to both associations, by default Sequelize will add classId, however if you configured underscored: true on the models it will be class_id
Here is a working solution
const User = sequelize.define('user', {
id: { type: DataTypes.UUID, primaryKey: true },
});
const Class = sequelize.define('class', {
id: { type: DataTypes.UUID, primaryKey: true },
students: DataTypes.ARRAY({ type: DataTypes.UUID }),
});
Class.hasMany(User, { foreignKey: 'class_id' });
User.belongsTo(Class, { foreignKey: 'class_id' });
Related
Let us say there are two tables namely User and User Role.
The relationship between user and user role is one to many.
Sequelize model for the user is as following -
const user = sequelize.define(
'user', {
id: {
type: DataTypes.BIGINT,
allowNull: false,
primaryKey: true,
autoIncrement: true,
field: 'id'
},
userName: {
type: DataTypes.STRING(200),
allowNull: false,
field: 'username'
},
password: {
type: DataTypes.STRING(200),
allowNull: false,
field: 'password'
}
}, {
tableName: 'user'
}
);
Sequelize model for user role is as follwing -
const userRole = sequelize.define(
'userRole', {
id: {
type: DataTypes.BIGINT,
allowNull: false,
primaryKey: true,
autoIncrement: true,
field: 'id'
},
userId: {
type: DataTypes.BIGINT,
allowNull: false,
primaryKey: true,
autoIncrement: true,
field: 'user_id'
},
password: {
type: DataTypes.STRING(200),
allowNull: false,
field: 'password'
}
}, {
tableName: 'userRole'
}
);
Sequelize association is defined as follows -
user.hasMany(models.userRole, { foreignKey: 'user_id', as: 'roles' });
userRole.belongsTo(models.user, { foreignKey: 'user_id', as: 'user' });
I want to generate the following query using
Sequelize -
SELECT *
FROM USER
INNER JOIN (SELECT user_role.user_id,
role
FROM user_role
INNER JOIN USER tu
ON tu.id = user_role.user_id
GROUP BY user_id
ORDER BY role) AS roles
ON USER.id = roles.user_id;
I am developing an API which will be consumed by the front end grid for showing user info. There is search functionality on role attribute of user role table. If any of role of a specific user is matched then I expect a user record with all the roles which are associated with that user.
To get all roles that are associated with the user even if one of them matches with a query you need to define another association so that you can include role table twice in sequelize statement.
User.hasMany(models.UserRole, { foreignKey: 'user_id', as: 'roles2' });
Sequelize statement -
const userInfo = await User.findAndCountAll({
include: [
{
model: UserRole,
attributes: ['id', 'role'],
as: 'roles',
where: { [Op.or]: [{ role: { [Op.like]: '%MANAGER%' } },
required: true
},
{
model: UserRole,
attributes: ['id', 'role'],
as: 'roles2',
required: true
}
],
where: whereStatement,
});
With the help of first include (join), you can filter user records based on user role and with the help of the second include(join), you can get all the roles of a user whose one of the roles is matched.
You have to use include (regarding the doc : https://sequelize.org/master/manual/models-usage.html#-code-findandcountall--code----search-for-multiple-elements-in-the-database--returns-both-data-and-total-count)
Exemple :
Models.User.findAll({
where :{
id: userId
},
group: ['roles.user_id'],
order: [['roles.role', 'ASC']] //or DESC, as you want
include: {
model: Models.UserRole,
as: 'roles',
attributes: ['user_id', 'role'],
required: true
},
})
Hope it helps you
I created the following two models...
const Account = sequelize.define("account",
{
id_account: {
type: Sequelize.INTEGER.UNSIGNED,
primaryKey: true,
autoIncrement: true
},
name: {...},
surname: {...},
username: {...},
password: {...}
},
{
name: {
singular: "Account",
plural: "Accounts"
},
freezeTableName: true,
hooks: {
beforeSave: ((account, options) => {
return bcrypt.hash(account.password, 10)
.then(hash => {account.password = hash;})
.catch(err => {throw new Error();});
})
}
});
const Genre = sequelize.define("genre",
{
genre_name: {
type: Sequelize.STRING(40),
primaryKey: true
},
url_img: {
type: Sequelize.STRING(40),
allowNull: false
}
},
{
name: {
singular: "Genre",
plural: "Genres"
},
freezeTableName: true
});
...and the following associations
Account.Genres = Account.belongsToMany(Genre, {
through: "AccountGenre",
foreignKey: "ref_account"
});
Genre.Accounts = Genere.belongsToMany(Account, {
through: "AccountGenre",
foreignKey: "ref_genre"
});
I created the following genres: Rock, Metal, Pop, Hardcore.
Now i want to create an Account and associate it 3 genres.
The following code creates the Account but doesn't create the association with the existing genres in the AccountGenre table:
const genresArray = ["Rock", "Metal", "Pop"];
const account = {...} // I have an object with account properties
Account.create({
name: account.name,
surname: account.cognome,
username: account.nome_utente,
password: account.password,
genres: genresArray
}, {
include: [Genre]
});
What's wrong in this code?
The problem here is that you gave the reference to the
association the plural name “Genres”. You need the same in the include statement.
include: [Genres]
I have model called proxyPool with next fields:
poolId: {
type: DataTypes.INTEGER,
references: {
model: 'pool',
key: 'id',
},
field: 'pool_id',
},
proxyId: {
type: DataTypes.INTEGER,
references: {
model: 'proxy',
key: 'id',
},
field: 'proxy_id',
},
It's a N:M table for two tables which have next associations:
proxy.associate = (models) => {
proxy.belongsToMany(models.pool, {
through: models.proxyPool,
foreignKey: 'proxy_id',
});
};
and
pool.associate = (models) => {
pool.belongsToMany(models.proxy, {
through: models.proxyPool,
foreignKey: 'pool_id',
});
};
When I call proxyPool.findOrCreate({where: {proxyId, poolId}}) it says that column proxyPool.proxyId does not exist, but in raw SQL I see:
SELECT "id", "pool_id" AS "poolId", "createdAt", "updatedAt", "pool_id", "proxy_id"
FROM "portnoi"."proxy_pool" AS "proxyPool"
WHERE "proxyPool"."proxyId" = '3' AND "proxyPool"."pool_id" = '1' LIMIT 1;
Why does it use alias for poolId = pool_id but not use alias described in model for proxyId = proxy_id?
Have you tried to make a query like that?
proxyPool.findOrCreate({
where: {
proxyId: {
[Op.eq]: proxyId,
},
...
}
})
I have two tables on a PostgreSQL database, contracts and payments. One contract has multiple payments done.
I'm having the two following models:
module.exports = function(sequelize, DataTypes) {
var contracts = sequelize.define('contracts', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true
}
}, {
createdAt: false,
updatedAt: false,
classMethods: {
associate: function(models) {
contracts.hasMany(models.payments, {
foreignKey: 'contract_id'
});
}
}
});
return contracts;
};
module.exports = function(sequelize, DataTypes) {
var payments = sequelize.define('payments', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true
},
contract_id: {
type: DataTypes.INTEGER,
},
payment_amount: DataTypes.INTEGER,
}, {
classMethods: {
associate: function(models) {
payments.belongsTo(models.contracts, {
foreignKey: 'contract_id'
});
}
}
});
return payments;
};
I would like to sum all the payments made for every contract, and used this function:
models.contracts.findAll({
attributes: [
'id'
],
include: [
{
model: models.payments,
attributes: [[models.sequelize.fn('sum', models.sequelize.col('payments.payment_amount')), 'total_cost']]
}
],
group: ['contracts.id']
})
But it generates the following query:
SELECT "contracts"."id", "payments"."id" AS "payments.id", sum("payments"."payment_amount") AS "payments.total_cost"
FROM "contracts" AS "contracts"
LEFT OUTER JOIN "payments" AS "payments" ON "contracts"."id" = "payments"."contract_id" GROUP BY "contracts"."id";
I do not ask to select payments.id, because I would have to include it in my aggregation or group by functions, as said in the error I have:
Possibly unhandled SequelizeDatabaseError: error: column "payments.id"
must appear in the GROUP BY clause or be used in an aggregate function
Am I missing something here ? I'm following this answer but even there I don't understand how the SQL request can be valid.
This issue has been fixed on Sequelize 3.0.1, the primary key of the included models must be excluded with
attributes: []
and the aggregation must be done on the main model (infos in this github issue).
Thus for my use case, the code is the following
models.contracts.findAll({
attributes: ['id', [models.sequelize.fn('sum', models.sequelize.col('payments.payment_amount')), 'total_cost']],
include: [
{
model: models.payments,
attributes: []
}
],
group: ['contracts.id']
})
Try
group: ['contracts.id', 'payments.id']
Can you write your function as
models.contracts.findAll({
attributes: [
'models.contracts.id'
],
include: [
{
model: models.payments,
attributes: [[models.sequelize.fn('sum', models.sequelize.col('payments.payment_amount')), 'total_cost']]
}
],
group: ['contracts.id']
})
Is the issue that you might want to be selecting from payments and joining contracts rather than the other way around?
I'm new to Sails.js (v0.10.5) and Waterline ORM. I have 3 tables in database: users (id, name), roles(id, alias) and join table users_roles(user_id, role_id). It's important not to change table names and field names in database. I want Policy entity to be a join entity between User and Role. Here is some mapping code:
//User.js
module.exports = {
tableName: 'users',
autoCreatedAt: false,
autoUpdatedAt: false,
attributes: {
id: {
type: 'integer',
required: true
},
name: {
type: 'string'
},
roles: {
collection: 'role',
via: 'users',
through: 'policy'
},
}
}
//Role.js
module.exports = {
tableName: "roles",
autoCreatedAt: false,
autoUpdatedAt: false,
attributes: {
id: {
type: 'integer',
required: true
},
alias: {
type: 'string',
required: true
},
users: {
collection: 'user',
via: 'roles',
through: 'policy'
}
}
}
//Policy.js
module.exports = {
tableName: "users_roles",
tables: ['users', 'roles'],
junctionTable: true,
autoCreatedAt: false,
autoUpdatedAt: false,
attributes: {
user: {
columnName: 'user',
type: 'integer',
foreignKey: true,
references: 'user',
on: 'id',
via: 'role',
groupBy: 'user'
},
roles: {
columnName: 'role',
type: 'integer',
foreignKey: true,
references: 'role',
on: 'id',
via: 'user',
groupBy: 'role'
}
}
}
But when I trying to access roles atribute in controller
User.findOne({id: 1}).populate('roles').exec(function(err, user) {
console.log(JSON.stringify(user.roles));
});
this returns
[]
And
User.findOne({id: 1}).populate('roles').exec(function(err, user) {
console.log(JSON.stringify(user));
});
returns
{"id":1,"name":"test", "roles":[]}
I checked twice that user, role and association between them exists in database. What is my mistake?
I have found way to solve this problem. It's not what I exactly want, but it works.
First: join entity:
//Policy.js
module.exports = {
tableName: "users_roles",
autoPK: false,
attributes: {
id: {
type: 'integer',
primaryKey: true,
autoIncrement: true,
},
user: {
columnName: 'user_id',
model: 'user'
},
role: {
columnName: 'role_id',
model: 'role'
}
},
//tricky method to get all users for specified role_id
//or to get all roles for specified user_id
get: function(id, modificator, cb) {
var fields = ['user', 'role'];
if (fields.indexOf(modificator) < 0) {
cb(new Error('No such modificator in Policy.get()'), null);
}
var inversedField = fields[(fields.indexOf(modificator) + 1) % 2];
var condition = {};
condition[inversedField] = id;
this.find(condition).populate(modificator).exec(function(err, policies) {
if (err) {
cb(err, null);
return;
}
var result = [];
policies.forEach(function(policy) {
result.push(policy[modificator]);
});
cb(null, result);
return;
});
}
}
As you see, I added ID field to this entity (and to db table users_roles too), so it's not the great solution.
//User.js
module.exports = {
tableName: 'users',
autoPK: false,
attributes: {
id: {
type: 'integer',
primaryKey: true,
autoIncrement: true,
unique: true,
},
name: {
type: 'string'
},
policies: {
collection: 'policy',
via: 'user'
}
}
}
And Role Entity:
//Role.js
module.exports = {
tableName: 'roles',
autoPK: false,
attributes: {
id: {
type: 'integer',
primaryKey: true,
autoIncrement: true,
},
alias: {
type: 'string',
required: true,
unique: true,
},
policies: {
collection: 'policy',
via: 'role'
}
}
}
That's how I get all roles for specified user_id:
...
id = req.session.me.id; //user_id here
Policy.get(id, 'role', function(err, roles) {
var isAdmin = false;
roles.forEach(function(role) {
isAdmin |= (role.id === 1);
});
if (isAdmin) {
next(null);
return;
} else {
return res.redirect('/login');
}
});
...
Maybe it'll be usefull for somebody =)