Sequelize - The multi-part identifier XXX could not be bound - javascript

I'm trying to run a query with an order option in Sequelize with SQL Server. I've read some examples in SO but I haven't found a solution while running the queries on Sequelize.
let order = [["winner","new_apv","asc"]];
const include = [
{
model: Item_Supplier,
as: "itemSupplier",
attributes: ["id", "supplierOrderId", "cost"],
include: [
{
model: Supplier,
as: "supplier"
}
]
},
{
model: Winner,
as: "winner",
attributes: ["supplierId", "new_apv"],
include: [
{
model: Supplier,
as: "supplier",
attributes: ["supplierName"]
}
]
}
];
await Item.findAll({include,order})
This is the error message
The multi-part identifier "winner.new_apv" could not be bound.
Here is the SQL Server query Sequelize generates:
SELECT [item].*, [itemSupplier].[id] AS
[itemSupplier.id], [itemSupplier].[supplierOrderId] AS
[itemSupplier.supplierOrderId], [itemSupplier].[cost] AS
[itemSupplier.cost], [itemSupplier->supplier].[id] AS
[itemSupplier.supplier.id], [itemSupplier->supplier].[supplierName] AS
[itemSupplier.supplier.supplierName], [itemSupplier->supplier].[duns] AS
[itemSupplier.supplier.duns]
FROM (SELECT [item].[id], [item].[item_price], [item].[common_code], [item].[uom], [item].[usage_per_item], [item].[apv],
[item].[impac_commodity], [item].[mfgname], [item].[mtr_grp_desc], [item].[description], [item].[comments], [item].[renewed_2019],
[item].[currency], [item].[contractId], [item].[mtrId], [item].[allocationId], [winner].[id] AS [winner.id],
[winner].[supplierId]
AS [winner.supplierId], [winner].[new_apv]
AS [winner.new_apv], [winner->supplier].[id]
AS [winner.supplier.id], [winner->supplier].[supplierName]
AS [winner.supplier.supplierName] FROM [items] AS [item]
INNER JOIN [winners] AS [winner]
ON [item].[id] = [winner].[itemId]
AND [winner].[deletedAt] IS NULL
INNER JOIN [suppliers] AS [winner->supplier]
ON [winner].[supplierId] = [winner->supplier].[id]
WHERE ([item].[deletedAt] IS NULL AND ([item].[contractId] = 4 AND [item].[renewed_2019] LIKE N'YES%'))
ORDER BY [item].[id] OFFSET 0 ROWS FETCH NEXT 5 ROWS ONLY) AS [item] LEFT OUTER JOIN [item_suppliers] AS [itemSupplier]
ON [item].[id] = [itemSupplier].[itemId] AND ([itemSupplier].[deletedAt] IS NULL) LEFT OUTER JOIN [suppliers]
AS [itemSupplier->supplier] ON [itemSupplier].[supplierId] = [itemSupplier->supplier].[id] ORDER BY [winner].[new_apv] ASC;

Using Sequelize literal and adding the single quotes in the literal itself did the trick.
order = [[Sequelize.literal('"winner.new_apv"'), desc ? "DESC" : "ASC"]];

Related

Sequelize select from select

