Sequelize query in query - node.js

I have 2 models Shop and Beacon. I have to return sum beacons assigned to each shop. I know how to do it in SQL:
SELECT Shops.*, (SELECT COUNT(id) FROM Beacons WHERE shopId = Shops.id) FROM Shops
But i have a problem with Sequelize, i try something like below:
models.Shop.findAll({
attributes: ['id', sqz.fn('count', sqz.col('shopId'))],
include: [
{
model: models.Beacon
}
]
});

This solution work, but return query:
SELECT Shop.*, count(shopId) FROM Shops LEFT OUTER JOIN Beacons ON Shop.id = Beacons.shopId

Related

How sequelize generate T-SQL query with sum, order and group?

I have this sequelize query, it gives a total taxable by article categories group.
const result = await models.Sale.findAll({
raw: true,
attributes: [
[Sequelize.literal('SUM(Sales.totalTaxable)'), 'total'],
"Article.category"
],
limit: 25,
offset: 0,
where: Sequelize.where(Sequelize.fn('YEAR', Sequelize.col('docDate')), year)
include: [{ model: models.Article, attributes: [] }],
group: ["Article.category"],
order: [[models.Article, "category", "DESC"], [Sequelize.literal("total"), "DESC"],],
})
and the output is that query
SELECT
SUM(Sales.totalTaxable) AS [total], [Article].[category]
FROM
[Sales] AS [Sales]
LEFT OUTER JOIN
[Articles] AS [Article] ON [Sales].[ArticleId] = [Article].[id]
AND ([Article].[deletedAt] IS NULL)
WHERE
(YEAR([docDate]) = N'2021'
GROUP BY
[Article].[category]
ORDER BY
[Sales].[id]
OFFSET 0 ROWS FETCH NEXT 25 ROWS ONLY;
It does not work because of ORDER BY [Sales].[id] but it's automatically added by sequelize and it's clearly unwanted.
(manually testing without it works)
I'm using sequelize v6.6.5, dialect: mssql (tedious)
Is this a sequelize bug or am I doing something wrong?

TypeORM second JOIN statement does not provide results

I have a database with products, manufacturers and collections. The products and manufacturers are connected with a n:m relation. In a collection users can store products, so the products and collections are also connected with a n:m relation.
Now I have to load the products with their manufacturer depending on the collectionId.
Currently I have the following snippet:
CollectionProducts.createQueryBuilder("cp")
.innerJoinAndSelect("cp.product", "product")
.leftJoinAndSelect(
Manufacturer,
"manufacturer",
"product.manufacturerId = manufacturer.id"
)
.where("cp.collectionId IN(:...ids)", { ids: collectionIds })
.getMany();
The products are loaded successfully, but the manufacturers are not loaded.
Here is the printed out result:
[
CollectionProducts {
collectionId: 1,
productId: 1,
__product__: Product {
id: 1,
name: 'Tea',
productImage: '',
description: 'Peach',
releaseDate: 2021-10-19T18:13:42.938Z,
status: null,
origin: 'Germany',
manufacturerId: 1
}
}
]
Does somebody know what I am doing wrong there?
I've got the solution for my specific problem. Instead of using getMany() I had to use getRawMany(). The generated SQL itself was correct, with getRawMany() I got all the results needed.

How to filter through 3 table joins using sequelize in expressjs api

I have 3 tables named:
//restaurants
columns ( id, name, restaurant_type_id(FK)
//restaurant_branches
columns ( id, name, restaurant_id(FK)
//restaurant_types
columns ( id, restaurant_type_name('italian', 'french'...etc))
I would like to filter restaurant_branches by restaurant_type_id using query params in my restaurant_branches.findAll(); action in the controller as the following.
const findAll = async (req, res) => {
let RestaurantTypeId= req.query.restaurantType ? parseInt(req.query.restaurantType): null ;
var type = RestaurantTypeId ? {where:{ restaurantTypeId: RestaurantTypeId }} : null ;
console.log(RestaurantTypeId);
await RestaurantBranch.findAll({
order: [
['id', 'ASC']
],
include:
[
{
model: Restaurant,
type,
include: [{
model: RestaurantType,
}
]
}
]
}).then((restaurantBranches) => {
return res.status(200).send({
message: "restaurant branches returned",
data: restaurantBranches
})
})
.catch((error) => {res.status(500).send(error.message);});
}
module.exports = {
findAll
}
//Sequelize Associations
db.RestaurantType.hasMany(db.Restaurant);
db.Restaurant.belongsTo(db.RestaurantType);
// Restaurant / Restaurant Branches
db.RestaurantBranch.belongsTo(db.Restaurant);
db.Restaurant.hasMany(db.RestaurantBranch);
Sequelize log:
Executing (default): SELECT "restaurant_branches"."id", "restaurant_branches"."name", "restaurant_branches"."description", "restaurant_branches"."email", "restaurant_branches"."phoneNumber", "restaurant_branches"."address", "restaurant_branches"."country_code", "restaurant_branches"."image", "restaurant_branches"."latitude", "restaurant_branches"."longitude", "restaurant_branches"."workingHours", "restaurant_branches"."workingDays", "restaurant_branches"."offDays", "restaurant_branches"."locationAddress", "restaurant_branches"."locationCity", "restaurant_branches"."status", "restaurant_branches"."hasParking", "restaurant_branches"."instruction", "restaurant_branches"."isActive", "restaurant_branches"."isDeleted", "restaurant_branches"."createdAt", "restaurant_branches"."updatedAt", "restaurant_branches"."restaurantId", "restaurant_branches"."cityId", "restaurant_branches"."districtId", "city"."id" AS "city.id", "city"."name" AS "city.name", "city"."code" AS "city.code", "city"."status" AS "city.status", "city"."isDeleted" AS "city.isDeleted", "city"."createdAt" AS "city.createdAt", "city"."updatedAt" AS "city.updatedAt", "city"."countryId" AS "city.countryId", "district"."id" AS "district.id", "district"."name" AS "district.name", "district"."isDeleted" AS "district.isDeleted", "district"."createdAt" AS "district.createdAt", "district"."updatedAt" AS "district.updatedAt", "district"."cityId" AS "district.cityId", "restaurant"."id" AS "restaurant.id", "restaurant"."name" AS "restaurant.name", "restaurant"."aboutUs" AS "restaurant.aboutUs", "restaurant"."phoneNumber" AS "restaurant.phoneNumber", "restaurant"."address" AS "restaurant.address", "restaurant"."latitude" AS "restaurant.latitude", "restaurant"."longitude" AS "restaurant.longitude", "restaurant"."image" AS "restaurant.image", "restaurant"."countryCode" AS "restaurant.countryCode", "restaurant"."restaurantRegisterDocument" AS "restaurant.restaurantRegisterDocument", "restaurant"."isDeleted" AS "restaurant.isDeleted", "restaurant"."createdAt" AS "restaurant.createdAt", "restaurant"."updatedAt" AS "restaurant.updatedAt", "restaurant"."restaurantTypeId" AS "restaurant.restaurantTypeId", "restaurant"."categoryId" AS "restaurant.categoryId", "restaurant"."userId" AS "restaurant.userId", "restaurant->restaurant_type"."id" AS "restaurant.restaurant_type.id", "restaurant->restaurant_type"."name" AS "restaurant.restaurant_type.name", "restaurant->restaurant_type"."photo" AS "restaurant.restaurant_type.photo", "restaurant->restaurant_type"."createdAt" AS "restaurant.restaurant_type.createdAt", "restaurant->restaurant_type"."updatedAt" AS "restaurant.restaurant_type.updatedAt" FROM "restaurant_branches" AS "restaurant_branches" LEFT OUTER JOIN "cities" AS "city" ON "restaurant_branches"."cityId" = "city"."id" LEFT OUTER JOIN "districts" AS "district" ON "restaurant_branches"."districtId" = "district"."id" LEFT OUTER JOIN "restaurants" AS "restaurant" ON "restaurant_branches"."restaurantId" = "restaurant"."id" LEFT OUTER JOIN "restaurant_types" AS "restaurant->restaurant_type" ON "restaurant"."restaurantTypeId" = "restaurant->restaurant_type"."id" ORDER BY "restaurant_branches"."id" ASC;
so far I'm doing this, and I get All the restaurant branches if I GET request to this URL:
{{URL}}/restaurant_branches?restaurantType=2
what I'd like to be getting instead is all the restaurant branches whom their restaurants belong to the restaurant type with id 2
Any help or guidance is highly appreciated.
You included where condition in include option as a type prop that's why it does not work as expected. You just need to indicate where either with your condition or as an empty object:
var where = RestaurantTypeId ? { restaurantTypeId: RestaurantTypeId } : {} ;
include:
[
{
model: Restaurant,
where,
include: [{
model: RestaurantType,
}
]
}
]

aggregate function in sequelize

i need a query which has aggregate function in include. i want to get count of ticket sell in each events
i tried code below
const data = await Items.paginate({
page: offset.value,
paginate: limit.value,
where: itemType,
include:[
{model:ItemTypes},
{model:ItemPayment,
attributes: [[db.sequelize.fn('sum', db.sequelize.col('ItemPayments.numberOfTickets')), 'total']],
group: ["ItemPayments.itemId"],
}],
raw:true
});
but it gives me an error like below
In aggregated query without GROUP BY, expression #1 of SELECT list contains nonaggregated column 'Items.id'; this is incompatible with sql_mode=only_full_group_by
Add separate : true and try to execute the query again
{
model:ItemPayment,
attributes: [[db.sequelize.fn('sum', db.sequelize.col('ItemPayments.numberOfTickets')), 'total']],
group: ["ItemPayments.itemId"],
separate : true // <--------- HERE
}

Joining same table multiple times with Sequelize

I have the following models:
const User = Sequelize.define('user', {
login: Sequelize.DataTypes.STRING,
password: Sequelize.DataTypes.STRING,
is_manager: Sequelize.DataTypes.BOOLEAN,
notes: Sequelize.DataTypes.STRING
});
const Bike = Sequelize.define('bike', {
model: Sequelize.DataTypes.STRING,
photo: Sequelize.DataTypes.BLOB,
color: Sequelize.DataTypes.STRING,
weight: Sequelize.DataTypes.FLOAT,
location: Sequelize.DataTypes.STRING,
is_available: Sequelize.DataTypes.BOOLEAN
});
const Rate = Sequelize.define('rate', {
rate: Sequelize.DataTypes.INTEGER
});
Rate.belongsTo(User);
User.hasMany(Rate);
Rate.belongsTo(Bike);
Bike.hasMany(Rate);
And I'd like to select bikes with their average rates, plus rates of the current user for each bike:
Bike.findAll({
attributes: {include: [[Sequelize.fn('AVG', Sequelize.col('rates.rate')), 'rate_avg']],
},
include: [{
model: Rate,
attributes: []
}, {
model: Rate,
attributes: ['rate'],
include: [{
model: User,
attributes: [],
where: {
login: req.user.login
}
}]
}],
group: Object.keys(Bike.rawAttributes).map(key => 'bike.' + key) // group by all fields of Bike model
})
It constructs the following query: SELECT [bike].[id], [bike].[model], [bike].[photo], [bike].[color], [bike].[weight], [bike].[location], [bike].[is_available], AVG([rates].[rate]) AS [rate_avg], [rates].[id] AS [rates.id], [rates].[rate] AS [rates.rate] FROM [bikes] AS [bike] LEFT OUTER JOIN [rates] AS [rates] ON [bike].[id] = [rates].[bikeId] LEFT OUTER JOIN ( [rates] AS [rates] INNER JOIN [users] AS [rates->user] ON [rates].[userId] = [rates->user].[id] AND [rates->user].[login] = N'user' ) ON [bike].[id] = [rates].[bikeId] GROUP BY [bike].[id], [bike].[model], [bike].[photo], [bike].[color], [bike].[weight], [bike].[location], [bike].[is_available];
And fails: SequelizeDatabaseError: The correlation name 'rates' is specified multiple times in a FROM clause.
How do I write the query right? I need Sequelize to assign another alias to the rates table used in the 2nd join (and add its columns to the GROUP BY clause, but that's the next step).
You can do multiple inner joins with same table by adding extra same association with that model but with a different alias that is as: 'alias1' , as: 'alias2' ,... - all this existing with the same model + same type of association.
Also posted this solution at github issue: https://github.com/sequelize/sequelize/issues/7754#issuecomment-783404779
E.g. for Chats that have many Receiver
Associations (Duplicating for as many needed)
Chat.hasMany(Receiver, {
// foreignKey: ...
as: 'chatReceiver',
});
Chat.hasMany(Receiver, {
// foreignKey: ...
as: 'chatReceiver2',
});
Now you are left to include associated model multiple times all with different alias so it does not gets overridden.
So you can use them in query as below:
Chat.findAll({
attributes: ["id"],
include: [{
required: true,
model: Receiver,
as: 'chatReceiver', // Alias 1
attributes: [],
where: { userID: 1 }, // condition 1
}, {
required: true,
model: Receiver,
as: 'chatReceiver2', // Alias 2
attributes: [],
where: { userID: 2 }, // condition 2 as needed
}]
});
Solution :
Bike.findAll({
attributes: {include: [[Sequelize.fn('AVG', Sequelize.col('rates.rate')), 'rate_avg']],
},
include: [{
model: Rate,
attributes: []
}, {
model: Rate,
required : false , // 1. just to make sure not making inner join
separate : true , // 2. will run query separately , so your issue will be solved of multiple times
attributes: ['rate'],
include: [{
model: User,
attributes: [],
where: {
login: req.user.login
}
}]
group : [] // 3. <------- This needs to be managed , so please check errors and add fields as per error
}],
group: Object.keys(Bike.rawAttributes).map(key => 'bike.' + key) // group by all fields of Bike model
})
NOTE : READ THE COMMENTS
Sequelize doesn't support including through the same association twice (see here, here, and here). At the model level, you can define 2 different associations between Bike and Rate, but having to change the model, adding new foreign keys etc, is a very hacky solution.
Incidentally, it wouldn't solve your other problem, which is that you're grouping only by Bike but then want to select the user's rate. To fix that, you'd also have to change your grouping to include the user rates. (Note that if a user has more than 1 rate per bike, that might also create some inefficiency, as the rates for the bike are averaged repeatedly for each of the user's rates.)
A proper solution would be using window functions, first averaging the rates per bike and then filtering out all the rates not belonging to the logged in user. Might look something like this:
SELECT *
FROM (
SELECT bike.*,
users.login AS user_login,
AVG (rates.rate) OVER (PARTITION BY bike.id) AS rate_avg
FROM bike
INNER JOIN rates ON rates.bikeId = bike.id
INNER JOIN users ON rates.userId = users.id
)
WHERE user_login = :req_user_login
Unfortunately, as far as I'm aware sequelize doesn't currently support subqueries in the FROM clause and using window functions in this way, so you'd have to fall back to a raw query.

Resources