I would like to make 2 tables and setup associations between them to have something that looks like this : https://i.stack.imgur.com/hFjCP.png
I'm not really sure but it looks like a ternary association where 2 columns are from the same table. Both player1 and player2 are of type User.
I tried something like this but I'm really not sure this is the way to go.
const User = sequelize.define('User', { id: DataTypes.STRING })
const Battle = sequelize.define('Battle', { id: DataTypes.STRING })
const UserBattleParticipation = sequelize.define('UserBattleParticipation', {
battleId: {
type: DataTypes.STRING,
primaryKey: true,
allowNull: false,
references: {
model: Battle,
key: 'id'
}
},
player1: {
type: DataTypes.STRING,
primaryKey: true,
allowNull: false,
references: {
model: User,
key: 'id'
}
},
player2: {
type: DataTypes.STRING,
primaryKey: true,
allowNull: false,
references: {
model: User,
key: 'id'
}
},
additional: {
type: DataTypes.STRING,
allowNull: false
}
})
Battle.belongsToMany(User, { as: 'Participant', through:UserBattleParticipation, foreignKey: { name: 'battleId', allowNull: false} });
User.belongsToMany(Battle, { as: 'Attacker', through:UserBattleParticipation, foreignKey: { name: 'player1', allowNull: false } });
User.belongsToMany(Battle, { as: 'Target', through: UserBattleParticipation, foreignKey: { name: 'player2', allowNull: false } });
I suppose you turned upside down meanings of concrete associations:
// If you want to get a list of attackers in a certain battle you should define such association:
// I recommend to use aliases in plural because it's many-to-`many`
Battle.belongsToMany(User, { as: 'Attackers', through:UserBattleParticipation, foreignKey: { name: 'battleId', allowNull: false }, otherKey: { name: 'player1', allowNull: false } });
// If you want to get a list of battle where a user was as an attacker you should define such association:
User.belongsToMany(Battle, { as: 'AttackerBattles', through:UserBattleParticipation, foreignKey: { name: 'player1', allowNull: false }, otherKey: { name: 'battleId', allowNull: false } });
You cannot define an association to get all participants of a battle because you have two different columns for a user.
If you you have a predefined list of users who are participating in a certain battle then you maybe should change your structure and add Participants (ParticipantId, BattleId, UserId) and use it in BattleParticipants (BattleId, ParticipantId1, ParticipantId2, other fields).
Related
I've created two Models User and Gasto(means spent in portuguese), these two tables has an association like One User to Many Gasto, so I'm trying to implement the OnDelete 'CASCADE' to when I delete one User all Gasto from this User has to be deleted together, but my association is not working. Can somebody help me?
My Database Config:
import 'dotenv/config'
import { Options } from 'sequelize'
const config: Options = {
username: process.env.DB_USER ?? 'root',
password: process.env.DB_PASS ?? '123456',
database: 'gastos_app_db',
host: process.env.DB_HOST ?? 'localhost',
port: Number(process.env.DB_PORT) ?? 3002,
dialect: 'mysql',
dialectOptions: {
timezone: 'Z'
},
logging: false
}
module.exports = config
Migration from User, Gasto has the same example.
'use strict'
/** #type {import('sequelize-cli').Migration} */
module.exports = {
async up (queryInterface, Sequelize) {
await queryInterface.createTable('users', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
allowNull: false,
autoIncrement: true,
onDelete: 'CASCADE'
},
username: {
type: Sequelize.STRING,
allowNull: false
},
email: {
type: Sequelize.STRING,
allowNull: false
},
role: {
type: Sequelize.STRING,
allowNull: false
},
password: {
type: Sequelize.STRING,
allowNull: false
}
})
},
async down (queryInterface, Sequelize) {
await queryInterface.dropTable('users')
}
}
My Instance of the Sequelize and configurations.
import { Sequelize } from 'sequelize'
import * as config from '../config/database'
export default new Sequelize(config)
My Model of user and the Association.
import { DataTypes } from 'sequelize'
import db from '.'
import { Gasto } from './Gasto'
export const User = db.define('User', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true
},
username: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: false
},
password: {
type: DataTypes.STRING,
allowNull: false
},
role: {
type: DataTypes.STRING,
allowNull: false
}
}, {
timestamps: false,
tableName: 'users',
underscored: true
})
User.hasMany(Gasto, {
foreignKey: 'userId',
onDelete: 'CASCADE'
})
Gasto.hasOne(User, {
foreignKey: 'id'
})
Gasto Model:
import { DataTypes } from 'sequelize'
import db from '.'
export const Gasto = db.define('Gasto', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true
},
userId: {
type: DataTypes.INTEGER,
allowNull: false
},
type: {
type: DataTypes.STRING,
allowNull: false
},
value: {
type: DataTypes.INTEGER,
allowNull: false
},
gastoDate: {
type: DataTypes.STRING,
allowNull: false
}
}, {
timestamps: false,
tableName: 'gastos',
underscored: true
})
Gasto Migration
'use strict'
/** #type {import('sequelize-cli').Migration} */
module.exports = {
async up (queryInterface, Sequelize) {
await queryInterface.createTable('gastos', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
allowNull: false,
autoIncrement: true
},
user_id: {
type: Sequelize.INTEGER,
allowNull: false
},
type: {
type: Sequelize.STRING,
allowNull: false
},
value: {
type: Sequelize.STRING,
allowNull: false
},
gasto_date: {
type: Sequelize.STRING,
allowNull: false
}
})
},
async down (queryInterface, Sequelize) {
await queryInterface.dropTable('gastos')
}
}
I've already readed the all documentations, and checked some old projects, but I can't found the right way to fix this.
Your user_id field in your migration to create the gastos table must have a Foreign Key definition inside of it, along with the on update/on delete logic you desire (Cascade).
user_id: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'users',
key: 'id',
},
onUpdate: 'cascade',
onDelete: 'cascade'
}
You can find an example of this in the sequelize query interface documentation.
I have two tables defined, one for movies, one for characters, these are interconnected, by the MovieCharacter table.
Setting the tables to maintain a one-to-many relationship via belongstomany allows me to create a duplicate relationship, and I'm not getting around it.
I leave my code below, i have some experience with mongoose and nosql db but this is new for me.
Thanks!
charModel.js
const { Model, DataTypes } = require('sequelize');
const CHARACTER_TABLE = 'character';
const CharacterSchema = {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
allowNull: false,
},
name: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
age: {
type: DataTypes.BIGINT,
allowNull: false,
},
weight: {
type: DataTypes.BIGINT,
allowNull: false,
},
history: {
type: DataTypes.STRING,
allowNull: false
},
image: {
type: DataTypes.STRING,
allowNull: false
},
};
modelMovie.js
class Character extends Model {
static associate(models){
this.belongsToMany(models.Movie, {
as: "movies",
through: "MovieCharacter",
foreignKey: "characterId",
otherKey: "movieId",
});
}
static config(sequelize){
return {
sequelize,
tableName: CHARACTER_TABLE,
modelName: 'Character',
timestamps: false,
}
}
}
const moment = require("moment");
const { Model, DataTypes } = require("sequelize");
const { GENRES_TABLE } = require("./genre.model");
const MOVIES_TABLE = "movie";
const MovieSchema = {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
allowNull: false,
},
title: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
creationDate: {
field: "creation_date",
type: DataTypes.STRING,
get(){
return moment(this.getDataValue('creationDate')).format('DD-MM-YYYY')
},
allowNull: false,
},
rating: {
type: DataTypes.INTEGER,
allowNull: false,
},
image: {
type: DataTypes.STRING,
allowNull: false,
},
type: {
type: DataTypes.STRING,
allowNull: false,
},
genreId: {
field: "genre_id",
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: GENRES_TABLE,
key: "id",
},
onUpdate: "CASCADE",
onDelete: "SET NULL",
},
};
class Movie extends Model {
static associate(models) {
this.belongsToMany(models.Character, {
as: "characters",
through: "MovieCharacter",
foreignKey: "movieId",
otherKey: "characterId",
});
this.belongsTo(models.Genre, {
as: "genre",
});
}
static config(sequelize) {
return {
sequelize,
tableName: MOVIES_TABLE,
modelName: "Movie",
timestamps: false,
};
}
}
module.exports = { MOVIES_TABLE, MovieSchema, Movie };
modelCharMov.js
const { Model, DataTypes} = require('sequelize');
const { CHARACTER_TABLE} = require('./character.model');
const { MOVIES_TABLE} = require('./movies.model')
const MOVIES_CHARACTERS_TABLE = 'movies_characters';
const MoviesCharactersSchema = {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
allowNull: false,
},
movieId:{
field: 'movie_id',
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: MOVIES_TABLE,
key: 'id',
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL'
},
characterId:{
field: 'character_id',
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: CHARACTER_TABLE,
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL'
}
};
class MovieCharacter extends Model {
static config(sequelize) {
return {
sequelize,
tableName: MOVIES_CHARACTERS_TABLE,
modelName: 'MovieCharacter',
timestamps: false
};
}
}
Add 2 BelongsTo associations on the junction table pointing to the tables that are using it to find one another. That would be my first guess because that is the only thing that would have been different in my code compared to yours if implementing this. If that doesn’t work, more details please sir 🧐
There is a table called employees and the employee id wants to self reference to the employee table when the employee has a team lead.
Team lead is also an employee. So how to self reference using Sequelize?
const Employee = sequelize.define("employee", {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
allowNull: false,
},
first_name: {
type: DataTypes.STRING(45),
allowNull: false,
},
lead_role: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
},
designation: {
type: DataTypes.STRING(100),
allowNull: true,
},
resignation_date: {
type: DataTypes.DATE,
allowNull: true,
},
lead_id: {
type: DataTypes.INTEGER,
allowNull: true,
},
});
this is my employee model associations
Employee.associate = function (models) {
Employee.hasOne(models.user, { foreignKey: "employee_id" });
Employee.hasMany(models.leave_request, {
foreignKey: "employee_id",
});
Employee.belongsTo(models.team, {
foreignKey: "team_id",
});
Employee.belongsTo(models.team_manager, {
foreignKey: "manager_id",
});
Employee.hasMany(models.attendance, {
foreignKey: "employee_id",
});
Employee.belongsTo(models.employee, {
foreignKey: "lead_id",
});
Employee.hasMany(models.employee, {
foreignKey: "lead_id",
});
};
You just need to indicate aliases for both a team lead link and employees link:
Employee.belongsTo(models.employee, {
foreignKey: "lead_id",
as: 'TeamLead'
});
Employee.hasMany(models.employee, {
foreignKey: "lead_id",
as: 'TeamEmployees'
});
And you need to indicate the same aliases in queries:
const employees = await Employee.findAll({
where: {
team_id: teamId
},
include: [{
model: Employee,
as: 'TeamLead'
}]
})
const teamLead = await Employee.findAll({
where: {
id: teamLeadId
},
include: [{
model: Employee,
as: 'TeamLead'
}]
})
I have 2 tables, ItemLegacy :
module.exports = function(sequelize, DataTypes) {
return sequelize.define('ItemLegacy', {
id: {
type: DataTypes.INTEGER(11).UNSIGNED,
allowNull: false,
primaryKey: true
},
parent: {
type: DataTypes.INTEGER(11),
allowNull: false,
defaultValue: 0,
},
child: {
type: DataTypes.INTEGER(11),
allowNull: false,
defaultValue: 0
}
}, {
tableName: 'ItemLegacy',
timestamps: false,
underscored: false
});
};
and Item :
module.exports = function(sequelize, DataTypes) {
return sequelize.define('Item', {
id: {
type: DataTypes.INTEGER(11).UNSIGNED,
allowNull: false,
primaryKey: true
},
title: {
type: DataTypes.STRING(500),
allowNull: false,
defaultValue: ''
},
code: {
type: DataTypes.STRING(20),
allowNull: true
},
}, {
tableName: 'Item',
timestamps: false,
underscored: false
});
};
I also defined two relationships :
db.ccnLegacy.hasOne(db.ccn, { foreignKey: 'id', sourceKey: 'parent' })
db.ccnLegacy.hasOne(db.ccn, { foreignKey: 'id', sourceKey: 'child' })
My question is, I would like to create a select request using sequelize, with a relation for each of the 2 fields parent and child.
I know how to do that with one relation, but how do I do it with 2 ?
My code with only one relation :
db.itemLegacy.findOne({
raw: true,
where: { child: idChild },
include: [
{
model: db.item
},
]
})
You simply should indicate aliases for both associations and use them in queries. Second is you used hasOne instead of belongTo because belongTo is used exactly in a case when you go from N to 1 in N:1 relationship.
Associations:
db.ccnLegacy.belongsTo(db.ccn, { foreignKey: 'parent', as: 'Parent' })
db.ccnLegacy.belongsTo(db.ccn, { foreignKey: 'child', as: 'Child' })
Query:
db.itemLegacy.findOne({
raw: true,
where: { child: idChild },
include: [
{
model: db.item,
as: 'Child'
},
{
model: db.item,
as: 'Parent'
},
]
})
I have two models. User and Manager
User Model
const UserMaster = sequelize.define('User', {
UserId: {
type: DataTypes.BIGINT,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
RelationshipId: {
type: DataTypes.STRING,
allowNull: true,
foreignKey: true
},
UserName: {
type: DataTypes.STRING,
allowNull: true
}
})
Manager model
const Manager = sequelize.define('Manager', {
ManagerId: {
type: DataTypes.BIGINT,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
RelationshipId: {
type: DataTypes.STRING,
allowNull: true,
foreignKey: true
},
MangerName: {
type: DataTypes.STRING,
allowNull: true
}
})
Models are minified to simplyfy the problem
Associations..
User.belongsTo(models.Manager, {
foreignKey: 'RelationshipId',
as: 'RM'
});
Manger.hasMany(model.User, {
foreignKey: 'RelationshipId',
as: "Users"
})
So, on user.findAll()
var userObject = models.User.findAll({
include: [{
model: models.Manager,
required: false,
as: 'RM',
attributes: ['ManagerName']
}]
});
I get the following.
userObject = [{
UserId: 1,
RelationshipId: 4545,
UserName: 'Jon',
RM: {
ManagerName: 'Sam'
}
},
{
UserId: 2,
RelationshipId: 432,
UserName: 'Jack',
RM: {
ManagerName: 'Phil'
}
},
...
]
How can I move 'ManagerName' attribute from Manager model (associated as RM) to UserObject?
Is it possible to somehow load attributes from eagerly-loaded models without nesting them under a separate object?
I expected the resulting Object to look Like the object
Expected Object --
userObject = [{
UserId: 1,
RelationshipId: 4545,
UserName: 'Jon',
ManagerName: 'Sam' // <-- from Manager model
},
{
UserId: 2,
RelationshipId: 432,
UserName: 'Jack',
ManagerName: 'Phil' // <-- from Manager model
},
...
]
Thank you.
Adding raw: true along with attributes option worked to get the desired object format.
So,
var userObject = models.User.findAll({
raw:true,
attributes: {
include: [Sequelize.col('RM.ManagerName'), 'ManagerName']
},
include: [{
model: models.Manager,
required: false,
as: 'RM',
attributes: []
}]
});