Sequelize a raw query, with COUNT, SUM, INNER JOIN, WHERE etc - sql

I'm trying to change this raw query in sequelize.
I have trying many things but nothing works like expected.
For the moment i'm here :
ItemModel.findAndCountAll({
attributes: ['count', [sequelize.fn('sum', sequelize.col('rating')), 'sum']],
include: [{
model: models.ReviewModel,
required: true,
where: [``]
}],
where : [{`items.connector->'SOLD'->>'name' = '${itemName}'`}],
})
And the raw query that i'm tring to transform is :
`SELECT COUNT(*) AS count, SUM(reviews.rating) AS sum
FROM items
INNER JOIN reviews ON reviews.id = items.review_id
WHERE items.connector->'SOLD'->>'name' = '${itemName}'`,
);
I'm really a beginner in sql req and sequelize ... Sorry if it's look ugly...

Sequelize is not intended to construct such aggregations. It's more about CRUD.
It's better to use plain SQL query like this:
const results = await Sequelize.query(`SELECT COUNT(*) AS count, SUM(reviews.rating) AS sum
FROM items
INNER JOIN reviews ON reviews.id = items.review_id
WHERE items.connector->'SOLD'->>'name' = $itemName`, {
type: Sequelize.QueryTypes.SELECT,
bind: {
itemName
},
}

Related

Convert SQL query to Sequilize

This is my SQL query, I want to convert this query to sequelize query:
SELECT
`users`.`uId`, `users`.*,
COUNT(`tasks`.`userUId`) AS 'tasksCount'
FROM
`users` AS `users`
LEFT OUTER JOIN
`tasks` AS `tasks` ON `users`.`uId` = `tasks`.`userUId`
GROUP BY
`users`.`uId`;
You can use Sequelize.fn in the attrubutes option and indicate users.id in group option:
const users = await Users.findAll({
attributes: {
include:[[Sequelize.fn('SUM', Sequelize.col('tasks.userId')), 'tasksCount']]
},
include: [{
model: Tasks,
attributes: ['userId']
}],
group: [Sequelize.col('tasks.userId')]
})

Sequelize - Subselect association to filter parent model

I've recently started to use Sequelize 6. Currently trying to express the following query unsuccessfully.
SELECT u.*
FROM users u
JOIN requests r
ON u.id = r.user_id
WHERE (r.status = true AND r.created_at IN (SELECT
MAX(r.created_at)
FROM users u
INNER JOIN requests r ON r.user_id = u.id
GROUP BY u.id))
GROUP BY u.id, r.id
What I have tried so far:
const result = await User.findAll({
offset: page,
subQuery: false,
limit: rowsPerPage,
where: {
'$requests.status$': true,
},
include: [{
as: 'requests',
model: Request,
separate: true,
required: true,
order: [['createdAt', 'desc']],
limit: 1, // problem is here -> error: missing FROM-clause entry for table "requests"
}],
});
In other words, I'm trying to load the parent model only when the association's status value is true on the last record, and return each individual user row alongside with its latest association.
Regarding the limit problem on my comment, this thread might be useful
https://github.com/sequelize/sequelize/issues/11617
I had to change original SQL a little bit to work with Sequelize because a certain combination of required, where didn't work well.
So, I went off from your description.
I'm trying to load the parent model only when the association's status
value is true on the last record
If I understand this correctly, I think you can use having query to achieve this.
const result = await User.findAll({
offset: page,
subQuery: false,
limit: rowsPerPage,
include: [{
as: 'requests',
model: Request,
required: true
}],
having: {
'$requests.createdAt$': [sequelize.fn('MAX', sequelize.col('`requests`.`createdAt`'))],
'$requests.status$': true
},
group: ['users.id'],
});
This will produce a following SQL. Please try this SQL and let me know if this is what you are looking for or not.
SELECT ... FROM `users` AS `users`
INNER JOIN `requests` AS `requests`
ON `users`.`id` = `requests`.`userId`
GROUP BY `users`.`id`
HAVING `requests`.`createdAt` IN (MAX(`requests`.`createdAt`)) AND `requests`.`status` = 1
LIMIT 0, 5;

How to make complex nested where conditions with typeORM?

I am having multiple nested where conditions and want to generate them without too much code duplication with typeORM.
The SQL where condition should be something like this:
WHERE "Table"."id" = $1
AND
"Table"."notAvailable" IS NULL
AND
(
"Table"."date" > $2
OR
(
"Table"."date" = $2
AND
"Table"."myId" > $3
)
)
AND
(
"Table"."created" = $2
OR
"Table"."updated" = $4
)
AND
(
"Table"."text" ilike '%search%'
OR
"Table"."name" ilike '%search%'
)
But with the FindConditions it seems not to be possible to make them nested and so I have to use all possible combinations of AND in an FindConditions array. And it isn't possible to split it to .where() and .andWhere() cause andWhere can't use an Object Literal.
Is there another possibility to achieve this query with typeORM without using Raw SQL?
When using the queryBuilder I would recommend using Brackets
as stated in the Typeorm doc: https://typeorm.io/#/select-query-builder/adding-where-expression
You could do something like:
createQueryBuilder("user")
.where("user.registered = :registered", { registered: true })
.andWhere(new Brackets(qb => {
qb.where("user.firstName = :firstName", { firstName: "Timber" })
.orWhere("user.lastName = :lastName", { lastName: "Saw" })
}))
that will result with:
SELECT ...
FROM users user
WHERE user.registered = true
AND (user.firstName = 'Timber' OR user.lastName = 'Saw')
I think you are mixing 2 ways of retrieving entities from TypeORM, find from the repository and the query builder. The FindConditions are used in the find function. The andWhere function is use by the query builder. When building more complex queries it is generally better/easier to use the query builder.
Query builder
When using the query build you got much more freedom to make sure the query is what you need it to be. With the where you are free to add any SQL as you please:
const desiredEntity = await connection
.getRepository(User)
.createQueryBuilder("user")
.where("user.id = :id", { id: 1 })
.andWhere("user.date > :date OR (user.date = :date AND user.myId = :myId)",
{
date: specificCreatedAtDate,
myId: mysteryId,
})
.getOne();
Note that depending on your used database the actual SQL that you use here needs to be compatible. With that could also come a possible draw back of using this method. You will tie your project to a specific database. Make sure to read up about the aliases for tables you can set if you are using relations this would be handy.
Repository
You already saw that this is much less comfortable. This is because the find function or more specific the findOptions are using objects to build the where clause. This makes is harder to implement a proper interface to implement nested AND and OR clauses side by side. There for (I assume) they have chosen to split AND and OR clauses. This makes the interface much more declarative and means the you have to pull your OR clauses to the top:
const desiredEntity = await repository.find({
where: [{
id: id,
notAvailable: Not(IsNull()),
date: MoreThan(date)
},{
id: id,
notAvailable: Not(IsNull()),
date: date
myId: myId
}]
})
I cannot imagin looking a the size of the desired query that this code would be very performant.
Alternatively you could use the Raw find helper. This would require you to rewrite your clause per field, since you will only get access to the one alias at a time. You could guess the column names or aliases but this would be very poor practice and very unstable since you cannot directly control this easily.
if you want to nest andWhere statements if a condition is meet here is an example:
async getTasks(filterDto: GetTasksFilterDto, user: User): Promise<Task[]> {
const { status, search } = filterDto;
/* create a query using the query builder */
// task is what refer to the Task entity
const query = this.createQueryBuilder('task');
// only get the tasks that belong to the user
query.where('task.userId = :userId', { userId: user.id });
/* if status is defined then add a where clause to the query */
if (status) {
// :<variable-name> is a placeholder for the second object key value pair
query.andWhere('task.status = :status', { status });
}
/* if search is defined then add a where clause to the query */
if (search) {
query.andWhere(
/*
LIKE: find a similar match (doesn't have to be exact)
- https://www.w3schools.com/sql/sql_like.asp
Lower is a sql method
- https://www.w3schools.com/sql/func_sqlserver_lower.asp
* bug: search by pass where userId; fix: () whole addWhere statement
because andWhere stiches the where class together, add () to make andWhere with or and like into a single where statement
*/
'(LOWER(task.title) LIKE LOWER(:search) OR LOWER(task.description) LIKE LOWER(:search))',
// :search is like a param variable, and the search object is the key value pair. Both have to match
{ search: `%${search}%` },
);
}
/* execute the query
- getMany means that you are expecting an array of results
*/
let tasks;
try {
tasks = await query.getMany();
} catch (error) {
this.logger.error(
`Failed to get tasks for user "${
user.username
}", Filters: ${JSON.stringify(filterDto)}`,
error.stack,
);
throw new InternalServerErrorException();
}
return tasks;
}
I have a list of
{
date: specificCreatedAtDate,
userId: mysteryId
}
My solution is
.andWhere(
new Brackets((qb) => {
qb.where(
'userTable.date = :date0 AND userTable.type = :userId0',
{
date0: dates[0].date,
userId0: dates[0].type,
}
);
for (let i = 1; i < dates.length; i++) {
qb.orWhere(
`userTable.date = :date${i} AND userTable.userId = :userId${i}`,
{
[`date${i}`]: dates[i].date,
[`userId${i}`]: dates[i].userId,
}
);
}
})
)
That will produce something similar
const userEntity = await repository.find({
where: [{
userId: id0,
date: date0
},{
id: id1,
userId: date1
}
....
]
})

Making a select with an array

Hello I have and Array with objects, each object have atributes that I need for an select:
In this case it is the result from another consult with typeorm
" const CompaniesRelation: Array = await getRepository(CompanyRelation).find({ where:{ UserId: data.UserId, IsActive: true} });"
Companies: Array = [{CompanyId="a"}{CompanyId="b"}{CompanyId="c"}];
I need to make an select of all the data that matches with the Ids that are into Companies so for that I need to make an SQL like it:
const CompanyData: Array = SELECT *
FROM Company
INNER JOIN Company.CompanyId = CompaniesRelation[].CompanyId;
but it throw me error in typing, ¿how can I acces to each objetc into the array for make that match?
At the final I should traduce it sql to typeOrm, but I new and solving first in SQL it should help me to traduce to typeorm
Okay great, let us consider what we have to work with right:
So first we have a statement that gets a list of companies like so:
const CompaniesRelation: Array = await getRepository(CompanyRelation).find({
where: {
UserId: data.UserId,
IsActive: true
}
});
which ends up with something like this:
[ { CompanyId: 'a' }, { CompanyId: 'b' }, { CompanyId: 'c' } ]
Now we want to get a list of companies from an SQL DB with these Company IDs.
So the query should look like this:
// so first we re map the relation to an array of strings...
const ids: Array<string> = CompaniesRelation.map(c => c.CompanyId);
// then use it in the query, note the string interpolation for the query
const query: string = `SELECT * FROM Company WHERE CompanyId IN(${JSON.stringify(ids).slice(1, -1)});`;
I don't think this will cover the scope of the problem you have, I hope it helps though...feel free to ask

How do I convert a SQL query for Sequelize?

I have a SQL query (using mysql as DB) that I now need to rewrite as a sequelize.js query in node.js.
SQL Query
SELECT p.UserID, SUM(p.score), u.username
FROM Picks p
LEFT JOIN Users u
ON p.UserId = u.id
GROUP BY p.UserId;
not quite sure how this query needs to be structured to get the same results with sequelize.
This should do what you're needing:
db.Pick.findAll({
attributes: [
'UserID',
[db.sequelize.fn('SUM', db.sequelize.col('score')), 'score']
],
include: [{
model: db.User,
required: true,
attributes: ['username']
}],
group: ['UserID']
}).then((results) => {
...
})
Maybe try this (I assume you already associate Picks and Users), and you can access user.name by pick.user.username:
Picks.findAll({
attributes: ['UserID', [sequelize.fn('SUM', 'score'), 'sumScore']]
groupBy: ['UserID']
include: [
model: Users
]
});
The website at this domain no longer provides this tool. It's now filled with ads and likely malware.
I know this question is old but this answer may help others.
I have found an online converter that can convert raw SQL to Sequelize.
The link is https://pontaku-tools.com/english/
When converted from this site I got the following reponse.
Picks.hasMany(Users,{foreignKey: '', as: 'u'});
var _q = Picks;
_q.findAll({
include: [{model: Users, as: 'u', required: false,}, ],
attributes: [[sequelize.fn('SUM', sequelize.col('p.score')), 'p.score'],['p.UserID', 'p.UserID'],['u.username', 'u.username']],
group: [''],
});
Writing a sql query may not always be very simple with sequelize functions. Sometimes I recommend to run your plain sql query by combining it with this function.
const { QueryTypes } = require('sequelize');
async message_page (req,res) {
const messagePage = await db.query("SELECT * FROM ..", { type: QueryTypes.SELECT });
return messagePage;},