Sequelize: Wrong column names on junction table columns - mysql

I have a MySQL database where everything is in snake_case. I have two models with many-to-many relationship (RoomBooking and User), and one manually-defined model (called MeetingGuest) that acts as their junction table (among other things). The problem is, Sequelize keeps generating queries with PascalCase column and table names for this junction model.
MeetingGuest is generated using sequelize-cli, and tweaked to become like so:
const { Model } = require('sequelize')
module.exports = (sequelize, DataTypes) => {
class MeetingGuest extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate(models) {
}
}
MeetingGuest.init(
{
room_booking_id: {
type: DataTypes.INTEGER,
references: {
model: 'RoomBooking',
key: 'id',
},
},
user_id: {
type: DataTypes.INTEGER,
references: {
model: 'User',
key: 'id',
},
},
status: DataTypes.STRING,
check_in: DataTypes.BOOLEAN,
},
{
sequelize,
modelName: 'MeetingGuest',
tableName: 'meeting_guests',
createdAt: 'created_at',
updatedAt: 'updated_at',
},
)
return MeetingGuest
}
The query it generates is like this:
SELECT
MeetingGuest.RoomBookingId, -- `room_booking_id` is never aliased into RoomBookingId
-- Other selected columns...
FROM `users` AS `User`
INNER JOIN `meeting_guests` AS `MeetingGuest`
ON `User`.`id` = `MeetingGuest`.`UserId` AND `MeetingGuest`.`RoomBookingId` = 1;

Defining the foreign keys explicitly is the key.
I went from
modelA.belongsToMany(modelB,
{ through: associativeTable }
);
modelB.belongsToMany(modelA,
{ through: associativeTable }
);
to
modelA.belongsToMany(modelB,
{ through: associativeTable, foreignKey: 'modelA_id' }
);
modelB.belongsToMany(modelA,
{ through: associativeTable, foreignKey: 'modelB_id' }
);
And now everything is peachy

Looks like I have to add foreignKey and otherKey to the model to be associated with. In my case, it's RoomBooking (not the join table). Like this:
const { Model } = require('sequelize')
const User = require('./User')
module.exports = (sequelize, DataTypes) => {
class RoomBooking extends Model {
static associate(models) {
// define association here
this.belongsTo(models.Room, {
foreignKey: 'room_id',
})
this.belongsTo(models.User, {
foreignKey: 'user_id',
as: 'host',
})
this.belongsToMany(models.User, {
through: models.MeetingGuest,
foreignKey: 'room_booking_id',
otherKey: 'user_id',
as: 'guests',
})
}
}
RoomBooking.init(
{
user_id: {
type: DataTypes.INTEGER,
references: {
model: 'User',
key: 'id',
},
},
room_id: DataTypes.INTEGER,
// ...other fields
},
{
sequelize,
modelName: 'RoomBooking',
tableName: 'room_bookings',
createdAt: 'created_at',
updatedAt: 'updated_at',
},
)
return RoomBooking
}

Related

Nodejs Sequelize

