Sequelize - composite key made up of foreign key in another table - sql

I'm trying to define a model with a composite key made up of the created_by and user_id fields, where the user_id is also a foreign key in the user table.
I'm following the example for composite keys from the Sequelize docs, where you have to set primaryKey to true on each of the fields which make up the composite key.
However, this isn't working - the result is that the created_by becomes the PK and user_id is a FK for the user table. When I try to insert a new record with the same created_by value, but a different user_id this doesn't work.
class my_model extends Model = (sequelize, Sequelize) => {
my_model.init({
'some_val': { type: Sequelize.DOUBLE, defaultValue: 0},
'some_val2': { type: Sequelize.DOUBLE, defaultValue: 0},
'created_by': { type: Sequelize.DATEONLY, allowNull: false, primaryKey: true},
'user_id': { type: Sequelize.INTEGER, allowNull: false, primaryKey: true, references: {model: 'user', key: 'id'}},
},
{
'indexes': [{unique: true, fields: ['user_id', 'created_by']}]
}
);
};

Related

define belongsToMany relation with self referencing table in sequelize

i have "User" table with phoneNumber as a table column.
I want to have association so that each User can add many contacts... basically contacts will be other User.
Usually in many to many relation we have two tables and a joining table.
But can i have one table, that is User table and a joining table referencing to User itself ??
my User table:
const User = sequelize.define(
"users",
phone_number: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
} );
can i have following "UserContact" joining table without "Contact table" with this association
User.associate = (models) => {
models.User.belongsToMany(models.Contact, {through: 'UserContact' })
};
my UserContact table:
const UserContact = sequelize.define(
"user_contacts",
{
user_id: {
type: DataTypes.INTEGER,
allowNull: false,
},
contact_id: {
type: DataTypes.INTEGER,
allowNull: false,
},
}
);
Is there any other way like referencing contacts through some field in User table itself?? again one user should have one or many contacts.
You can indicate User model in a association and indicate fields for both sides:
User.associate = (models) => {
models.User.belongsToMany(models.User, {through: 'UserContact' }, foreignKey: 'user_id', otherKey: 'contact_id')
};

How to prevent inserting redundant combinations in Sequelize Node.js?

I have a table for the English words: WORDS, and another for antonyms: ANTONYMS, with each word having a unique integer id in the WORDS table. The ANTONYMS table has only 2 columns: ID_1 and ID_2, both referencing ids from WORDS.
const WORDS = database.define('WORDS', {
id: {
type: Sequelise.INTEGER,
primaryKey: true,
autoIncrement: true
},
word: {
type: Sequelise.STRING,
unique: true,
validate: {
is: ['^[a-z ]{2,30}$']
},
allowNull: false
},
definition: {
type: Sequelise.TEXT,
allowNull: true,
validate: {
is: ['^[a-z0-9 .,;]{10,500}$|^$', 'i']
}
}
})
const ANTONYMS = database.define('ANTONYMS', {
id_1: {
type: Sequelise.INTEGER,
primaryKey: true,
references: {
model: WORDS,
key: 'id'
}
},
id_2: {
type: Sequelise.INTEGER,
unique: true,
references: {
model: WORDS,
key: 'id'
}
}
})
Now, suppose I have two words as antonyms, one with id=1 and the other with id=2 in the WORDS table, and the row (1, 2) has already been inserted in ANTONYMS. How to set a constraint to prevent the user from inserting the redundant value (2, 1)?
You can create a unique index in Postgres:
create unique index on antonyms (least(id_1, id_2), greatest(id_1, id_2));

Sequelize define association on Array

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' });

How to generate query on join table using Sequelize?

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

How can I model a one-to-one relationship on a primary key in Waterline?

I have a pair of tables (MySQL, if you're curious) such that one is a primary table and one is an auxiliary table whose PK is an FK to the first one -- they share a one-to-one relationship.
I've defined both tables adequately, but I run into trouble when I attempt to define the primary table's model to refer to the secondary. Specifically, all attempts to do /this/ fail:
/* existing primary key defined, works okay */
id: {
columnName: 'KeyID',
type: 'integer',
primaryKey: 'true',
autoIncrement: 'true'
},
/* adding this ruins everything */
moreData: {
columnName: 'KeyID',
model: 'extraData'
}
Running this sees the ORM /return/ but the results are strange and corrupt, with KeyID being a copy of what moreData should be, and moreData being a strange array with DB fields and not properly rename attributes. Excluding the columnName sees the query fail because moreData isn't in the primary table's field list.
Am I approaching this incorrectly?
Let me know if I'm not understanding this correctly. It seems that what you have is two models, PrimaryData and ExtraData, that should be in a one-to-one relationship. Is that right?
If so, I would set this up as follows:
//PrimaryData.js
module.exports = {
attributes: {
id: {
column: 'KeyID',
type: 'integer',
primaryKey: true,
autoIncrement: true
},
extraData: {
column: 'ExtraKeyID',
model: 'ExtraData'
}
}
}
//ExtraData.js
module.exports = {
attributes: {
id: {
column: 'KeyID',
type: 'integer',
primaryKey: true,
autoIncrement: true
},
primaryData: {
column: 'PrimaryKeyID',
model: 'PrimaryData'
}
}
}
This sets up a one-to-one relationship between the two data models.
Note that one-to-one relationships in Waterline are not currently updated automatically in both tables.
https://github.com/balderdashy/waterline/issues/360
So depending on your needs, you could change this to a one-to-many relationship.
If ExtraData is really just an extension of PrimaryData, and will never be used on it's own, then one-to-one is probably the way to go, and you don't even need the pointer back to PrimaryData in the ExtraData model.
//ExtraData.js
module.exports = {
attributes: {
id: {
column: 'KeyID',
type: 'integer',
primaryKey: true,
autoIncrement: true
},
}
}
Hope I understood your problem correctly and that this helps.