I want join and filter raw query
const projects = await sequelize.query('SELECT * FROM projects + SQL MAGIC', {
model: Projects,
mapToModel: true,
type: QueryTypes.SELECT,
});
In this query replace projects table with select+magic:
const dinamic_where = {id: 1}
const projects = await Projects.findAll(
where: { ..dinamic_where },
include: [{ model: Organization }],
)
So generated query shall become
SELECT fields,... FROM (SELECT * FROM projects + SQL MAGIC) JOIN organization WHERE organization.id = 1;
bind not suitable because of dinamic_where can contan different number of fields.
If you need to modify FROM part, I think you need to use a little more low level access to Sequelize.
There is a function queryGenerator.selectQuery however this takes string as FROM table name meaning if I do
selectQuery('(...SQL MAGIC)', options, Projects)
This will generate a query string as
SELECT ... FROM '(...SQL MAGIC)' ...
FROM query is taken as a string value which is not a valid SQL.
So, a little hacky workaround.
const customQuery = selectQuery('FROM_TO_BE_REPLACED', options, Projects)
// Use JS string replace to add dynamic SQL for FROM.
// If it is Postgres, double quotes are added by queryGenerator.
// If MySQL, it would be ``
customQuery.replace('"FROM_TO_BE_REPLACED"', '(...SQL MAGIC)')
All in action.
const Model = require("sequelize/lib/model");
const parentOptions = {
where: {
id: 1,
key: 'value'
},
include: [Organization]
};
// This is required when the inline query has `include` options, this 1 line make sure to serialize the query correctly.
Model._validateIncludedElements.bind(Projects)(parentOptions);
const customQuery = sequelize.getQueryInterface()
.queryGenerator
.selectQuery('FROM_TO_BE_REPLACED', parentOptions, Projects);
const fromQuery = '(SELECT * FROM SQL MAGIC)';
const projects = await sequelize.query(customQuery.replace('"FROM_TO_BE_REPLACED"', fromQuery),
{
type: QueryTypes.SELECT
}
);

Sequelize [Op.and] not working with M:N association

I have two models
Units and Filters which are related with M:N association through unit_filter table
this.belongsToMany(Unit, {
through: "unit_filter",
as: "units",
});
this.belongsToMany(Filter, {
through: 'unit_filter',
as: 'filters',
});
The goal is to fetch units which have more than 1 filter associated with and condition.
let result = await Unit.findAll({
include: [
{
model: Filter,
where: {
id: {
[Op.and] : [2,252,4,80]
}
},
as: 'filters',
},
],
});
The above is only working if there is only one id in the array which does not make any sense.
Seqeulize documenation states
Post.findAll({
where: {
[Op.and]: [{ a: 5 }, { b: 6 }], // (a = 5) AND (b = 6)
}
})
So the code I have should work theoritically. I also tried with
where: {
[Op.and] : [{id:2},{id:252},{id:4},{id:80}]
}
which results in getting all the items from the db. It does not even care about the where condition in this case.
Would be of great help if any one points me in right direction.
You need to use Sequelize.literal with a subquery in where option in order to filter units that have more than 1 filter because simply indicating several ids of filters you will get units that have one of indicated filters (from 1 to N).
let result = await Unit.findAll({
where: Sequelize.where(
Sequelize.literal('(SELECT COUNT(*) FROM unit_filter as uf WHERE uf.unit_id=unit.id AND uf.filter_id in ($filter_ids))'),
Op.gt,
'1'),
bind: {
filter_ids: [2,252,4,80]
}
});

Order a Sequelize query by a join table using model instance getters

