Sequelize - Query to Sequelize methods - mysql

I'm using Sequelize for a node project but I'm having some trouble converting a query in a Sequelize way.
Scenario: I have 2 tables (User and Event) with a n:m relation through UserEvent table. In UserEvent table there is is_creator attribute; what I need is an object with event name, event id and is_creator field.
Example:
[{
id: 1,
name: "Event",
is_creator: true
}]
Here is the query:
SELECT `Event`.`id`,`Event`.`name` as `Name`, `UserEvents`.`is_creator` AS `is_creator` FROM `Events` AS `Event` INNER JOIN (`UserEvents` INNER JOIN `Users` AS `Users` ON `Users`.`id` = `UserEvents`.`UserId`) ON `Event`.`id` = `UserEvents`.`EventId` AND `Users`.`id` = 1;
Unfortunately, I'm unable to "translate" it to Sequelize.
Actual code:
var queryOptions: FindOptions = {
include: [
{
model: db.User,
where: {
id: user.id
}
}
],
};
Query executed:
SELECT `Event`.`id`, `Event`.`uuid`, `Event`.`image`, `Event`.`name`, `Event`.`start_at`, `Event`.`end_at`, `Event`.`location`, `Event`.`description`, `Event`.`createdAt`, `Event`.`updatedAt`, `Users`.`id` AS `Users.id`, `Users`.`uuid` AS `Users.uuid`, `Users`.`email` AS `Users.email`, `Users`.`password` AS `Users.password`, `Users`.`firstName` AS `Users.firstName`, `Users`.`lastName` AS `Users.lastName`, `Users`.`activationCode` AS `Users.activationCode`, `Users`.`resetCode` AS `Users.resetCode`, `Users`.`active` AS `Users.active`, `Users`.`dob` AS `Users.dob`, `Users`.`image` AS `Users.image`, `Users`.`createdAt` AS `Users.createdAt`, `Users`.`updatedAt` AS `Users.updatedAt`, `Users.UserEvent`.`uuid` AS `Users.UserEvent.uuid`, `Users.UserEvent`.`is_creator` AS `Users.UserEvent.is_creator`, `Users.UserEvent`.`createdAt` AS `Users.UserEvent.createdAt`, `Users.UserEvent`.`updatedAt` AS `Users.UserEvent.updatedAt`, `Users.UserEvent`.`UserId` AS `Users.UserEvent.UserId`, `Users.UserEvent`.`EventId` AS `Users.UserEvent.EventId` FROM `Events` AS `Event` INNER JOIN (`UserEvents` AS `Users.UserEvent` INNER JOIN `Users` AS `Users` ON `Users`.`id` = `Users.UserEvent`.`UserId`) ON `Event`.`id` = `Users.UserEvent`.`EventId` AND `Users`.`id` = 1;
Thanks in advance for your cooperation!

Do you want to limit the number of columns that your query is returning?
You can look at the attributes option. You can specify the table fields that you want.
For example, if you want the id, email and firstName of a user, you can specify it this way.
var queryOptions: FindOptions = {
include: [
{
model: db.User,
where: {
id: user.id
},
attributes: ['id', 'email', 'firstName']
}
],
};
You can similarly add the attributes option for the UserEvents table.
PS: I currently, do not have enough reputation to add comments on questions. Otherwise I would've done that.

Related

Sorting of the sequencing belongstomany relationship does not work

