Sequelize move JavaScript logic to the DB? - node.js

An Organization can have multiple Grounds, and the number of grounds that are available can vary on daily basis.
The user should get a list of all the Grounds, irrespective of the fact whether they are available during the given/specified dates.
The thing which will be displayed to the user, is that the certain ground is not available in the given dates.
Denoted by rest["available"] in the Code.
So I am doing this work manually in javascript, can not I some how shift the javascript logic to the
Sequelize as well? so that it returns me the "available" status as well as "average" in the response.
Actually, I have large amount of data, and I believe looping in the JS code is not an efficient way of doing it.
Therefore, I believe the logic must be moved to the Data Base, but I am unsure about doing it using Sequelize.
The database Tables, their relationship & JS code are given below.
Organization.model.js
module.exports = function(sequelize, DataTypes) {
let Organization = sequelize.define('Organization', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
unique: true,
allowNull: false
},
name: {
type: DataTypes.STRING,
primaryKey: true,
}
}, {
timestamps: false,
freezeTableName: true,
tableName: "Organization",
underscored: false
});
Organization.associate = function(models) {
Organization.hasMany(models.Grounds, {
onDelete: 'cascade',
hooks: true,
foreignKey: 'OrganizationName',
sourceKey: 'name'
});
};
return Organization;
};
Grounds.model.js
module.exports = function(sequelize, DataTypes) {
let Grounds = sequelize.define('Grounds', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
OrganizationName: {
type: DataTypes.STRING,
references: {
model: 'Organization',
key: 'name'
}
},
NumbersAvailable: {
type: DataTypes.INTEGER
},
Date: DataTypes.DATEONLY
}, {
timestamps: false,
freezeTableName: true,
tableName: "Grounds",
underscored: false
});
Grounds.associate = function(models) {
Grounds.belongsTo(models.Organization, {
foreignKey: 'OrganizationName',
targetKey: 'name'
});
};
return Grounds;
};
JavaScript Logic:
//Get all the Grounds, in the specified Dates, e.g: '2018-05-01' & '2018-05-04'
let organizations = await Organization.findAll({
include: [
model: Grounds,
where: {
Date: {
$gte: '2018-05-01',
$lte: '2018-05-04'
}
}
]
});
//Calculate the Grounds Availability, in the specified Dates, e.g: '2018-05-01' & '2018-05-04'
let finalResult = organizations.map(function(currVal){
let organization = currVal.dataValues;
let {Grounds, ...rest} = organization;
rest["available"] = true; //Custom Key.
rest["average"] = 0; //Custom Key.
Grounds.forEach(function(ground){
let Date = ground.Date;
rest["average"] += ground.NumbersAvailable;
let number = ground.NumbersAvailable;
if(number == 0) rest["available"] = false;
});
rest["average"] = rest["average"]/Grounds.length;
});
Sample Table Data:
Organization TABLE:
id name
---------------------------
1 authority1
2 authority2
3 authority3
Grounds TABLE:
id NumbersAvailable OrganizationName Date GroundName
----------------------------------------------------------------------------
1 5 authority1 2018-05-01 someName
2 3 authority1 2018-05-02 someName
3 6 authority1 2018-05-03 someName
4 2 authority1 2018-05-04 someName
5 7 authority2 2018-05-01 someName
6 3 authority2 2018-05-02 someName
7 0 authority2 2018-05-03 someName
8 1 authority2 2018-05-04 someName
9 2 authority3 2018-05-01 someName
10 1 authority3 2018-05-02 someName
11 3 authority3 2018-05-03 someName
12 1 authority3 2018-05-04 someName