I have these 2 models:
Orders Models
Solutions model
Orders Model
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Orders extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate(models) {
// define association here
Orders.hasMany(models.Payments, {
foreignKey: {
name: 'order',
allowNull: false,
},
constraints: false,
onDelete: 'cascade',
});
Orders.hasOne(models.Solutions, {
foreignKey: {
name: 'order',
allowNull: false,
},
constraints: false,
onDelete: 'cascade',
as: "solution"
});
}
}
Orders.init(
{
order_no: {
defaultValue: DataTypes.UUIDV4,
type: DataTypes.UUID,
primaryKey: true,
allowNull: false,
unique: true,
},
order_date: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
},
title: {
type: DataTypes.STRING,
allowNull: false,
},
},
{
sequelize,
modelName: 'Orders',
tableName: 'Orders',
}
);
return Orders;
};
#2. Solutions table
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Solutions extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate(models) {
// define association here
Solutions.belongsTo(models.Orders, {
foreignKey: 'order',
onDelete: 'cascade',
constraints: false,
as: "solution"
});
}
}
Solutions.init(
{
solutionId: {
defaultValue: DataTypes.UUIDV4,
type: DataTypes.UUID,
primaryKey: true,
allowNull: false,
unique: true,
},
content: {
type: DataTypes.TEXT,
allowNull: false,
},
additional_instruction: {
type: DataTypes.TEXT,
allowNull: true,
},
date_submited: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
},
},
{
sequelize,
modelName: 'Solutions',
}
);
return Solutions;
};
I am trying to get all orders where it's solution has not been submitted to the solutions table, i.e order field(Foreign key in solution table) is null.
I have tried this
Orders.findAndCountAll({
include: [
{
model: Users,
attributes: ['username', 'email', 'uid'],
},
{
model: Solutions,
as: "solution",
where: {
solutionId: {
[Op.notIn]: Solutions.findAll({
attributes: ['solutionId']
})
}
}
}
],
offset: page,
limit,
})
I was expecting to get a list of all orders where the solutions in the solution table has not been added. Am a bit new to sequelize.
You can try to filter after left join, Sequelize can apply where clause directly on the join or after join.
Orders.findAndCountAll({
where: {
'$orders.solution$': null,
},
include: [
{
model: Solutions,
as: "solution",
required: false
},
],
})
In SQL it's like :
SELECT COUNT(*)
FROM orders o
LEFT JOIN solutions s ON o.id = s.order AND s.order IS NULL
VS
SELECT COUNT(*)
FROM orders o
LEFT JOIN solutions s ON o.id = s.order
WHERE s IS NULL
You can perform a left join with a filter which excludes records from Solutions table if the order does not exit.
Orders.findAndCountAll({
include: [
{
model: Users,
attributes: ['username', 'email', 'uid'],
},
{
model: Solutions,
as: "solution",
required: false,
},
],
where: {
'$solution.order$': null
},
offset: page,
limit,
})
For those coming later to this question, I have come to the conclusion that a LEFT OUTER JOIN between the two tables performs the exact same thing I was looking for. I want to give credit back to #Shengda Liu and #8bitIcon for the solution given.
In sequelize the solution would involve just adding the required field in the include statement on the target model to enforce the rule(i.e) find all rows that have an associations in the target associated table. For my case, the solution is as follows.
Orders.findAndCountAll({
include: [
{
model: Users,
attributes: ['username', 'email', 'uid'],
},
{
model: Solutions,
as: "solution",
required: true, // this is the only thing I have added
/// and removed the where clause in the include field.
},
],
offset: page,
limit,
})

SequelizeJS Eager Loading two identical N:M models one returns data other does not