The post and user tables created through sequencing have a belongstomany relationship, and a mapping table called like is created.
db.Post.belongsToMany(db.User, { through: 'Like', as: 'Likers' });
db.User.belongsToMany(db.Post, { through: 'Like', as: 'Liked' });
Using this, I wrote the following router to sort posts by the most likes.
const express = require('express');
const { Sequelize, Op } = require('sequelize');
const { Post, User, Image, Comment } = require('../models');
const router = express.Router();
router.get('/top', async (req, res, next) => { // loadTopPostsAPI / GET /posts/top
try {
const posts = await Post.findAll({
limit: 20,
offset: 0,
// Sort posts by the most likes
order: [[Sequelize.literal("(COUNT(`Likers->Like`.`PostId`))"), "ASC"]],
include: [{
model: User, // Post author
attributes: ['id', 'nickname'],
}, {
model: Image, // Post image
}, {
model: Comment, // Post Comment
include: [{
model: User, // Post Comment author
attributes: ['id', 'nickname'],
}],
}, {
model: User, // People who liked the post
as: 'Likers',
attributes: ['id'],
}],
})
res.status(200).json(posts);
} catch (error) {
console.error(error);
next(error);
}
});
But when I run the router, I get the following error
code: 'ER_BAD_FIELD_ERROR',
errno: 1054,
sqlState: '42S22',
sqlMessage: "Unknown column 'Likers->Like.PostId' in 'order clause'",
sql: 'SELECT `Post`.*, `User`.`id` AS `User.id`, `User`.`nickname` AS `User.nickname`, `Images`.`id` AS `Images.id`, `Images`.`src` AS `Images.src`, `Images`.`createdAt` AS `Images.createdAt`, `Images`.`updatedAt` AS `Images.updatedAt`, `Images`.`PostId` AS `Images.PostId`, `Comments`.`id` AS `Comments.id`, `Comments`.`content` AS `Comments.content`, `Comments`.`createdAt` AS `Comments.createdAt`, `Comments`.`updatedAt` AS `Comments.updatedAt`, `Comments`.`UserId` AS `Comments.UserId`, `Comments`.`PostId` AS `Comments.PostId`, `Comments->User`.`id` AS `Comments.User.id`, `Comments->User`.`nickname` AS `Comments.User.nickname`, `Likers`.`id` AS `Likers.id`, `Likers->Like`.`createdAt` AS `Likers.Like.createdAt`, `Likers->Like`.`updatedAt` AS `Likers.Like.updatedAt`, `Likers->Like`.`PostId` AS `Likers.Like.PostId`, `Likers->Like`.`UserId` AS `Likers.Like.UserId` FROM (SELECT `Post`.`id`, `Post`.`title`, `Post`.`desc`, `Post`.`ingredient`, `Post`.`recipes`, `Post`.`tips`, `Post`.`tags`, `Post`.`createdAt`, `Post`.`updatedAt`, `Post`.`UserId` FROM `posts` AS `Post` ORDER BY (COUNT(`Likers->Like`.`PostId`)) ASC LIMIT 0, 20) AS `Post` LEFT OUTER JOIN `users` AS `User` ON `Post`.`UserId` = `User`.`id` LEFT OUTER JOIN `images` AS `Images` ON `Post`.`id` = `Images`.`PostId` LEFT OUTER JOIN `comments` AS `Comments` ON `Post`.`id` = `Comments`.`PostId` LEFT OUTER JOIN `users` AS `Comments->User` ON `Comments`.`UserId` = `Comments->User`.`id` LEFT OUTER JOIN ( `Like` AS `Likers->Like` INNER JOIN `users` AS `Likers` ON `Likers`.`id` = `Likers->Like`.`UserId`) ON `Post`.`id` = `Likers->Like`.`PostId` ORDER BY (COUNT(`Likers->Like`.`PostId`)) ASC;',
parameters: undefined
},
How can I sort by resolving the above error?
Sequelize tries to form Subquery by default with associations and ORDER BY clause is composed within the subquery. However, SQL's ORDER BY has to be at the top level, so many cases when you need ORDER BY, OFFSET, LIMIT, you need to disable the subquery by adding subQuery: false. This will make Sequelize to form the query with JOIN instead of subquery.
await Post.findAll({
limit: 20,
offset: 0,
// Sort posts by the most likes
order: [[Sequelize.literal("(COUNT(`Likers->Like`.`PostId`))"), "ASC"]],
subQuery: false,
...
})
I bet this will make your current error go away but you have a new aggregation error, because this is trying to count full records which is disabled by MySQL by default. (ref: mysql error "ERROR 3029 (HY000): Expression #1 of ORDER BY contains aggregate function and applies to the result of a non-aggregated query")
To fix this issue and do count Likes by Post id, add PARTITION BY.
await Post.findAll({
limit: 20,
offset: 0,
// Sort posts by the most likes
order: [[Sequelize.literal("COUNT(`Likers->Like`.`PostId`) OVER (PARTITION BY `Post`.`id`)"), "ASC"]],
subQuery: false,
...
})

