Sequelize select with two relations to the same entity - orm

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'
},
]
})

Related

I have a trouble with association with sequelize and postgres

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 🧐

I tried to add self reference relationship using Sequelize

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

Ternary association with same table

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).

Load attributes from associated model in sequelize.js

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: []
}]
});

Sequelize belongToMany association not working through table

I'm using Sequelize on PostgreSQL to store users that belong to organizations. The organizations own devices that the users can access. So in essence, users also own devices through their organizations.
I set it up that each device is associated with an organization using the organization_id as well each user is associated with an organization using an organization_id. I'm trying to set this up with Sequelize to read it properly. I'm trying my best not to resort to writing a custom query, but it's okay if I have to in the end.
I'm trying to get all devices associated with a user ID. Sequelize prints out this crazy query with an error when I try to run the findAll(...) command. It outputs this query and then an empty set:
SELECT
"receiver"."receiver_id" AS "id",
"receiver"."organization_id" AS "organizationID",
"receiver"."identifier",
"receiver"."secret",
"receiver"."iterations",
"receiver"."sodium",
"receiver"."algorithm",
"receiver"."created",
"receiver"."modified",
"receiver"."deleted",
"receiver"."organization_id",
"users"."user_id" AS "users.id",
"users"."password" AS "users.password",
"users"."sodium" AS "users.sodium",
"users"."email" AS "users.email",
"users"."organization_id" AS "users.organizationID",
"users"."iterations" AS "users.iterations",
"users"."algorithm" AS "users.algorithm",
"users"."created" AS "users.created",
"users"."modified" AS "users.modified",
"users"."deleted" AS "users.deleted",
"users"."organization_id" AS "users.organization_id",
"users.organizations"."created" AS "users.organizations.created",
"users.organizations"."modified" AS "users.organizations.modified",
"users.organizations"."organization_id" AS "users.organizations.organization_id"
FROM "receivers" AS "receiver"
INNER JOIN (
"organizations" AS "users.organizations"
INNER JOIN "users" AS "users"
ON "users"."user_id" = "users.organizations"."organization_id")
ON "receiver"."receiver_id" = "users.organizations"."organization_id"
AND ("users"."deleted" IS NULL AND "users"."user_id" = 2)
WHERE "receiver"."deleted" IS NULL;
How can I be writing the definitions or code better?
Thanks so much.
My table definitions in Sequelize:
var organization = sequelize.define( 'organization', {
'id': {
type: Sequelize.BIGINT,
field: 'organization_id',
allowNull: false,
primaryKey: true,
autoIncrement: true
},
'name' : {
type: Sequelize.STRING( 256 ),
field: 'name',
allowNull: false,
validate: {
notEmpty: true
}
}
}, {
'createdAt' : 'created',
'updatedAt' : 'modified',
'deletedAt' : 'deleted',
'tableName' : 'organizations',
'paranoid' : true
} );
var user = sequelize.define( 'user', {
'id': {
type: Sequelize.BIGINT,
field: 'user_id',
allowNull: false,
primaryKey: true,
autoIncrement: true
},
'password': {
type: Sequelize.STRING( 64 ),
field: 'password',
allowNull: false,
validate: {
notEmpty: true
}
},
'sodium': {
type: Sequelize.STRING( 64 ),
field: 'sodium',
allowNull: false,
validate: {
notEmpty: true
}
},
'email' : {
type: Sequelize.STRING( 64 ),
field: 'email',
allowNull: false,
validate: {
notEmpty: true
}
},
'organizationID' : {
type: Sequelize.BIGINT,
field: 'organization_id',
allowNull: false,
validate: {
notEmpty: true
}
},
'iterations' : {
type: Sequelize.INTEGER,
field: 'iterations',
allowNull: false,
validate: {
notEmpty: true
}
},
'algorithm' : {
type: Sequelize.STRING( 8 ),
field: 'algorithm',
allowNull: false,
defaultValue: 'sha256'
}
}, {
'createdAt' : 'created',
'updatedAt' : 'modified',
'deletedAt' : 'deleted',
'tableName' : 'users',
'paranoid' : true
} );
var receiver = sequelize.define( 'receiver', {
'id': {
type: Sequelize.BIGINT,
field: 'receiver_id',
allowNull: false,
primaryKey: true,
autoIncrement: true
},
'organizationID': {
type: Sequelize.BIGINT,
field: 'organization_id',
allowNull: false,
validate: {
notEmpty: true
}
},
'identifier': {
type: Sequelize.STRING( 64 ),
field: 'identifier',
allowNull: false,
validate: {
notEmpty: true
}
},
'secret' : {
type: Sequelize.STRING( 64 ),
field: 'secret',
allowNull: false,
validate: {
notEmpty: true
}
},
'iterations' : {
type: Sequelize.INTEGER,
field: 'iterations',
allowNull: false,
validate: {
notEmpty: true
}
},
'sodium': {
type: Sequelize.STRING( 64 ),
field: 'sodium',
allowNull: false,
validate: {
notEmpty: true
}
},
'algorithm' : {
type: Sequelize.STRING( 8 ),
field: 'algorithm',
allowNull: false,
defaultValue: 'sha256'
}
}, {
'createdAt' : 'created',
'updatedAt' : 'modified',
'deletedAt' : 'deleted',
'tableName' : 'receivers',
'paranoid' : true
} );
// Organizations have users and users have organizations
organization.hasMany( user, { 'foreignKey' : 'organization_id' } );
user.belongsTo( organization, { 'foreignKey' : 'organization_id' } );
// Organizations have receivers
organization.hasMany( receiver, { 'foreignKey' : 'organization_id' } );
receiver.belongsTo( organization, { 'foreignKey' : 'organization_id' } );
// Receivers to users
user.belongsToMany( receiver, { 'through' : 'organizations', 'foreignKey' : 'organization_id' } );
receiver.belongsToMany( user, { 'through' : 'organizations', 'foreignKey' : 'organization_id' } );
The code I'm using to query:
// Get the devices for this person
db.receiver.findAll( {
'include' : [
{
'model' : db.user,
'where' : { 'id' : 2 }
}
]
} )
.complete( function( error, result ) {
if( error ) {
console.log( error );
}
else {
console.log( result );
}
} );
Try the following, what it does it selects the user that matches the where statement, and includes organization associated with it, which in it's turn includes devices associated with it, so you should end up with devices associated with the user.
// Organizations have users
user.belongsTo(organization);
// Organizations have receivers
receiver.belongsTo(organization);
// Get the devices for this person
db.user.find( {
'include' : [
{model: db.organization,
include: [model: db.receiver]
}
]
'where' : { 'id' : 2 }
} )
.complete( function( error, result ) {
if( error ) {
console.log( error );
}
else {
console.log( result );
}
} );
if you are using underscore id field names like organization_id, you can specify "underscored: true" when creating the model, so you don't have to specify foreign key field when creating associations.