I would like to order my results from a join table property using Sequelize. Most of the answers I've seen on stack overflow aren't explaining how to order by a join table when using the model's getter methods. And the way I'm trying to use it is returning inconsistent results...sometimes it returns the right order but other times it doesn't. This has me stumped.
I have an app that has Playlists and Tracks. A user can add Tracks to a Playlist in a specific order.
Given the following models:
Playlist
const Playlist = sequelize.define('Playlist', {
title: DataTypes.STRING,
}, {});
Playlist.associate = function(models) {
Playlist.belongsToMany(models.Track, { through: models.PlaylistTrack, foreignKey: "playlistID", otherKey: "trackID" });
};
Track
const Track = sequelize.define('Track', {
title: DataTypes.STRING,
}, {});
Track.associate = function(models) {
Track.belongsToMany(models.Playlist, { through: models.PlaylistTrack, foreignKey: "trackID", otherKey: "playlistID" });
};
This is a many-to-many relationship, so I've created a join table (make sure to note the order property):
PlaylistTrack
const PlaylistTrack = sequelize.define('PlaylistTrack', {
playlistID: DataTypes.INTEGER,
trackID: DataTypes.INTEGER,
order: DataTypes.INTEGER <-- important property
}, {});
PlaylistTrack.associate = function(models) {
};
In my api, I have a method getTracksByPlaylist() where I query for the playlist instance and then use the getTracks() getter to return all the tracks for that playlist:
async getTracksByPlaylist(playlistId) {
// get instance of Playlist
const playlist = await Playlist.findOne({
where: { id: playlistId }
});
// use the available getter (getTracks) on the Playlist instance
var response = await playlist.getTracks({
include: [{ model: Playlist }],
order: [[ Playlist, PlaylistTrack, "order", "ASC" ]]
});
}
In my database join table, I set the order property. Therefore, in this example, I would expect to observe results that show tracks in the order of 0,1,2,3,4...etc. However, every once in a while, it will display the results not in ASCENDING order. Have I hit a race condition of sorts? What is the best way to return results using Sequelize getters and ordering through a join table?
If you're curious, here are the queries printed from the command line:
This one uses the method described above and produces inconsistent results
SELECT `Track`.`id`,`Track`.`title`, `Track`.`createdAt`, `Track`.`updatedAt`, `Playlists`.`id` AS `Playlists.id`, `Playlists`.`title` AS `Playlists.title`, `Playlists`.`createdAt` AS `Playlists.createdAt`, `Playlists`.`updatedAt` AS `Playlists.updatedAt`, `Playlists->PlaylistTrack`.`playlistID` AS `Playlists.PlaylistTrack.playlistID`, `Playlists->PlaylistTrack`.`trackID` AS `Playlists.PlaylistTrack.trackID`, `Playlists->PlaylistTrack`.`order` AS `Playlists.PlaylistTrack.order`, `Playlists->PlaylistTrack`.`createdAt` AS `Playlists.PlaylistTrack.createdAt`, `Playlists->PlaylistTrack`.`updatedAt` AS `Playlists.PlaylistTrack.updatedAt`, `PlaylistTrack`.`playlistID` AS `PlaylistTrack.playlistID`, `PlaylistTrack`.`trackID` AS `PlaylistTrack.trackID`, `PlaylistTrack`.`order` AS `PlaylistTrack.order`, `PlaylistTrack`.`createdAt` AS `PlaylistTrack.createdAt`, `PlaylistTrack`.`updatedAt` AS `PlaylistTrack.updatedAt`
FROM `Tracks` AS `Track`
LEFT OUTER JOIN ( `PlaylistTracks` AS `Playlists->PlaylistTrack`
INNER JOIN `Playlists` AS `Playlists` ON `Playlists`.`id` = `Playlists->PlaylistTrack`.`playlistID`) ON `Track`.`id` = `Playlists->PlaylistTrack`.`trackID`
INNER JOIN `PlaylistTracks` AS `PlaylistTrack` ON `Track`.`id` = `PlaylistTrack`.`trackID`
AND `PlaylistTrack`.`playlistID` = 3
ORDER BY `Playlists->PlaylistTrack`.`order` ASC;
Looks like it's doing something weird with a double inner join?
Using traditional methods I found on Stackoverflow, I can get consistent and correct results:
const response = await Playlist.findOne({
where: { id: 3 },
include: [{ model: Track }],
order: [[Track, PlaylistTrack, "order", "ASC"]]
})
From the response I can extract Tracks in correct ASCENDING order via response.Tracks.
This creates the following accurate query
SELECT `Playlist`.`id`, `Playlist`.`title`, `Playlist`.`createdAt`, `Playlist`.`updatedAt`, `Tracks`.`id` AS `Tracks.id`, `Tracks`.`title` AS `Tracks.title`, `Tracks`.`createdAt` AS `Tracks.createdAt`, `Tracks`.`updatedAt` AS `Tracks.updatedAt`, `Tracks->PlaylistTrack`.`playlistID` AS `Tracks.PlaylistTrack.playlistID`, `Tracks->PlaylistTrack`.`trackID` AS `Tracks.PlaylistTrack.trackID`, `Tracks->PlaylistTrack`.`order` AS `Tracks.PlaylistTrack.order`, `Tracks->PlaylistTrack`.`createdAt` AS `Tracks.PlaylistTrack.createdAt`, `Tracks->PlaylistTrack`.`updatedAt` AS `Tracks.PlaylistTrack.updatedAt`
FROM `Playlists` AS `Playlist`
LEFT OUTER JOIN ( `PlaylistTracks` AS `Tracks->PlaylistTrack`
INNER JOIN `Tracks` AS `Tracks` ON `Tracks`.`id` = `Tracks->PlaylistTrack`.`trackID`) ON `Playlist`.`id` = `Tracks->PlaylistTrack`.`playlistID`
WHERE `Playlist`.`id` = 3
ORDER BY `Tracks->PlaylistTrack`.`order` ASC;

