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

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;

Related

A field array with object ids returns automatically sorted unintentionally

I have a query which returns an array of object ids sorted by their id instead of the order that i put them in the array. Why is that happening? i don't have anything in the code related to the sort option, Moreover it does return like i wanted in the backend but in the front it returns sorted. why is that?
Edit:
i use graphql-compose-mongoose:
this is the part of the schema :
sections: {
type: [mongoose.Schema.Types.ObjectId],
ref: "Sections",
default: [],
},
and this is the relation:
resolver: () => SectionTC.getResolver("findByIds"),
prepareArgs: {
_ids: (source: Questionnaire) => source.sections,
},
projection: {
sections: true,
},
so the array is like:
63398d312940b0d8da89e1c1
62e7d0f168db432caf5c8ff7
62e7d11b68db432caf5c8ff8
63398d6c2940b0d8da89e1c8
but when i populate them, they return in a sorted order which i cannot get to disable.

Sequelize exclude orderBy from subQuery

Trying to use sequelize findAndCountAll method, to get items.
I've to use distinct, and offset with limit due to my task.
The problem is, that in associated model i've column with array type, and i need to order parent model by that array length.
My query looks like :
const { rows, count } = await this.repo.findAndCountAll({
where: { someField: someValue },
col: 'someCol',
distinct: true,
include: [
{
model: someNestedModel,
as: 'someNestedModelAssociation',
include: [{ model: someInnerNestedModel, as: 'someInnerNestedAssociation' }]
}
],
// eslint-disable-next-line #typescript-eslint/ban-ts-comment
//#ts-ignore
order: this.getOrderByOptions(sortOrder, orderBy),
limit,
offset
});
getOrderByOptions(sortOrder, orderBy) {
switch (orderBy) {
case Sort_By_Array_Length:
return Sequelize.literal('json_array_length(someModelName.someColumnWithArrayName) ASC');
default:
return [[orderBy, sortOrder]];
}
}
The problem is, that my order by query is used both in subQuery and mainQuery.
And using it into subQuery leads to error, cz there is no such field.
If i use subQuery:false flag, it works, but then i got messed with returning results, due to problems with subQuery:false and offset&limits.
So the question is, is there a way, to exclude orderBy field from subQuery?
P.S. Models have many to many association with through table.

Sequelize counting associated entries with separate

I'm trying to count the associated entries using the separate attribute in my includes to improve performance (without it the request it's taking 5s). But I'm receiving the following error:
"message": "missing FROM-clause entry for table "likedPosts""
Sorry for bad english, it's not my first. I hope you understand and can help me.
My code:
#Query((returns) => [Post], {
nullable: true
})
async getAllFeedPostsByUserId(#Arg('user_id') user_id: number): Promise < Post[] > {
const result = await Post.findAll({
attributes: {
include: [
[Sequelize.fn("COUNT", Sequelize.col("likedPosts.feed_post")), "likesAmount"]
]
},
include: [{
model: LikedPosts,
as: 'likedPosts',
attributes: [],
separate: true,
}, ]
});
return result;
}
I think group is must to count entries.
Post.findAll({
attributes: {
include: [[Sequelize.fn('COUNT', Sequelize.col('likedPosts.feed_post')), 'likesAmount']]
},
include: [{
model: LikedPosts,
attributes: []
}],
group: ['likedPosts.feed_post'] // groupBy is necessary else it will generate only 1 record with all rows count
})
I can see seperate
separate desc
To elaborate: by default, to retrieve the related model instance, Sequelize will use a SQL JOIN. By enabling separate, Sequelize will perform a separate query for each of the associated models, and join the resulting documents in code (instead of letting the database perform the join).

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

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"]];

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',
},
}
},
},
});

Categories

Resources