I have one model Link and two other Models Overlay & Tracker. Both have many to many relationship with Link model. The data is in MySQL database.
Overlay & Tracker uses identical association with Link yet when I try to eager load them using sequlize query, Tracker values are always null (returns as empty array).
For example:
Consider this example query.
const result = await Link.findAndCountAll({
where: {
userId: req.user.id
},
limit: limit,
offset: offset,
order: [
['createdAt', 'DESC']
],
include: [
{model: Tracker, as: 'trackers'},
{model: Overlay, as: 'overlays'}
]
});
The returned result includes overlays correctly but trackers is always empty array. I am really puzzled because both models are identical in associations.
Models
Link.js
'use strict';
module.exports = (sequelize, DataTypes) => {
const Link = sequelize.define('Link', {
name: DataTypes.STRING,
// ... removed for simplicity
}
}, {
freezeTableName: true,
tableName: 'links'
});
Link.associate = function(models) {
// associations can be defined here
Link.belongsTo(models.User, {
as: 'user',
foreignKey: 'userId'
});
Link.belongsToMany(models.Overlay, {
as: 'overlays',
through: models.LinkOverlays,
foreignKey: 'linkId',
targetKey: 'id',
});
Link.belongsToMany(models.Tracker, {
as: 'trackers',
through: models.LinkTrackers,
foreignKey: 'trackerId',
targetKey: 'id',
});
};
return Link;
};
Overlay.js
'use strict';
module.exports = (sequelize, DataTypes) => {
const Overlay = sequelize.define('Overlay', {
name: DataTypes.STRING(100),
// ... removed for simplicity
}, {
freezeTableName: true,
tableName: 'overlays'
});
Overlay.associate = function(models) {
// associations can be defined here
Overlay.belongsTo(models.User, {
foreignKey: 'userId'
});
Overlay.belongsToMany(models.Link, {
as: 'links',
through: models.LinkOverlays,
foreignKey: 'overlayId',
targetKey: 'id'
});
};
return Overlay;
};
Tracker.js
'use strict';
module.exports = (sequelize, DataTypes) => {
const Tracker = sequelize.define('Tracker', {
name: DataTypes.STRING(100),
// ...
}, {
freezeTableName: true,
timestamps: false,
tableName: 'trackers'
});
Tracker.associate = function(models) {
Tracker.belongsTo(models.User, {
foreignKey: 'userId'
});
Tracker.belongsToMany(models.Link, {
as: 'links',
through: models.LinkTrackers,
foreignKey: 'trackerId',
targetKey: 'id'
});
};
return Tracker;
};
Observations:
The created SQL query also seems to be identical for both associated models. Here is the resulted SQL of the given query
SELECT `Link`.*,
`trackers`.`id` AS `trackers.id`,
`trackers`.`createdat` AS `trackers.createdAt`,
`trackers`.`name` AS `trackers.name`,
`trackers`.`vendor` AS `trackers.vendor`,
`trackers`.`vendortrackerid` AS `trackers.vendorTrackerId`,
`trackers`.`userid` AS `trackers.userId`,
`trackers->LinkTrackers`.`linkid` AS `trackers.LinkTrackers.linkId`,
`trackers->LinkTrackers`.`trackerid` AS `trackers.LinkTrackers.trackerId`
,
`overlays`.`id` AS `overlays.id`,
`overlays`.`name` AS `overlays.name`,
`overlays`.`type` AS `overlays.type`,
`overlays`.`config` AS `overlays.config`,
`overlays`.`userid` AS `overlays.userId`,
`overlays`.`createdat` AS `overlays.createdAt`,
`overlays`.`updatedat` AS `overlays.updatedAt`,
`overlays->LinkOverlays`.`linkid` AS `overlays.LinkOverlays.linkId`,
`overlays->LinkOverlays`.`overlayid` AS `overlays.LinkOverlays.overlayId`
FROM (SELECT `Link`.`id`,
`Link`.`name`,
`Link`.`originalurl`,
`Link`.`code`,
`Link`.`type`,
`Link`.`userid`,
`Link`.`hitcount`,
`Link`.`opengraph`,
`Link`.`createdat`,
`Link`.`updatedat`
FROM `links` AS `Link`
WHERE `Link`.`userid` = 1
ORDER BY `Link`.`createdat` DESC
LIMIT 0, 10) AS `Link`
LEFT OUTER JOIN ( `link_trackers` AS `trackers->LinkTrackers`
INNER JOIN `trackers` AS `trackers`
ON `trackers`.`id` =
`trackers->LinkTrackers`.`trackerid`)
ON `Link`.`id` = `trackers->LinkTrackers`.`trackerid`
LEFT OUTER JOIN ( `link_overlays` AS `overlays->LinkOverlays`
INNER JOIN `overlays` AS `overlays`
ON `overlays`.`id` =
`overlays->LinkOverlays`.`overlayid`)
ON `Link`.`id` = `overlays->LinkOverlays`.`linkid`
ORDER BY `Link`.`createdat` DESC;
More Information
Below are two relational table model
LinkOverlays.js
'use strict';
module.exports = (sequelize, DataTypes) => {
const LinkOverlays = sequelize.define('LinkOverlays', {
linkId: {
type: DataTypes.INTEGER,
primaryKey: true,
unique: 'linkIdOverlayIdComposite'
},
overlayId: {
type: DataTypes.INTEGER,
primaryKey: true,
unique: 'linkIdOverlayIdComposite'
}
}, {
freezeTableName: true,
timestamps: false,
tableName: 'link_overlays'
});
LinkOverlays.associate = function(models) {
// associations can be defined here
LinkOverlays.belongsTo(models.Link, {
foreignKey: 'linkId',
through: models.LinkOverlays,
targetKey: 'id'
});
// associations can be defined here
LinkOverlays.belongsTo(models.Overlay, {
foreignKey: 'overlayId',
through: models.LinkOverlays,
targetKey: 'id'
});
};
return LinkOverlays;
};
LinkTrackers.js
'use strict';
module.exports = (sequelize, DataTypes) => {
const LinkTrackers = sequelize.define('LinkTrackers', {
linkId: {
type: DataTypes.INTEGER,
primaryKey: true,
unique: 'linkIdTrackerIdComposite'
},
trackerId: {
type: DataTypes.INTEGER,
primaryKey: true,
unique: 'linkIdTrackerIdComposite'
}
}, {
freezeTableName: true,
timestamps: false,
tableName: 'link_trackers'
});
LinkTrackers.associate = function(models) {
LinkTrackers.belongsTo(models.Link, {
foreignKey: 'linkId',
through: models.LinkTrackers,
targetKey: 'id'
});
LinkTrackers.belongsTo(models.Tracker, {
foreignKey: 'trackerId',
through: models.LinkTrackers,
targetKey: 'id'
});
};
return LinkTrackers;
};
I have tried many hours to track down the issue but failed. The most puzzling is that one is working (Overlay) and other is not (Tracker)
In your
link.js
Link.belongsToMany(models.Overlay, {
as: 'overlays',
through: models.LinkOverlays,
foreignKey: 'linkId',
targetKey: 'id',
});
Link.belongsToMany(models.Tracker, {
as: 'trackers',
through: models.LinkTrackers,
foreignKey: 'trackerId', // should be linkId
targetKey: 'id',
});
the relation with LinkOverlays is via linkId but the relation with LinkTrackers is via trackerId it should be linkId as well

graphql multiple associations

