rewrite left outer join for sub queries in bookshelf.js - mysql

Note : I have not shared database schema as I am mainly looking for a help only w.r.t. last step which is 'left outer join' on 2 sub-queries.
select *
from
(select id
from Action
where id = 3) AS act1
left Outer Join
(select Action.name,
completed_At as completedAt,
deadline, notes,
ActionAssignedTo.action_Id as actionId,
from Action
inner join Employee
on Action.created_By_Id = Employee.id
and Employee.vendor_Id = 2
inner join ActionAssignedTo
on Action.id = ActionAssignedTo.action_Id
and ActionAssignedTo.action_Id = 3
where Action.created_By_Id = 7
group by Action.id
limit 2) AS act2
on act1.id = act2.actionId
I need to write this above query using Bookshelf
let options = {columns: [ 'Action.name', 'completed_At as completedAt',
'deadline', 'notes',
'ActionAssignedTo.action_Id as actionId',
]};
let action2 = new Action();
action2.query().innerJoin('Employee', function () {
this.on('Action.created_By_Id', 'Employee.id')
.andOn('Employee.vendor_Id', bookshelf.knex.raw(1));
});
action2.query().innerJoin('ActionAssignedTo', function () {
this.on('Action.id', 'ActionAssignedTo.action_Id')
.andOn('ActionAssignedTo.action_Id', bookshelf.knex.raw(5));
});
action2.query().where(function() {
this.where('Action.created_By_Id', empId)
});
action2.query().groupBy('Action.id');
action2.query().limit(2);
action2.query().columns(options.columns);
let action1;
action1 = Action.where('id', actionId);
action1.query().columns('id');
return bookshelf.knex.raw('select * from '
+ '(' + action1.query().toString() + ') AS act1'
+ ' left Outer Join '
+ '(' + action2.query().toString() + ') AS act2'
+ ' on act1.id = act2.actionId');
I am not keen on using bookshelf.knex.raw for using the left Outer Join as the output given by knex.raw and bookshelf differ.
Is there a way I can do the 'left Outer Join' directly using bookshelf library.
I looked into the code but it seems leftOuterJoin only takes table name as the first parameter and what I need is a query.

I think your main problem is that you're using Bookshelf like you would be using knex. Bookshelf is meant to be used with models you would define and then query on them.
Here is an example of what you should have as model
// Adding registry to avoid circular references
// Adding camelcase to get your columns names converted to camelCase
bookshelf.plugin(['bookshelf-camelcase', 'registry']);
// Reference: https://github.com/brianc/node-pg-types
// These two lines convert all bigint values coming from Postgres from JS string to JS integer.
// Removing these lines will mess up with Bookshelf count() methods and bigserial values
pg.types.setTypeParser(20, 'text', parseInt);
const Action = db.bookshelf.Model.extend({
tableName: 'Action',
createdBy: function createdBy() {
return this.belongsTo(Employee, 'id', 'created_By_Id');
},
assignedTo: function assignedTo() {
return this.hasMany(ActionAssignedTo, 'action_id');
},
});
const Employee = db.bookshelf.Model.extend({
tableName: 'Employee',
createdActions: function createdActions() {
return this.hasMany(Action, 'created_By_Id');
},
});
const ActionAssignedTo = db.bookshelf.Model.extend({
tableName: 'ActionAssignedTo',
action: function action() {
return this.belongsTo(Action, 'id', 'action_Id');
},
employee: function employee() {
return this.belongsTo(Employee, 'id', 'employee_Id');
},
});
module.exports = {
Action: db.bookshelf.model('Action', Action),
Employee: db.bookshelf.model('Employee', Employee),
ActionAssignedTo: db.bookshelf.model('ActionAssignedTo', ActionAssignedTo),
db,
};
You would then be able to fetch your results with a query like this
const Model = require('model.js');
Model.Action
.where({ id: 3 })
.fetchAll({ withRelated: ['createdBy', 'assignedTo', 'assignedTo.employee'] })
.then(data => {
// Do what you have to do
});
What your want to achieve is not possible with only one query in Bookshelf. You probably need to do a first query using knex to get a list of Action ids and then give them to Bookshelf.js
db.bookshelf.knex.raw(`
select ActionAssignedTo.action_Id as actionId,
from Action
inner join Employee
on Action.created_By_Id = Employee.id
and Employee.vendor_Id = ?
inner join ActionAssignedTo
on Action.id = ActionAssignedTo.action_Id
and ActionAssignedTo.action_Id = ?
where Action.created_By_Id = ?
group by Action.id
limit ?`,
[2, 3, 7, 2]
)
.then(result => {
const rows = result.rows;
// Do what you have to do
})
And then use the recovered Ids to get your Bookshelf query like this
Model.Action
.query(qb => {
qb.whereIn('id', rows);
})
.fetchAll({
withRelated: [{
'createdBy': qb => {
qb.columns(['id', 'firstname', 'lastname']);
},
'assignedTo': qb => {
qb.columns(['action_Id', 'employee_Id']);
},
'assignedTo.employee': qb => {
qb.columns(['id', 'firstname', 'lastname']);
},
}],
columns: ['id', 'name', 'completed_At', 'deadline', 'notes']
})
.fetchAll(data => {
// Do what you have to do
});
Note that the columns used for joins MUST BE in the columns list for each table. If you omit the columns, all the columns will be selected.
By default, Bookshelf will retrieve all columns and all root objects. The default is kind of LEFT OUTER JOIN.

