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
Related
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,
...
})
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()
I have the following Model:
AuthorModel.hasMany(BookModel);
BookModel.belongsTo(AuthorModel);
Some authors have no books.
I want to select an author whose name or title of one of his books matches the search string.
I can achieve this with the following statement, but only for authors with books in their BookModel
Author.findOne({
include: [{
model: Book,
where: {
[Op.or]: [
{'$author.name$': 'search string'},
{ title: 'search string'}
]
},
}]
})
This gives me more or less the following mysql query:
SELECT
`author`.`name`,
`book`.`title`
FROM `author` INNER JOIN `book`
ON `author`.`id` = `book`.`authorId`
AND ( `author`.`name` = 'search string' OR `book`.`title` = 'search string');
The problem here is, if an author has no books, then the result is empty. Even if there is an author that matches the search criteria.
I tried to set the include to required: false, which gives a left outer join. In that case, I get some not matching results. The where clause is omitted.
How do I have to change my sequelize query, or what would be the proper mysql query?
The MySql query should probably be something like
SELECT
`author`.`name`,
`book`.`title`
FROM `author` LEFT JOIN `book`
ON `author`.`id` = `book`.`authorId`
WHERE ( `author`.`name` = 'search string' OR `book`.`title` = 'search string')
Note how here filtering condition is in WHERE rather than part of JOIN ... ON clause
Basing on the example at Top level where with eagerly loaded models you squizzle query should probably be something like
Author.findOne({
where: {
[Op.or]: [
{'$author.name$': 'search string'},
{ '$Book.title$': 'search string'}
]
},
include: [{
model: Book,
required: false
}]})
In my Node application we have used Sequelize to connect with mysql
Have two table User & UserOption
User table have following fields
user_id(pk)
user_email
UserOption table have following fields
option_id(pk)
user_id(fk)
first_name
I need to list all user by search text in user_email & first_name
Is there any option to search both parent & child table fields in Sequelize?
UPDATE
User table
user_id user_email
1 text#text.com
2 anything#anything.com
3 jhon#smthng.com
UserOption table
option_id user_id first_name
1 1 jhon
2 2 smith
3 3 david
If I search for "jhon", the result will be both user with id 1 and 2
You need to include model UserOption in lookup on model User. This generates a JOIN clause with condition UserOption.user_id = User.user_id, as well as adds specified WHERE clause to perform text lookup on user_email and first_name columns of both tables
User.findAll({
where: {
user_email: { $like: '%text%' }
},
include: [
{
model: UserOption,
where: {
first_name: { $like: '%text%' }
},
attributes: []
}
]
}).then((users) => {
// result
});
EDIT
Basing on your updated question, I think that you can try using sequelize.literal method, but, according to the documentation:
Creates a object representing a literal, i.e. something that will not be escaped.
So it is necessary to escape desired values by yourself
let escValue = sequelize.escape(lookupText);
User.findAll({
where: {
$or: [
{ email: { $like: '%' + lookupText + '%' },
{ id: { $in: sequelize.literal(`(SELECT uo.user_id FROM user_options uo WHERE uo.first_name LIKE CONCAT('%', ${escValue}, '%'))`) }
]
}
}).then((users) => {
// result...
});
It would generate a query which selects users from users table where email LIKE '%text%' or where users.id is in specified query result.
I am very curious if this satisfies your needs, waiting for feedback.
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.