The way you'd move your logic back into the data layer is by using a view or database function (links to Postgres docs; other databases may use "procedure" instead of "function"). Since your specific case here involves running some simple calculations on all organizations and their grounds, a view on a query that joins those two tables with GROUP BY and some aggregation should suffice. You can treat the view as a read-only model as far as Sequelize is concerned; per this issue, the only catch is that you can't sync it.
If you're doing something more complex, you'll need a function, and this is where things start to get hairy. How you implement a function depends on the dialect of SQL you're using, since each database has its own linguistic quirks (SQL Server uses SELECT TOP n instead of SELECT .... LIMIT n, and so on). Here's where you run into the first potential issue -- you're now locked into that one database server, be it Postgres, SQL Server, or what have you. If you want to support another database, you'll need to port your function into its dialect.
The second problem with using a function is that, to my knowledge, Sequelize doesn't provide an easy way to execute database function which return tabular results -- the reasoning being that it, like other object-relational mappers, abstracts specificities such as dialect away and only provides an interface for things you can write the basic suite of SELECT, INSERT, etc statements against. Functions are therefore ignored. In order to execute one, you'll need to use its raw query functionality.

Related

Sequelize column reference is ambiguous

I'm trying to accomplish the task of joining all associated models, and then retrieving the count of all the models
I would want the data to be returned as
product {
main_dish: {},
side_dish: {},
drink: {},
purchase_product_count: x,
purchased_products: []
}
I have constructed this in sequelize to try to do this.
await Product.findAll({
include: [{
model: MainCourse,
required: false,
}, {
model: SideDish,
required: false,
}, {
model: Drink,
required: false,
}, {
model: ProductPurchase,
required: false,
where: {
createdAt: {[Op.between]: [two_days_before, current_date]}
},
}],
attributes: {
include: ['*',
[Sequelize.literal(
"(SELECT COUNT(*) from product_purchases as p where p.purchase_product_id = products.product_id)"), "product_purchase_count"],
]
},
offset: 1 * 5,
limit: 5
})
.then((results) => {
console.log(results)
res.status(200).json(results)
})
.catch((err) => {
console.log(err)
res.status(400).json(err)
})
Attempting to run this eager load gives this error
column reference "product_id" is ambiguous
This is the sql generated by sequelize
SELECT "products".*, "main_course"."main_course_id" AS "main_course.main_course_id", "main_course"."type" AS "main_course.type", "main_course"."createdAt" AS "main_course.createdAt", "main_course"."updatedAt" AS "main_course.updatedAt", "main_course"."deletedAt" AS "main_course.deletedAt", "main_course"."main_product_id" AS "main_course.main_product_id", "side_dish"."side_dish_id" AS "side_dish.side_dish_id", "side_dish"."type" AS "side_dish.type", "side_dish"."createdAt" AS "side_dish.createdAt", "side_dish"."updatedAt" AS "side_dish.updatedAt", "side_dish"."deletedAt" AS "side_dish.deletedAt", "side_dish"."side_product_id" AS "side_dish.side_product_id", "drink"."drink_id" AS "drink.drink_id", "drink"."type" AS "drink.type", "drink"."createdAt" AS "drink.createdAt", "drink"."updatedAt" AS "drink.updatedAt", "drink"."deletedAt" AS "drink.deletedAt", "drink"."drink_product_id" AS "drink.drink_product_id", "product_purchases"."product_purchase_id" AS "product_purchases.product_purchase_id", "product_purchases"."price" AS "product_purchases.price", "product_purchases"."quantity" AS "product_purchases.quantity", "product_purchases"."createdAt" AS "product_purchases.createdAt", "product_purchases"."updatedAt" AS "product_purchases.updatedAt", "product_purchases"."deletedAt" AS "product_purchases.deletedAt", "product_purchases"."purchase_product_id" AS "product_purchases.purchase_product_id", "product_purchases"."restaurant_order_id" AS "product_purchases.restaurant_order_id" FROM (SELECT "products"."product_id", "products"."title", "products"."price", "products"."createdAt", "products"."updatedAt", "products"."deletedAt", "products"."restaurant_id", "products".*, (SELECT COUNT(*) from product_purchases as p where p.purchase_product_id = products.product_id) AS "product_purchase_count" FROM "products" AS "products" WHERE ("products"."deletedAt" IS NULL) LIMIT 5 OFFSET 5) AS "products" LEFT OUTER JOIN "main_courses" AS "main_course" ON "products"."product_id" = "main_course"."main_product_id" AND ("main_course"."deletedAt" IS NULL) LEFT OUTER JOIN "side_dishes" AS "side_dish" ON "products"."product_id" = "side_dish"."side_product_id" AND ("side_dish"."deletedAt" IS NULL) LEFT OUTER JOIN "drinks" AS "drink" ON "products"."product_id" = "drink"."drink_product_id" AND ("drink"."deletedAt" IS NULL) LEFT OUTER JOIN "product_purchases" AS "product_purchases" ON "products"."product_id" = "product_purchases"."purchase_product_id" AND ("product_purchases"."deletedAt" IS NULL AND "product_purchases"."createdAt" BETWEEN '2022-05-06 08:35:12.577 +00:00' AND '2022-05-08 08:35:12.577 +00:00');
According to pgadmin the problem starts when doing the join on main_courses
ERROR: column reference "product_id" is ambiguous
LINE 1: ...EFT OUTER JOIN "main_courses" AS "main_course" ON "products"...
I seem to not understand how the sequelize join works as I am using the ORM method of not having any columns be the same. The foreign keys are all different from 'product_id'. Is product_id getting created and joined again on the aggregation possibly? Why am I getting this error, and how can I achieve the result of getting the count of the product_purchases along with the joined array of purchased products.
Obviously I could just get the purchased_products.length and then add it to the object itself, but I'm trying to figure out if you can do it through sequelize without having to hack it in.