Related

How to write an Inner Join query using sequelize ORM?

My inner join query looks as below
SELECT list.id, list.name,
sa.createdBy
FROM list
INNER JOIN data sa
ON sa.listId = list.id
WHERE sa.type = 'type1'
and sa.data = 'data1'
I am trying to write the above query using sequelize ORM.
I have written the following query but it is not giving desired result.
list.findAll({
include: [{
model: data,
required: true
where: {type: 'type1'}
}]
}).then(list => {
/* ... */
});
Your where clause doesn't fully contain the query you wrote
list.findAll({
include: [{
model: data,
required: true,
where: {
type: 'type1', // sa.type = 'type1'
data: 'data1', // sa.data = 'data1'
},
}]
}).then(lists => { // renamed to lists to prevent shadowing the "list" model variable
/* ... */
});

MySql Store Procedure Search Like

I have search field with param called "query" this param will search different columns for a match. It works but the query has to match exactly in order to get a return. I have tried using '%' but I dont think I am using it correctly. Im trying to have more generatic search.
CREATE DEFINER=`root`#`localhost` PROCEDURE `client_search_reps`(IN offset INT, IN row_count INT, IN query varchar(100))
BEGIN
SELECT
U.Id,
U.RoleId,
UP.FirstName,
UP.LastName,
UP.FileUrl,
L.City,
L.Zip,
CP.Name,
CP.Url,
CP.Phone,
CP.Email,
P.ProductOne,
P.ProductTwo,
P.ProductThree,
P.ProductFour,
FROM user_profiles AS UP
LEFT JOIN users AS U ON U.Id = UP.UserId
LEFT JOIN location AS L ON L.UserProfileId = UP.UserId
LEFT JOIN company_profile AS CP ON CP.UserId = UP.UserId
LEFT JOIN products AS P ON P.UserId = U.Id
WHERE UP.FirstName LIKE query || UP.LastName LIKE query || CP.Name LIKE query
|| CP.Phone LIKE query || CP.Email LIKE query
LIMIT offset, row_count;
END
Below is my React.Js code just in case it helps understand my issue.
searchAccount = (query) => {
profileServices
.searchAccounts(0, query)
.then(this.searchSuccess)
.catch(this.searchError);
};
searchSuccess = (data) => {
let accounts = data.item.pagedItems;
this.setState({
mappedProfiles: accounts.map(this.mapSearch),
currentItems: data.item.totalCount,
});
};
searchError = (data) => {
swal({
title: "Search is Broad",
text: "Search by: Company Name, Phone, or Email",
icon: "warning",
buttons: true,
dangerMode: true,
});
};
onSearch = (e) => {
let value = e.target.value;
this.setState((prevState) => {
return {
...prevState,
query: value,
};
});
};
clearSearch = () => {
this.setState((prevState) => {
return {
...prevState,
query: "",
};
});
// this.getProfiles(0);
};
search = () => {
if (this.state.query.length > 0 ? this.searchAccount(this.state.query) : 0);
this.setState({
searchModal: true,
});
};
You can add SQL's wildcard character to the query
CREATE PROCEDURE client_search_reps(
offset INT,
row_count INT,
query varchar(100)
)
BEGIN
SET query = CONCAT('%', query, '%');
...
Btw, it's better to use standard OR instead of non-standard || (the || is deprecated in MySQL 8.0.17 and has different behavior depending on the sql_mode being used in all versions).

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()