Possible to WHERE on nested includes in Sequelize?

I've got a problem that I've been stuck on, to no avail - seemingly similar in nature to Where condition for joined table in Sequelize ORM, except that I'd like to query on a previous join. Perhaps code will explain my problem. Happy to provide any extra info.
Models:
A.hasMany(B);
B.belongsTo(A);
B.hasMany(C);
C.belongsTo(B);
This is what I'd like to be able to achieve with Sequelize:
SELECT *
FROM `A`AS `A`
LEFT OUTER JOIN `B` AS `B` ON `A`.`id` = `B`.`a_id`
LEFT OUTER JOIN `C` AS `B->C` ON `B`.`id` = `B->C`.`b_id`
AND (`B`.`b_columnName` = `B->C`.`c_columnName`);
This is how I imagine this working: (instead it will create a raw query (2 raw queries, for A-B/C) with AND ( `C`.`columnName` = '$B.columnName$')) on the join (second arg is a string). Have tried sequelize.col, sequelize.where(sequelize.col..., etc..)
A.findOne({
where: { id: myId },
include: [{
model: B,
include: [{
model: C,
where: { $C.c_columnName$: $B.b_columnName$ }
}]
}]
});
Use the Op.col query operator to find columns that match other columns in your query. If you are only joining a single table you can pass an object instead of an array to make it more concise.
const Op = Sequelize.Op;
const result = await A.findOne({
include: {
model: B,
include: {
model: C,
where: {
c_columnName: {
[Op.col]: 'B.b_columnName',
},
}
},
},
});

Sequelize join table without selecting

I'd like to select IDs from a join table in a many-to-many relation in Sequelize, and restrict results based on one of the endpoints. For example:
ModelA:
- id
- deleted_at
- is_expired
ModelB:
- id
- deleted_at
- is_active
ModelAToModelB:
- a_id
- b_id
I'd like to do something like
SELECT id FROM ModelAToModelB ab INNER JOIN ModelB b ON ab.b_id = b.id WHERE ab.id = someA_ID AND b.deleted_at IS NULL;
I'm currently do something like
const ModelB = require("./modelb")
const ModelAToModelB = require("./modelatob");
ModelAToModelB.findAll({
where: { id: someA_ID },
include: [
{
model: ModelB,
where: { deleted_at: null }
}
]
})
That seems to work, except then I also get all the data from ModelB as well, when all I want is ModelAToB.id.
Is there any way to scrap ModelB's data? Or maybe use something from ModelA to get just the association IDs?
const ModelB = require("./modelb")
const ModelAToModelB = require("./modelatob");
ModelAToModelB.findAll({
where: { id: someA_ID },
include: [
{
model: ModelB.scope(null), //Prevent the default scope from adding attributes
where: { deleted_at: null },
required: true, //Force an inner join
attributes: [] //Join without selecting any attributes
}
]
})

Categories

Resources