postgresql with Node js Sequalize One-To-One allows multiple instances of model 1 to be related to the same instance of model 2

I am trying to create different model association using node js and sequalize, but I am having problems creating a one-to-one association.
The documentation says to use .hasOne() and .belongsTo() to create a One-To-One. From the sequalize documentation:
Foo.hasOne(Bar);
Bar.belongsTo(Foo);
results in the following sql statement:
CREATE TABLE IF NOT EXISTS "foos" (
/* ... */
);
CREATE TABLE IF NOT EXISTS "bars" (
/* ... */
"fooId" INTEGER REFERENCES "foos" ("id") ON DELETE SET NULL ON UPDATE CASCADE
/* ... */
);
Alright now isn't a One-To-One association should have a constraint to prevent multiple 'Bars' from referring to the same 'Foo'? because I can't see such a constraint in the resulting sql, and I have tried the code and yes I can have multiple 'Bars' pointing to one 'Foo' which makes it a One-To-Many isn't it?
Also from the documentation a One-To-Many association with the following code:
Team.hasMany(Player);
Player.belongsTo(Team);
results in the following sql statement:
CREATE TABLE IF NOT EXISTS "Teams" (
/* ... */
);
CREATE TABLE IF NOT EXISTS "Players" (
/* ... */
"TeamId" INTEGER REFERENCES "Teams" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
/* ... */
);
which is identical to the One-To-One resulting sql statement. Am I missing something. Someone help please.
I am expecting the 'fooId' column to have a unique restraint to prevent multiple "bars" from relating to a single "foo"
It is odd that Foo.hasOne(Bar) is not enough to create any constraints to stop multiple Bar objects from being associated with the same Foo object, at least in version 6 of sequelize. However, there is a difference in the instance methods on a Foo object where Foo.hasOne(Bar) versus Foo.hasMany(Bar) (see the docs for associations). Specifically, you'll have:
Foo.hasOne(Bar)
fooInstance.getBar()
fooInstance.setBar()
fooInstance.createBar()
versus
Foo.hasMany(Bar)
fooInstance.getBars()
fooInstance.countBars()
fooInstance.hasBar()
fooInstance.hasBars()
fooInstance.setBars()
fooInstance.addBar()
fooInstance.addBars()
fooInstance.removeBar()
fooInstance.removeBars()
fooInstance.createBar()
That being said, unique constraints can be added to the model definitions. For example,
let Parent = sequelize.define('parent', {
id: {
type: DataTypes.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
name: DataTypes.STRING
},
{
tableName: 'parents',
timestamps: false
})
let Child = sequelize.define('child', {
id: {
type: DataTypes.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
name: DataTypes.STRING,
parentId: {
type: DataTypes.INTEGER,
references: {
model: Parent,
key: 'id'
},
unique: true
}
},
{
tableName: 'children',
timestamps: false
})
Parent.hasOne(Child, {
foreignKey: 'parentId',
sourceKey: 'id'
})
Child.belongsTo(Parent, {
foreignKey: 'parentId',
targetKey: 'id'
})
The import part of the above snipped is the line that reads:
unique: true
This will result in:
CREATE TABLE IF NOT EXISTS "children" ("id" SERIAL , "name" VARCHAR(255), "parentId" INTEGER UNIQUE REFERENCES "parents" ("id") ON DELETE CASCADE ON UPDATE CASCADE, PRIMARY KEY ("id"));

Sequelize - How to pull records with 1 associated record and order by associated record attribute

I have a Convos table, which has many Messages.
What I want: pull all convos and last message. Order the convos by last_message.created_at
models.Convos.findAll({
include: [
{
model: models.Messages,
as: "last_message",
order: [ [ 'created_at', 'DESC' ]],
limit: 1,
}
],
where:{
[Op.or]: [
{
sender_id: req.decoded.id
},
{
recipient_id: req.decoded.id
}
],
},
)}
The closest I've gotten to ordering is with :
order: [
[{model: models.Messages, as: 'last_message'}, 'created_at', 'DESC'],
],
But this gives the error:
`Unhandled rejection SequelizeDatabaseError: missing FROM-clause entry for table "last_message"`
From here, I am guessing this error may allude that there are convos without any messages, making last_message.created_at undefined (I could be completely misunderstanding this error though).
So, from there, I have been trying to add a clause to the where statement that only pulls convos that have at least 1 message. Here are a bunch of things I've tried, and they all throw an error:
adding to where:
Sequelize.literal("`last_message`.`id` IS NOT NULL")
'$models.Messages.id$': { [Op.ne]: null },
Sequelize.fn("COUNT", Sequelize.col("last_message")): { [Op.gt]: 0 }
'$last_message.id$': { [Op.ne]: null }
'$last_message.id$': {
[Op.ne]: null
}
I've also tried having instead of a where statement:
having: Sequelize.where(Sequelize.fn('COUNT', Sequelize.col('$last_message.id$')), '>=', 0)
How can I properly sort the convos by it's associated record, last_message.created_at?
UPDATE - RELEVANT PARTS OF CONVOS MODEL
"use strict";
module.exports = (sequelize, DataTypes) => {
let Convos = sequelize.define(
"Convos",
{
sender_id: {
type: DataTypes.INTEGER,
references: {
model: "Users",
key: "id"
}
},
recipient_id: {
type: DataTypes.INTEGER,
references: {
model: "Users",
key: "id"
}
},
created_at: DataTypes.DATE,
updated_at: DataTypes.DATE
},
{
timestamps: false,
freezeTableName: true,
schema: "public",
tableName: "convos"
}
);
Convos.associate = models => {
Convos.hasMany(models.Messages, {
as: "last_message",
foreignKey: "convo_id",
sourceKey: "id"
});
};
return Convos;
};
UPDATE
I've figured out the issue is using Sequelize.literal when the associated model has limit. For example, this works:
models.Convos.findAll({
include: [
{
model: models.Messages,
as: "last_message",
order: [ [ 'created_at', 'DESC' ]],
//limit: 1,
required: true,
duplicating: false,
},
],
where: {
[Op.or]: [
{
sender_id: req.decoded.id
},
{
recipient_id: req.decoded.id
}
],
},
order: [[Sequelize.literal(`last_message.created_at`), 'DESC']],
offset: offset,
limit: 10,
}).then(convos => { ....
But when I uncomment the limit: 1 in the include part, I get the error:
Unhandled rejection SequelizeDatabaseError: missing FROM-clause entry for table "last_message"
Here is the query logs without limit 1:
Executing (default): SELECT "Convos"."id", "Convos"."sender_id", "Convos"."recipient_id", "Convos"."created_at", "Convos"."updated_at", "last_message"."id" AS "last_message.id", "last_message"."body" AS "last_message.body", "last_message"."read" AS "last_message.read", "last_message"."group_meeting_id" AS "last_message.group_meeting_id", "last_message"."user_id" AS "last_message.user_id", "last_message"."created_at" AS "last_message.created_at", "last_message"."updated_at" AS "last_message.updated_at", "last_message"."convo_id" AS "last_message.convo_id", "last_message->user"."id" AS "last_message.user.id", "last_message->user"."first_name" AS "last_message.user.first_name", "last_message->user"."avatar_file_name" AS "last_message.user.avatar_file_name", "senderUser"."id" AS "senderUser.id", "senderUser"."first_name" AS "senderUser.first_name", "senderUser"."avatar_file_name" AS "senderUser.avatar_file_name", "recipientUser"."id" AS "recipientUser.id", "recipientUser"."first_name" AS "recipientUser.first_name", "recipientUser"."avatar_file_name" AS "recipientUser.avatar_file_name" FROM "public"."convos" AS "Convos" INNER JOIN "public"."msgs" AS "last_message" ON "Convos"."id" = "last_message"."convo_id" LEFT OUTER JOIN "public"."users" AS "last_message->user" ON "last_message"."user_id" = "last_message->user"."id" LEFT OUTER JOIN "public"."users" AS "senderUser" ON "Convos"."sender_id" = "senderUser"."id" LEFT OUTER JOIN "public"."users" AS "recipientUser" ON "Convos"."recipient_id" = "recipientUser"."id" WHERE ("Convos"."sender_id" = 32 OR "Convos"."recipient_id" = 32) ORDER BY last_message.created_at DESC LIMIT 10 OFFSET 70;
Query with limit: 1:
Executing (default): SELECT "Convos"."id", "Convos"."sender_id", "Convos"."recipient_id", "Convos"."created_at", "Convos"."updated_at", "senderUser"."id" AS "senderUser.id", "senderUser"."first_name" AS "senderUser.first_name", "senderUser"."avatar_file_name" AS "senderUser.avatar_file_name", "recipientUser"."id" AS "recipientUser.id", "recipientUser"."first_name" AS "recipientUser.first_name", "recipientUser"."avatar_file_name" AS "recipientUser.avatar_file_name" FROM "public"."convos" AS "Convos" LEFT OUTER JOIN "public"."users" AS "senderUser" ON "Convos"."sender_id" = "senderUser"."id" LEFT OUTER JOIN "public"."users" AS "recipientUser" ON "Convos"."recipient_id" = "recipientUser"."id" WHERE ("Convos"."sender_id" = 32 OR "Convos"."recipient_id" = 32) ORDER BY last_message.created_at DESC LIMIT 10 OFFSET 0;
Here are some links that were useful in understanding limit is causing the issue, but I have still not found a solution that solves this problem.
Link 1
Link 2
Link 3
Link 4
Thanks!
Disclaimer: I do not know sequelize. And - there are three documented versions and you have not stated which one you are using.
What I want: pull all convos and last message. Order the convos by
last_message.created_at
I can offer a SQL (Postgres) solution.
I assume a convos table (something like):
CREATE TABLE convos (
id INT PRIMARY KEY,
sender_id INT,
recipient_id INT
);
and a messages table
CREATE TABLE messages (
id INT PRIMARY KEY,
convo_id INT REFERENCES convos (id),
created_at timestamp without time zone
);
And some test data:
INSERT INTO
convos (id, sender_id, recipient_id)
VALUES (1,1,1), (2,2,2), (3,3,4);
INSERT INTO
messages (id, convo_id, created_at)
VALUES
(1,1, '1990-07-24'),
(2,1, '2019-07-24'),
(3,2, '1990-07-24'),
(4,2, '2019-07-24'),
(5,3, null);
When you want to query the convos table and fetch the latest message from messages table you will have to use the result from messages in a subquery (or a CTE, or lateral join, ...).
For example:
SELECT
convos.sender_id,
convos.recipient_id,
messages.last_message_date
FROM convos
LEFT JOIN
(
SELECT convo_id, max(created_at) as last_message_date
FROM messages
GROUP BY convo_id
) messages ON convos.id=messages.convo_id
ORDER BY messages.last_message_date DESC
So for sequelize.js - you have to find out how it does subqueries with associated models and use the result.

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.

How to configure hasMany relationship with many-to-many table

I have the following setup:
Organization (1)----(*) OrganizationArticleItemMap (*)----(1) ArticleItem
(1)
|
|
|
(*)
ArticleItemPriceRule
An article item can thus belong to many organizations and an organization can have many article items. For every article item in an organization there will be multiple price rules.
The many to many relationship has been configured as such:
this.models.ArticleItem.belongsToMany(this.models.Organization, {
through: this.models.OrganizationArticleItemMap,
foreignKey: 'ArticleItemId'
});
this.models.Organization.belongsToMany(this.models.ArticleItem, {
through: this.models.OrganizationArticleItemMap,
foreignKey: 'OrganizationId'
});
I do not really know how I should configure ArticleItemPriceRule so that I can fetch the Price rules for all articles for a given organization.
I have tried the following :
this.models.ArticleItem.hasMany(this.models.OrganizationArticleItemMap, {
foreignKey: 'ArticleItemId',
as: 'OrganizationMaps'
});
this.models.OrganizationArticleItemMap.hasMany(this.models.ArticleItemPriceRule, {
as: 'Prices',
foreignKey: 'Organizations_articleitem_map_Id'
});
and then the following query:
DataAccess.dataContext.models.ArticleItem.findAll({
include: [
{
model: DataAccess.dataContext.models.Organization,
where: {OrganizationId: '1'},
attributes: [],
through: {
attributes: []
}
},
{
model: DataAccess.dataContext.models.OrganizationArticleItemMap,
as: 'OrganizationMaps',
required: true,
include: [{
model: DataAccess.dataContext.models.ArticleItemPriceRule,
as: 'Prices',
required: false
}]
}
],
where: {ArticleItemId: '1'}
})
The problem with this is that the sql query that was generated included an inner join with OrganizationArticleItemMap twice.
SELECT `ArticleItem`.`ArticleItemId`,
`ArticleItem`.`ArticleCategoryId`,
`ArticleItem`.`VisibleOnOnlineBooking`,
`OrganizationMaps`.`Organization_articleitem_map_Id` AS `OrganizationMaps.OrganizationArticleItemMapId`,
`OrganizationMaps`.`OrganizationId` AS `OrganizationMaps.OrganizationId`,
`OrganizationMaps`.`ArticleItemId` AS `OrganizationMaps.ArticleItemId`,
`OrganizationMaps.Prices`.`ArticleItemPriceRuleId` AS `OrganizationMaps.Prices.ArticleItemPriceRuleId`,
`OrganizationMaps.Prices`.`Organizations_articleitem_map_Id` AS `OrganizationMaps.Prices.Organizations_articleitem_map_Id`,
`OrganizationMaps.Prices`.`CurrencyId` AS `OrganizationMaps.Prices.CurrencyId`,
`OrganizationMaps.Prices`.`Price` AS `OrganizationMaps.Prices.Price`,
`OrganizationMaps.Prices`.`ValidFrom` AS `OrganizationMaps.Prices.ValidFrom`
FROM `articleitem` AS `ArticleItem`
INNER JOIN (`organization_articleitem_map` AS `Organizations.OrganizationArticleItemMap`
INNER JOIN `organizations` AS `Organizations` ON `Organizations`.`OrganizationId` = `Organizations.OrganizationArticleItemMap`.`OrganizationId`) ON `ArticleItem`.`ArticleItemId` = `Organizations.OrganizationArticleItemMap`.`ArticleItemId`
AND `Organizations`.`OrganizationId` = '1'
AND `Organizations`.`IsDeleted` = 0
INNER JOIN `organization_articleitem_map` AS `OrganizationMaps` ON `ArticleItem`.`ArticleItemId` = `OrganizationMaps`.`ArticleItemId`
LEFT OUTER JOIN `articleitempricerule` AS `OrganizationMaps.Prices` ON `OrganizationMaps`.`Organization_articleitem_map_Id` = `OrganizationMaps.Prices`.`Organizations_articleitem_map_Id`
AND `OrganizationMaps.Prices`.`IsDeleted` = 0
WHERE `ArticleItem`.`ArticleItemId` = '1'
AND `ArticleItem`.`IsDeleted` = 0;
Well, this is the response I got from the sequelize team on github:
Sequelize doesn't know that those two relations uses the same tables.
You look to be doing it correctly, we just aren't smart enough to
infer that we could use the join table just a single time.
https://github.com/sequelize/sequelize/issues/5376

Resources