Convert SQL query to sequelize query with multiple tables - mysql

I've been at this for several days attempting to convert what I thought was a relatively simple SQL query into sequelize format. I cannot seem to figure it out for the life of me. I'm relatively new to sequelize, and my SQL skills can use some help as well.
Any help is greatly appreciated, thanks!
Here is the SQL query I have (which works for what I'm attempting to do) which I'm struggling to get to work in sequelize:
SELECT
book.id,
book.author,
book.title,
book_type.type,
book_sub_type.sub_type,
book_location.location,
book_language.language
FROM book
INNER JOIN book_type ON book.book_type_id = book_type.id
INNER JOIN book_sub_type ON book.book_sub_type_id = book_sub_type.id
INNER JOIN book_location ON book.book_location_id = book_location.id
INNER JOIN book_language ON book.book_language_id = book_language.id
WHERE
book.author LIKE '%history%' OR
book.title LIKE '%history%' OR
book_type.type LIKE '%history%' OR
book_sub_type.sub_type LIKE '%history%' OR
book_language.language LIKE '%history%' OR
book_location.location LIKE '%history%'
ORDER BY book_type.type, book_sub_type.sub_type;
Here is as far as I have gotten (this sequelize query returns 0 results because it is searching for the substring "history" in all columns, instead of at least one column):
const books = await Book.findAll({
where: {
[Op.or]: [
{author: { [Op.substring]: 'history' }},
{title: { [Op.substring]: 'history' }}
]
},
attributes: ['id', 'author', 'title'],
include: [
{
model: BookType,
attributes: ['type'],
where: {
type: { [Op.substring]: 'history' }
}
},
{
model: BookSubType,
attributes: ['sub_type'],
where: {
sub_type: { [Op.substring]: 'history' }
}
},
{
model: BookLanguage,
attributes: ['language'],
where: {
language: { [Op.substring]: 'history' }
}
},
{
model: BookLocation,
attributes: ['location'],
where: {
location: { [Op.substring]: 'history' }
}
},
]
});
My schema is as follows:
`book` table columns:
`id`, `author`, `title`, `book_type_id`, `book_sub_type_id`,
`book_language_id`, `book_location_id`
`book_type` table columns:
`id`, `type`
`book_sub_type` table columns:
`id`, `sub_type`
`book_location` table columns:
`id`, `location`
`book_language` table columns:
`id`, `language`
In sequelize, I have the following relationships established:
Book.belongsTo(BookType);
Book.belongsTo(BookSubType);
Book.belongsTo(BookLanguage);
Book.belongsTo(BookLocation);
BookType.hasMany(Book);
BookSubType.hasMany(Book);
BookLanguage.hasMany(Book);
BookLocation.hasMany(Book);
The output should be 7 columns:
book.id, book.author, book.title, book_type.type, book_sub_type.sub_type, book_location.location, book_language.language

Sequelize build a SQL with a conditions in JOINs, so this is not good aproach. You should remove all where conditions from includes. There was a way in a sequelize <4.0.0 to write conditions to subquery using syntax
where: {
$or: [{
'$book.sub_type$$': 'history'
}, {
'$book_type.type$': 'history'
}]
}
but I think this is not longer supported. Only way would be a custom query or use a sequelize literal in where object.
where: {
[Op.or]: [{
Sequelize.literal(`book_type.type LIKE ${history}`)
}, {
Sequelize.literal(`book_sub_type.sub_type LIKE ${history}`)
}]
}
Keep in mind that with this approach there is a risk of SQL injection so you should validate an input or use some escape character strategy. Check sequelize raw queries and seqeulize literal

Related

how to get rows that has at least 1 association row with sequelize

I have 'Ingredient' and 'Log' Tables like this
[Ingredient Table]
id
..
...
[Log Table]
id
Ingredient_id
record_date
..
...
the relationship is Log.belongsTo(Ingredeint)
how can I find all ingredients which have at least 1 row of Log?
I mean when I searching the Ingredients, If there is no related Log on Ingredient, I don't want to include that Ingredient on my search result.
what I did now to accomplish that is
const ingredients = await Ingredient.findAll({
include: {
model: Log
},
group: "id",
attributes: {
include: [
[sequelize.fn("COUNT", sequelize.col("record_date")), "order_count"]
]
}
})
const sortedIngredient = ingredients
.filter(ingredient => ingredient.dataValues.order_count > 0)
But I think there would be a better way.
Thank you for reading this.
If I understand you correctly you want to do a inner join in your include, then you would only return ingredients that have some match in the included model.
Try to change the include to:
include: {
model: Log
required: true, // <-- Add this row
}
More info about require can be found in the docs: https://sequelize.org/master/class/lib/model.js~Model.html#static-method-findAll
Another option that maybe could help you is to add having to filter on a aggregated column, like this:
const ingredients = await Ingredient.findAll({
include: {
model: Log,
},
group: "id",
attributes: {
include: [
[sequelize.fn("COUNT", sequelize.col("record_date")), "order_count"],
],
},
having: sequelize.literal("`order_count` > 0"), // <-- Add this row
});
Does that help?

How can I convert this raw query to sequelize ORM

The relationship between models is as:
I have three tables Staff,Modules,Tests
Staff (many-to-many) Modules
Modules (one-to-many) Tests
This is my raw query:
select *,count(distinct tests.testId),count(distinct staffModules.id) from modules
left join tests on modules.moduleId = tests.moduleModuleId
left join staffModules on modules.moduleId = staffModules.moduleModuleId
group by modules.moduleId
LIMIT 5 OFFSET 0
I have tried replicating it with Sequelize ORM but It doesn't work:
let resultModules = await modules.findAll({
attributes: ['moduleName'],
include: [{
model: staff,
attributes:['staffName'],
through: { attributes: [[sequelize.fn('COUNT', sequelize.col('staffStaffId')), 'staffCount']] },
},
{
model: tests,
attributes:['testsTaken','testsCompleted','testName'],
}
],
limit:parseInt(limit),offset:parseInt(offset),
});
Thank you for your time.
Maybe use possibility of raw queries for situations like this? ( http://docs.sequelizejs.com/manual/tutorial/raw-queries.html ) [ I would comment it, just don't have enough reputation ]
Try following Attributes in modules findAll -
attributes: [
"moduleName",
[Sequelize.literal("(SELECT count(DISTICT testId) FROM tests T WHERE (CONDITION) )"), "testIdCount"]
]
Only showing for test count can add the attribute for staffModules.

Sequilize association for 2 foriegn key of same model

I need to execute below query using sequelize
SELECT * FROM haulerrfquotes
LEFT JOIN quotes ON quotes.jobId = haulerrfquotes.jobId AND haulerrfquotes.haulerId = quotes.haulerId
WHERE haulerrfquotes.jobId = '11'
But i am not getting how to use two foriegn keys in same model(haulerrfquotes) and to create association of both foriegn keys to single model (quotes)
In sequelize, you could define your own join condition by supplying the on options in the include object. Here's an example to perform the query in question
haulerrfquotes.findAll({
where: {
jobId: 11
},
include: [{
model: quotes,
required: false,
on: {
jobId: {
$col: 'haulerrfquotes.jobId'
},
haulerId: {
$col: 'haulerrfquotes.haulerId'
}
}
}]
}).then(result => {
// The rest of your logic here...
});

Make a join query in loopback.io

I am trying to build a simple application using loopback.io as process of my learning. I have set up the project, created models and apis are working fine.
Now I am trying to create a custom api which can get the data from two different models by making a join query. So i have a two models
stories : id, title, noteId
notes : id , desc
i have stories.js file as
module.exports = function(Stories) {
Stories.list = function(cb) {
// make a join query
};
Stories.remoteMethod(
'list', {
http: {
path: '/list',
verb: 'get'
},
returns: {
arg: 'list',
type: 'array'
}
}
);
};
In general i will make a join in php api but here i am bit confused.Can i pass a raw query to database here or does loopback has some different way of achieving this. Any help would be appreciated.
You don't need to pass sql query. You can query data using PersistedModel find method by using include filter
In order to use include filter you have to create model relation.
For example:
Note relation:
"relations": {
"stories": {
"type": "hasMany",
"model": "Story",
"foreignKey": "noteId"
}
},
Query:
Note.find({include: ['stories']}, function(err, data) { ... });

How to make join queries using Sequelize on Node.js

I am using sequelize ORM; everything is great and clean, but I had a problem when I use it with join queries.
I have two models: users and posts.
var User = db.seq.define('User',{
username: { type: db.Sequelize.STRING},
email: { type: db.Sequelize.STRING},
password: { type: db.Sequelize.STRING},
sex : { type: db.Sequelize.INTEGER},
day_birth: { type: db.Sequelize.INTEGER},
month_birth: { type: db.Sequelize.INTEGER},
year_birth: { type: db.Sequelize.INTEGER}
});
User.sync().success(function(){
console.log("table created")
}).error(function(error){
console.log(err);
})
var Post = db.seq.define("Post",{
body: { type: db.Sequelize.TEXT },
user_id: { type: db.Sequelize.INTEGER},
likes: { type: db.Sequelize.INTEGER, defaultValue: 0 },
});
Post.sync().success(function(){
console.log("table created")
}).error(function(error){
console.log(err);
})
I want a query that respond with a post with the info of user that made it. In the raw query, I get this:
db.seq.query('SELECT * FROM posts, users WHERE posts.user_id = users.id ').success(function(rows){
res.json(rows);
});
My question is how can I change the code to use the ORM style instead of the SQL query?
While the accepted answer isn't technically wrong, it doesn't answer the original question nor the follow up question in the comments, which was what I came here looking for. But I figured it out, so here goes.
If you want to find all Posts that have Users (and only the ones that have users) where the SQL would look like this:
SELECT * FROM posts INNER JOIN users ON posts.user_id = users.id
Which is semantically the same thing as the OP's original SQL:
SELECT * FROM posts, users WHERE posts.user_id = users.id
then this is what you want:
Posts.findAll({
include: [{
model: User,
required: true
}]
}).then(posts => {
/* ... */
});
Setting required to true is the key to producing an inner join. If you want a left outer join (where you get all Posts, regardless of whether there's a user linked) then change required to false, or leave it off since that's the default:
Posts.findAll({
include: [{
model: User,
// required: false
}]
}).then(posts => {
/* ... */
});
If you want to find all Posts belonging to users whose birth year is in 1984, you'd want:
Posts.findAll({
include: [{
model: User,
where: {year_birth: 1984}
}]
}).then(posts => {
/* ... */
});
Note that required is true by default as soon as you add a where clause in.
If you want all Posts, regardless of whether there's a user attached but if there is a user then only the ones born in 1984, then add the required field back in:
Posts.findAll({
include: [{
model: User,
where: {year_birth: 1984}
required: false,
}]
}).then(posts => {
/* ... */
});
If you want all Posts where the name is "Sunshine" and only if it belongs to a user that was born in 1984, you'd do this:
Posts.findAll({
where: {name: "Sunshine"},
include: [{
model: User,
where: {year_birth: 1984}
}]
}).then(posts => {
/* ... */
});
If you want all Posts where the name is "Sunshine" and only if it belongs to a user that was born in the same year that matches the post_year attribute on the post, you'd do this:
Posts.findAll({
where: {name: "Sunshine"},
include: [{
model: User,
where: ["year_birth = post_year"]
}]
}).then(posts => {
/* ... */
});
I know, it doesn't make sense that somebody would make a post the year they were born, but it's just an example - go with it. :)
I figured this out (mostly) from this doc:
http://docs.sequelizejs.com/en/latest/docs/models-usage/#eager-loading
User.hasMany(Post, {foreignKey: 'user_id'})
Post.belongsTo(User, {foreignKey: 'user_id'})
Post.find({ where: { ...}, include: [User]})
Which will give you
SELECT
`posts`.*,
`users`.`username` AS `users.username`, `users`.`email` AS `users.email`,
`users`.`password` AS `users.password`, `users`.`sex` AS `users.sex`,
`users`.`day_birth` AS `users.day_birth`,
`users`.`month_birth` AS `users.month_birth`,
`users`.`year_birth` AS `users.year_birth`, `users`.`id` AS `users.id`,
`users`.`createdAt` AS `users.createdAt`,
`users`.`updatedAt` AS `users.updatedAt`
FROM `posts`
LEFT OUTER JOIN `users` AS `users` ON `users`.`id` = `posts`.`user_id`;
The query above might look a bit complicated compared to what you posted, but what it does is basically just aliasing all columns of the users table to make sure they are placed into the correct model when returned and not mixed up with the posts model
Other than that you'll notice that it does a JOIN instead of selecting from two tables, but the result should be the same
Further reading:
http://docs.sequelizejs.com/en/latest/docs/associations/#one-to-one-associations
http://docs.sequelizejs.com/en/latest/docs/associations/#one-to-many-associations
http://docs.sequelizejs.com/en/latest/docs/models-usage/#eager-loading
Model1.belongsTo(Model2, { as: 'alias' })
Model1.findAll({include: [{model: Model2 , as: 'alias' }]},{raw: true}).success(onSuccess).error(onError);
In my case i did following thing. In the UserMaster userId is PK and in UserAccess userId is FK of UserMaster
UserAccess.belongsTo(UserMaster,{foreignKey: 'userId'});
UserMaster.hasMany(UserAccess,{foreignKey : 'userId'});
var userData = await UserMaster.findAll({include: [UserAccess]});
Create associations in model file and then use include for joins you can use inner includes too join tables.
----model.js
blog1.hasMany(blog2, {foreignKey: 'blog_id'})
blog2.belongsTo(blog1, {foreignKey: 'blog_id'})
-----controller.js
blog2.find({ where: {blog_id:1}, include: [blog1]})