I have the following models in sequelize
User
User.associate = models => {
User.hasMany(models.Schedule, {
onDelete: 'CASCADE',
foreignKey: 'patient_id',
sourceKey: 'id',
as: 'patient'
});
User.hasMany(models.Schedule, {
onDelete: 'CASCADE',
foreignKey: 'professional_id',
sourceKey: 'id',
as: 'professional'
});
};
Schedule
Schedule.associate = models => {
Schedule.belongsTo(models.User, {
foreignKey: 'patient_id',
targetKey: 'id',
as: 'patient'
});
};
Schedule.associate = models => {
Schedule.belongsTo(models.User, {
foreignKey: 'professional_id',
targetKey: 'id',
as: 'professional'
});
};
And the following schemas in graphql
user
type User {
id: ID!
schedules1: [Schedule] #relation(name: "patient")
schedules2: [Schedule] #relation(name: "professional")
}
schedule
type Schedule {
id: ID!
date : Date
patient: User! #relation(name: "patient")
professional: User! #relation(name: "professional")
}
but when try a query of users with schedules like this
{
users{
id
name
schedules1{
id
}
}
}
i got the following result
{
"data": {
"users": [
{
"id": "1",
"name": "Gregorio",
"schedules1": null
},
...
My question is, how i can model multiple associations in graphql, i tried the anotation #relation without success.
noob mistake... i forgot to add the resolver to the relations.
User: {
patient: async(user, args, { models }) =>
await models.Schedule.findAll({
where: {
patient_id: user.id,
},
}),
professional: async(user, args, { models }) =>
await models.Schedule.findAll({
where: {
professional_id: user.id,
},
})
},

sequelizejs eager fetch many to many

I am trying to fetch eager on a join table in Nodejs with Sequelizejs v3 .
So, 1 Tag can belong to Many Images, and Many Images can have multiple tags.
Tag 1 - > M ImageTag M < - 1 Image
I am getting Unhandled rejection Error: Tag is not associated to ImageDetails when i tried to excute a query.
function getImagesFromAlbum(albumid, callback, errCallback){
ImageDetails.findAll({where: { AlbumId: albumid }, include: [{model: Tag}]}).then((data) => {
return callback(data)
}).catch((err) => {
return errCallback(err)
})
}
The expected return result should be the data according to the albumid, with the assiociate tags for that image
Here are the relationship joining
ImageDetails.belongsToMany(Tag, { as: { singular: "tag", plural: "tags" }, through: { model: ImageTag, unique: false }, foreignKey: "ImageId"})
Tag.belongsToMany(ImageDetails, { as: { singular: "image", plural: "images" }, through: { model: ImageTag, unique: false }, foreignKey: "TagId"})
Here are the model designs
Tag Model
const model = {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true
},
name: {
type: Sequelize.STRING
}
}
const name = "Tag"
ImageTag model (Join Table)
const model = {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true
}
}
const name = "ImageTag"
ImageDetails model
import { Sequelize, db } from "../config/MySqlConfiguration"
const model = {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true
},
ImageLocation: {
type: Sequelize.STRING
},
originalName: {
type: Sequelize.STRING
}
}
const name = "ImageDetails"
*Note sequelize.define is purposely omitted.
When defining relation between models with use of alias (as) in the belongsToMany, you need to remember to include this alias when eager loading models, so your query would look as follows
ImageDetails.findAll({
where: { AlbumId: albumid },
include: [{ model: Tag, as: 'tags' }]
}).then((data) => {
return callback(data)
}).catch((err) => {
return errCallback(err)
});
And what is the AlbumId you want to perform query on? According to your models definition, the ImageDetails model does not have such a field.

Sequelize Query - Finding records based on many-to-many table and parent table

Given the following sequelize models:
var User = db.define('user', {
name: Sequelize.STRING
});
var Group = db.define('group', {
name: Sequelize.STRING,
public : { type: Sequelize.BOOLEAN, defaultValue: true }
});
Group.belongsToMany(User, { as: 'specialUsers', through: 'user_groups', foreignKey: 'group_id' });
User.belongsToMany(Group, { through: 'user_groups', foreignKey: 'user_id' });
How would I go about finding the Groups for a through the Groups model where the Groups returned should be those where the user has a record in the many to many table -- or -- the group is a public group?
I've tried something like this:
return Group.findAll({
attributes: ['name', 'public'],
include: [{
model: User,
as: 'specialUsers',
where: {
$or : [
{name: 'Neill'},
Sequelize.literal('"group"."public" = true')
]
}
}]
});
return Group.findAll({
attributes: ['name', 'public'],
include: [{
model: User,
as: 'specialUsers',
}],
where: {
$or : {
'$users.name$": 'Neill',
public: true
}
}
});
Should work if you are on a fairly recent version. Note that I moved the where out of the include