graphql multiple associations - relational-database

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

Related

Sequelize: Wrong column names on junction table columns

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
}

Sequelize 'where' on parent and child when parent hasMany childs

I have 2 models:
class User extends Model {
static associate(models) {
User.hasMany(models.Role, {
foreignKey: 'userId'
});
}
};
User.init({
firstname: {
type: DataTypes.STRING,
},
lastname: {
type: DataTypes.STRING,
},
allowedApps: {
type: DataTypes.ENUM({
values: Object.keys(PORTALS)
}),
allowNull: false
}
}, {
sequelize,
paranoid: true,
modelName: 'User',
});
class Role extends Model {
static associate(models) {
Role.BelongsTo(models.User, {
foreignKey: 'userId'
});
}
};
Role.init({
type: {
type: DataTypes.STRING,
unique: true
},
name: DataTypes.STRING,
userId: {
type: DataTypes.INTEGER,
allowNull: false,
}
}, {
sequelize,
paranoid: true,
modelName: 'Role',
});
I would like to get all users where the firstname OR the role type matches a certain condition. Something like:
User
.findAndCountAll({
where: {
$or: [
{
firstname: "John Doe"
},
{
"$Role.type$": "Admin"
}
]
},
include: [{
model: Role,
}],
}).limit=10,offset=0
.then(users => res.status(200).send(users))
.catch(error => {
return res.sendStatus(500);
});
above query giving me error: "SequelizeDatabaseError: Unknown column 'Role.type' in 'field list'"
I want to search through child model when it has one to many relationship having my limit and offset intact.Same query would give me success if user would have HasOne relation with role.
This is just an example code of what I try to achieve so please ignore any typos and silly mistakes.
After some digging:
await User.findAll({
where: {
$or: [
{
firstname: "John Doe"
},
{
"$Role.type$": "Admin"
}
]
},
include: {
model: Role,
as: 'Role',
required: false
}
});
However, it doesn't make logical sense to select Users that have no associated Role (required: false), while querying such Users with a property that exists on Role ($or: $Role.type$). If we set Required = true, then we violate your initial condition
firstname OR the role type matches a certain condition.
The following addresses this problem:
await User.findAll({
include: {
model: Role,
required: false
}
})
.then(
users => users
.filter(user => user?.firstName === "John Doe" || user.role?.type ===
"Admin");
);

Sequelize throwing error "must be unique"