Query in Mysql and Node.js

I have this Mysql query that is working fine. However, I need to add 2 more conditions and I'm not sure how to do this.
//index.js
module.exports = {
getHomePage: (req, res) => {
let query ='SELECT Tbl_Email_mensagens.codigo AS Codigo, Tbl_Email_mensagens.mensagem AS Mensagem,Tbl_Email_mensagens.celular AS Celular, cm_custmaster.fullname AS NomeCompleto FROM Tbl_Email_mensagens LEFT JOIN cm_custmaster ON Tbl_Email_mensagens.celular = cm_custmaster.mobile';
// execute query
db.query(query, (err, result) => {
if (err) {
res.redirect('/');
}
res.render('index.ejs', {
title: ""
,players: result
});
});
},
};
I then need to add these 2 conditions:
Where group = '7' and send = '0'
Very thanks!
SELECT Tbl_Email_mensagens.codigo AS Codigo,
Tbl_Email_mensagens.mensagem AS Mensagem,
Tbl_Email_mensagens.celular AS Celular,
cm_custmaster.fullname AS NomeCompleto
FROM Tbl_Email_mensagens
LEFT JOIN cm_custmaster ON Tbl_Email_mensagens.celular = cm_custmaster.mobile
WHERE Tbl_Email_mensagens.`group` = 7
AND Tbl_Email_mensagens.send = 0
Pay attention - the word group is reserved one, so it MUST be wrapped into backticks. But it is more safe to rename it, to some group_number, for example.
Whereas the quotes over the values are excess (you may store them if according field has any string datatype).

Query records that does not have an entry in another table using Sequelize include clause

Given Users table and Ratings table
How do I query all user records from Users table that does not have any rating record in Ratings table using Sequelize include clause
Note: Sequelize version 5.x
Thanks in advance
You can do this in two ways depending on how your models are defined.
1. Get all Users along with Ratings by using Sequelize Eager Loading. Then filter where user does not have any ratings.
const users = Users.findAll({
include: [Ratings]
});
const filteredUsers = users.filter(user => user.ratings.length === 0);
2. Get all userIds from the Ratings table and then pass these userIds to the where clause using the notIn Sequelize operator
const ratings = Ratings.findAll({
attributes: ["userId"],
group: ["userId"]
});
const userIds = ratings.map(rating => rating.userId);
const filteredUsers = Users.findAll({
where: {
userId: { [Op.notIn]: userIds }
}
});
Try incorporating a sequelize literal in the where clause:
const ratings = Ratings.findAll({
attributes: ["userId"],
group: ["userId"],
where: {
$and: [
sequelize.literal(`NOT EXISTS (
SELECT 1 FROM Ratings r
WHERE r.userId = User.id
)`),
],
},
});
Assuming you have a relationship between Users and Ratings in your models, this can be accomplished in a single query by using a left outer join followed by a filter on the client side.
In your model definition:
Users.hasMany(Ratings, { foreignKey: 'user_id' });
Ratings.belongsTo(Users, { foreignKey: 'user_id' });
In your query:
const users = await Users.findAll({
include: [
{
model: Ratings,
required: false // left outer join
}
]
});
const usersWithoutRatings = users.filter(u => u.user_ratings.length === 0);