Sequelize 3 tables with where between the 2 joined tables

I want to replicate this MySQL query in Sequelize:
select u.email, a.serverId, ak.apikey from users u
join accounts a on u.id = a.userId
join apikeys ak on ak.userId = u.id
where u.state = "active" and ak.state = 1 and a.serverId = ak.serverId
This is my translation, except for the final and clause (a.serverId = ak.serverId) which I'm not sure how to achieve:
const activeKeys = await models.User.findAll({
attributes: ['email'],
raw: true,
where: {
state: "active"
},
include: [{
model: models.Account,
attributes: ['serverId']
},
{
model: models.Apikey,
attributes: ['apikey'],
where: {
state: 1
}
}]
})
I've tried multiple combinations of associations, these are the current ones:
db.Account.belongsTo(db.User) // account has 1 user
db.User.hasMany(db.Account) // user has multiple accounts
db.User.hasMany(db.Apikey) // user has multiple apikeys
db.Apikey.belongsTo(db.User) // apikey has 1 user
db.Apikey.hasMany(db.Account) // apikey has many accounts
db.Account.hasOne(db.Apikey) // account has 1 apikey
The only part that is missing is the and clause between the 2 included models. How can I achieve it?
Try something like this in the main where:
where: Sequelize.and(
Sequelize.where(Sequelize.col("state"), "=", "active"),
Sequilize.where(Sequelize.col("Account.serverId"),"=", Sequelize.col("Apikey.serverId"))
)

Sequelize Raw query not returning array for has many

I am trying to use a raw sql query in sequelize and have this code. My table structure is an external_profile that has many connections.
const users = await models.sequelize.query("SELECT `External_Profile`.*, AVG(Connections.rating) AS rating, `Connections`.`id` AS `Connections.id`, `Connections`.`known_type` AS `Connections.known_type`, `Connections`.`rating` AS `Connections.rating`, `Connections`.`createdAt` AS `Connections.createdAt`, `Connections`.`updatedAt` AS `Connections.updatedAt`, `Connections`.`UserId` AS `Connections.UserId`, `Connections`.`ExternalProfileId` AS `Connections.ExternalProfileId`, `Connections->User`.`id` AS `Connections.User.id`, `Connections->User`.`first_name` AS `Connections.User.first_name`, `Connections->User`.`last_name` AS `Connections.User.last_name`, `Connections->User`.`email` AS `Connections.User.email`, `Connections->User`.`password` AS `Connections.User.password`, `Connections->User`.`linkedinUrl` AS `Connections.User.linkedinUrl`, `Connections->User`.`createdAt` AS `Connections.User.createdAt`, `Connections->User`.`updatedAt` AS `Connections.User.updatedAt` FROM (SELECT `External_Profile`.`id`, `External_Profile`.`profile_data`, `External_Profile`.`name`, `External_Profile`.`headline`, `External_Profile`.`image`, `External_Profile`.`linkedinUrl`, `External_Profile`.`createdAt`, `External_Profile`.`updatedAt` FROM `External_Profiles` AS `External_Profile`) AS `External_Profile` LEFT OUTER JOIN `Connections` AS `Connections` ON `External_Profile`.`id` = `Connections`.`ExternalProfileId` LEFT OUTER JOIN `Users` AS `Connections->User` ON `Connections`.`UserId` = `Connections->User`.`id` GROUP BY External_Profile.name HAVING AVG(Connections.rating) > 3",
{
type: models.sequelize.QueryTypes.SELECT,
model: [models.External_Profile, models.Connection],
mapToModel: true,
nest: true,
raw: true
})
However this is only returning to me an object for the connection on the external profile. Thats a 1 to many relationship so it should be returning an array. Any ideas on why it wouldn't return all records?
Don't use raw: true
That will cause lots of serialization problem.
use toJSON() instead
const usersDao = await models.sequelize.query("SELECT `External_Profile`.*, AVG(Connections.rating) AS rating, `Connections`.`id` AS `Connections.id`, `Connections`.`known_type` AS `Connections.known_type`, `Connections`.`rating` AS `Connections.rating`, `Connections`.`createdAt` AS `Connections.createdAt`, `Connections`.`updatedAt` AS `Connections.updatedAt`, `Connections`.`UserId` AS `Connections.UserId`, `Connections`.`ExternalProfileId` AS `Connections.ExternalProfileId`, `Connections->User`.`id` AS `Connections.User.id`, `Connections->User`.`first_name` AS `Connections.User.first_name`, `Connections->User`.`last_name` AS `Connections.User.last_name`, `Connections->User`.`email` AS `Connections.User.email`, `Connections->User`.`password` AS `Connections.User.password`, `Connections->User`.`linkedinUrl` AS `Connections.User.linkedinUrl`, `Connections->User`.`createdAt` AS `Connections.User.createdAt`, `Connections->User`.`updatedAt` AS `Connections.User.updatedAt` FROM (SELECT `External_Profile`.`id`, `External_Profile`.`profile_data`, `External_Profile`.`name`, `External_Profile`.`headline`, `External_Profile`.`image`, `External_Profile`.`linkedinUrl`, `External_Profile`.`createdAt`, `External_Profile`.`updatedAt` FROM `External_Profiles` AS `External_Profile`) AS `External_Profile` LEFT OUTER JOIN `Connections` AS `Connections` ON `External_Profile`.`id` = `Connections`.`ExternalProfileId` LEFT OUTER JOIN `Users` AS `Connections->User` ON `Connections`.`UserId` = `Connections->User`.`id` GROUP BY External_Profile.name HAVING AVG(Connections.rating) > 3",
{
type: models.sequelize.QueryTypes.SELECT,
model: [models.External_Profile, models.Connection],
mapToModel: true,
nest: true,
raw: true
})
const cleanUser = usersDao.toJSON()