I am using Sequelize with my mysql database and have a many to many relationship with a CostumerDriverTransaction model as a through table
when I try to create a row form this table I get this error "message": "driverId must be unique"
CostumerDriverTransaction Model
const CostumerDriverTransaction = sequelize.define('CostumerDriverTransaction', {
costumerId: {
type:DataTypes.INTEGER,
},
driverId: {
type:DataTypes.INTEGER,
},
restaurantId: {
type:DataTypes.INTEGER,
},
orderId: DataTypes.INTEGER,
transactionId: {
type:DataTypes.INTEGER,
primaryKey:true,
},
},{});
and this the association:
index.js
db.user.belongsToMany(db.driver,{
through:db.costumerDriverTransaction,
foreignKey:{
name:'costumerId'
}
});
db.driver.belongsToMany(db.user,{
through:db.costumerDriverTransaction,
foreignKey:{
name:'driverId'
}
});
db.user.hasMany(db.costumerDriverTransaction,{
foreignKey:{
name:'costumerId'
}
});
db.driver.hasMany(db.costumerDriverTransaction,{
foreignKey:{
name:'driverId'
}
});
db.costumerDriverTransaction.belongsTo(db.user,{
foreignKey:{
name:'costumerId'
}
});
db.costumerDriverTransaction.belongsTo(db.driver,{
foreignKey:{
name:'driverId'
}
});
what's the problem please ?
As error indicates, you need to set driverId to be unique, that is:
const CostumerDriverTransaction = sequelize.define('CostumerDriverTransaction', {
costumerId: {
type:DataTypes.INTEGER,
},
driverId: {
type:DataTypes.INTEGER,
unique: true
},
restaurantId: {
type:DataTypes.INTEGER,
},
orderId: DataTypes.INTEGER,
transactionId: {
type:DataTypes.INTEGER,
primaryKey:true,
},
},{});
Most of the columns do not need to be specified in your define and will be create automatically, such at the primary keys and relationship fields. If you would like to override them, you can specify them in the column definition (this is only really notable because transactionId in your example would become id when autogenerated.
Here we create models for each of your objects and then define all the different relationships between them. Because the relationship table is a "super" many-to-many by having it's own primary key instead of a composite you can query from it or any of the other models "through" it.
If you don't want the createdAt and updatedAt columns on a table pass timestamps: false, into the options for sequelize.define().
// your other models
const User = sequelize.define('User', {}, {});
const Driver = sequelize.define('Driver', {}, {});
const Restaurant = sequelize.define('Restaurant', {}, {});
const Order = sequelize.define('Order', {}, {});
// the relational table, note that leaving off the primary key will use `id` instead of transactionId
const CostumerDriverTransaction = sequelize.define('CostumerDriverTransaction', {}, {});
// relate the transaction to the other
CostumerDriverTransaction.belongsTo(User, { foreignKey: 'customerId' });
CostumerDriverTransaction.belongsTo(Driver, { foreignKey: 'driverId' });
CostumerDriverTransaction.belongsTo(Restaurant, { foreignKey: 'restaurantId' });
CostumerDriverTransaction.belongsTo(Order, { foreignKey: 'orderId' });
// relate the models to the transactions
User.hasMany(CostumerDriverTransaction, { as: 'transactions', foreignKey: 'customerId' });
// relate models to other models through transactions
User.hasMany(Driver, { as: 'drivers', through: 'CostumerDriverTransaction', foreignKey: 'customerId', otherKey: 'driverId' });
User.hasMany(Restaurant, { as: 'restaurants', through: 'CostumerDriverTransaction', foreignKey: 'customerId', otherKey: 'restaurantId' });
User.hasMany(Order, { as: 'orders', through: 'CostumerDriverTransaction', foreignKey: 'customerId', otherKey: 'orderId' });
// Drivers
Driver.hasMany(CostumerDriverTransaction, { foreignKey: 'driverId' });
Driver.hasMany(User, { as: 'customers', through: 'CostumerDriverTransaction', foreignKey: 'driverId', otherKey: 'customerId' });
Driver.hasMany(Restaurant, { as: 'restaurants', through: 'CostumerDriverTransaction', foreignKey: 'driverId', otherKey: 'restaurantId' });
Driver.hasMany(Order, { as: 'orders', through: 'CostumerDriverTransaction', foreignKey: 'driverId', otherKey: 'orderId' });
// Restaurants
Restaurant.hasMany(CostumerDriverTransaction, { foreignKey: 'restaurantId' });
Restaurant.hasMany(Driver, { as: 'drivers', through: 'CostumerDriverTransaction', foreignKey: 'restaurantId', otherKey: 'driverId' });
Restaurant.hasMany(User, { as: 'customers', through: 'CostumerDriverTransaction', foreignKey: 'restaurantId', otherKey: 'customerId' });
Restaurant.hasMany(Order, { as: 'orders', through: 'CostumerDriverTransaction', foreignKey: 'restaurantId', otherKey: 'orderId' });
// Orders
Order.hasMany(CostumerDriverTransaction, { foreignKey: 'orderId' });
Order.hasMany(Driver, { as: 'drivers', through: 'CostumerDriverTransaction', foreignKey: 'orderId', otherKey: 'driverId' });
Order.hasMany(User, { as: 'customers', through: 'CostumerDriverTransaction', foreignKey: 'orderId', otherKey: 'customerId' });
Order.hasMany(Restaurant, { as: 'restaurants', through: 'CostumerDriverTransaction', foreignKey: 'orderId', otherKey: 'restaurantId' });
This will create models with primary key and relationship columns of:
// User
{
id: primary key,
createdAt: create date,
updatedAt: update date,
}
// Driver
{
id: primary key,
createdAt: create date,
updatedAt: update date,
}
// Restaurant
{
id: primary key,
createdAt: create date,
updatedAt: update date,
}
// Order
{
id: primary key,
createdAt: create date,
updatedAt: update date,
}
// Transaction
{
id: primary key,
userId: relationship to User,
driverId: relationship to Driver,
restaurantId: relationship to Restaurant,
orderId: relationship to Order,
createdAt: create date,
updatedAt: update date,
}

Sequilize query is returning only one row while using include

Context : I am having this problem were I am doing a query using sequilize an it only return's me an array with one position even though I have more than one field that correspond to the query.
This are my two involved models
This is my group.js model
module.exports = (sequelize, DataTypes) => {
const Group = sequelize.define('Group', {
name: DataTypes.STRING,
limit: DataTypes.STRING,
user_id: DataTypes.INTEGER
});
Group.associate = models => {
Group.belongsTo(models.User, { foreignKey: 'user_id' });
};
Group.associate = models => {
Group.hasMany(models.Movement, { foreignKey: 'group_id' });
};
return Group;
}
This is my movement.js model
module.exports = (sequelize, DataTypes) => {
const Mov = sequelize.define('Movement', {
description: DataTypes.STRING,
value: DataTypes.INTEGER,
group_id: DataTypes.INTEGER
});
Mov.associate = models => {
Mov.hasOne(models.Group, { foreignKey: 'group_id' });
};
return Mov;
}
This is my query (where you will see that I am doing an INNER JOIN to SUM the fields of the Movement table)
router.get('/', verify, async (req, res) => {
try {
const group = await Group.findAll({
attributes: [
'id',
'name',
'limit',
[sequelize.fn('SUM', sequelize.col('Movements.value')), 'total_spent'],
],
include: [{
attributes: [], // this is empty because I want to hide the Movement object in this query (if I want to show the object just remove this)
model: Movement,
required: true
}],
where: {
user_id: req.userId
}
});
if (group.length === 0) return res.status(400).json({ error: "This user has no groups" })
res.status(200).json({ groups: group }) //TODO see why this is onyl return one row
} catch (error) {
console.log(error)
res.status(400).json({ Error: "Error while fetching the groups" });
}
});
Problem is that it only return's one position of the expected array :
{
"groups": [
{
"id": 9,
"name": "rgrgrg",
"limit": 3454354,
"total_spent": "2533"
}
]
}
It should return 2 positions
{
"groups": [
{
"id": 9,
"name": "rgrgrg",
"limit": 3454354,
"total_spent": "2533"
},
{
"id": 9,
"name": "rgrgrg",
"limit": 3454354,
"total_spent": "2533"
}
]
}
This is the query sequilize is giving me:
SELECT `Group`.`id`, `Group`.`name`, `Group`.`limit`, SUM(`Movements`.`value`) AS `total_spent` FROM `Groups` AS `Group` INNER JOIN `Movements` AS `Movements` ON `Group`.`id` = `Movements`.`group_id` WHERE `Group`.`user_id` = 1;
I guess you need to add an appropriate group by clause as follows -
const group = await Group.findAll({
attributes: [
'id',
'name',
'limit',
[sequelize.fn('SUM', sequelize.col('Movements.value')), 'total_spent'],
],
include: [{
attributes: [], // this is empty because I want to hide the Movement object in this query (if I want to show the object just remove this)
model: Movement,
required: true
}],
where: {
user_id: req.userId
},
group: '`Movements`.`group_id`'
});
Many-to-many "through" table with multiple rows of identical foreign key pairs only returns one result?
I just ran into this bug and added this options to the main query:
{
raw: true,
plain: false,
nest: true
}
Then you just merge the query.
It's a workaround, but might help someone.

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