Getting wrong query generated by Sequelize Assocation

I have two table. One is Order and second one is OrderStatus.
In the orders table order_status_code is foreignKey that references on id to the OrderStatus table.
I have below model association definition.
Order.associate = function(models) {
// associations can be defined here
Order.hasOne(models.OrderItem,{foreignKey: "order_id"}),
Order.hasOne(models.OrderStatus, {foreignKey: "order_status_code"})
};
I am getting below error:
Unknown column 'OrderStatus.order_status_code' in 'field list
when I try to eager loading the OrderStatus.
const orders = await Order.findAll({
where: filter,
include: {
model: OrderStatus
}
})
Below is the query that is being shown on the console.
SELECT `Order`.`id`, `Order`.`buyer_id`, `Order`.`order_status_code`, `Order`.`order_detail`, `Order`.`order_date`, `Order`.`order_number`, `Order`.`created_at`, `Order`.`updated_at`, `OrderStatus`.`id` AS `OrderStatus.id`, `OrderStatus`.`order_status_code` AS `OrderStatus.order_status_code`, `OrderStatus`.`status` AS `OrderStatus.status`, `OrderStatus`.`created_at` AS `OrderStatus.created_at`, `OrderStatus`.`updated_at` AS `OrderStatus.updated_at` FROM `Orders` AS `Order` LEFT OUTER JOIN `OrderStatuses` AS `OrderStatus` ON `Order`.`order_status_code` = `OrderStatus`.`id` WHERE `Order`.`buyer_id` = 23;
I don't know why it is selecting OrderStatus.order_status_code
I fixed it by defining attributes to select from the included model and It fixed the problem for now.
const orders = await Order.findAll({
where: filter,
include: {
model: OrderStatus,
attributes:["status"]
}
})

Sequelize multi-table OR statement unknown column error

I'm trying to do a search on my models, I want to get all users where either the email, firstName, or lastName are like the search string, or where the UsersSecondaryEmails table (related model) includes an email like that string.
Because the parts of the OR statement are in different tables, this is getting a little tricky, and I can only find other StackOverflow answers to help me.
Here is my query (simplified):
const companiesUsersParams = {
where: {
companyId: req.params.companyId,
roleId: role.id
},
include: [{
model: models.Users,
attributes: ['id', 'firstName', 'lastName', 'email'],
where: req.query.s ? {
[Op.or]: [{
firstName: {
[Op.like]: `%${req.query.s}%`
}
}, {
lastName: {
[Op.like]: `%${req.query.s}%`
}
}, {
email: {
[Op.like]: `%${req.query.s}%`
}
}, {
'$usersSecondaryEmails.email$': {
[Op.like]: `%${req.query.s}%`
}
}]
} : null,
include: [{
model: models.UsersSecondaryEmails,
attributes: ['id', 'email'],
as: 'usersSecondaryEmails'
}]
}]
}
When req.query.s is not defined, the query runs as expected (No OR statement), so I know it is not an issue with my associations.
When I run this query WITH req.query.s defined, I get
Unknown column 'usersSecondaryEmails.email' in 'on clause'
And here is the SQL being generated (formatted as best as possible):
SELECT `companiesUsers`.`id`,
`companiesUsers`.`company_id` AS `companyId`,
`companiesUsers`.`user_id` AS `userId`,
`companiesUsers`.`role_id` AS `roleId`,
`companiesUsers`.`created_at` AS `createdAt`,
`companiesUsers`.`updated_at` AS`updatedAt`,
`user`.`id` AS `user.id`,
`user`.`first_name` AS `user.firstName`,
`user`.`last_name` AS `user.lastName`,
`user`.`email` AS `user.email`,
`user->usersSecondaryEmails`.`id` AS `user.usersSecondaryEmails.id`,
`user->usersSecondaryEmails`.`email` AS
`user.usersSecondaryEmails.email`
FROM `CompaniesUsers` AS `companiesUsers`
INNER JOIN `Users` AS `user`
ON `companiesUsers`.`user_id` = `user`.`id` AND
(`user`.`first_name` LIKE '%bob%' OR
`user`.`last_name` LIKE '%bob%' OR
`user`.`email` LIKE '%bob%' OR
`usersSecondaryEmails`.`email` LIKE '%bob%')
LEFT OUTER JOIN `UsersSecondaryEmails` AS `user->usersSecondaryEmails`
ON `user`.`id` = `user->usersSecondaryEmails`.`user_id`
WHERE `companiesUsers`.`company_id` = '1'
AND `companiesUsers`.`role_id` = 20;
Any any advice or links to documentation on multi-table OR statements in Sequelize would be great (I couldn't find anything this advanced in the documentation).
I am not sure how to modify Sequelize code for companiesUsersParams, but your final query should look like below to get desired output. This may help you to rewrite Sequelize code.
Left join on users instead of inner join.
You should move last OR option usersSecondaryEmails.email LIKE '%bob%' to UsersSecondaryEmails join condition
In the where clause, check for the condition (atleast one row should exists in either user table or usersSecondaryEmails for the userid)
SELECT companiesUsers.id,
companiesUsers.company_id AS companyId,
companiesUsers.user_id AS userId,
companiesUsers.role_id AS roleId,
companiesUsers.created_at AS createdAt,
companiesUsers.updated_at ASupdatedAt,
user.id AS user.id,
user.first_name AS user.firstName,
user.last_name AS user.lastName,
user.email AS user.email,
user->usersSecondaryEmails.id AS user.usersSecondaryEmails.id,
user->usersSecondaryEmails.email AS
user.usersSecondaryEmails.email
FROM CompaniesUsers AS companiesUsers
LEFT OUTER JOIN Users AS user ---- #1
ON companiesUsers.user_id = user.id AND
(user.first_name LIKE '%bob%' OR
user.last_name LIKE '%bob%' OR
user.email LIKE '%bob%' OR)
LEFT OUTER JOIN UsersSecondaryEmails AS user->usersSecondaryEmails
ON companiesUsers.user_id = user->usersSecondaryEmails.user_id
AND user->usersSecondaryEmails.email LIKE '%bob%' ---- #2
WHERE companiesUsers.company_id = '1'
AND companiesUsers.role_id = 20
AND (user.email is Not null OR user->usersSecondaryEmails.user_id is Not Null); ---- #3