sequelize: Sequelize count returns wrong count when association models included
Sequelize count returns wrong count when association models included, in my case, via the model’s defaultScope
.
What are you doing?
I have a Post model. The Post model has attachments (oneToMany)
I’m trying to count posts [with attachments] using Sequelize’s count
// Post model
module.exports = (sequelize, DataTypes) => {
const Post = sequelize.define(
'Post',
{
id: {
primaryKey: true,
type: DataTypes.UUID
},
title: DataTypes.STRING
}
)
Post.associate = models => {
Post.hasMany(models.PostAttachment, { as: 'attachments' })
Post.addScope('defaultScope', {
include: [
{ model: models.PostAttachment, as: 'attachments' }
],
order: [
['id', 'ASC']
]
}, {
override: true
})
}
return Post
}
// PostAttachment model
module.exports = (sequelize, DataTypes) => {
const PostAttachment = sequelize.define(
'PostAttachment',
{
id: {
primaryKey: true,
type: DataTypes.UUID
},
key: DataTypes.STRING
}
)
PostAttachment.associate = models => {
PostAttachment.belongsTo(models.Post)
}
return PostAttachment
}
Sequelize query
const posts = await Post.findAll()
const total = await Post.count()
return { posts, total }
Scenario 1: Single Post, multiple PostAttachments
Posts
table
id | title | createdAt | updatedAt |
---|---|---|---|
f8d68fc0-48b1-4e90-af73-c9a4dc577461 | Title1 | 2018-05-23 16:47:09.228000 +00:00 | 2018-05-23 16:47:09.228000 +00:00 |
PostAttachments
table
id | key | PostId | createdAt | updatedAt |
---|---|---|---|---|
410819f6-d1c4-44f4-9a52-edbac765e714 | Key1 | f8d68fc0-48b1-4e90-af73-c9a4dc577461 | 2018-05-23 16:47:09.228000 +00:00 | 2018-05-23 16:47:09.228000 +00:00 |
754ca095-4967-4472-a0f4-1a8e2d761a24 | Key2 | f8d68fc0-48b1-4e90-af73-c9a4dc577461 | 2018-05-23 16:47:09.228000 +00:00 | 2018-05-23 16:47:09.228000 +00:00 |
754ca095-4967-4472-a0f4-1a8e2d761a25 | Key3 | f8d68fc0-48b1-4e90-af73-c9a4dc577461 | 2018-05-23 16:47:09.228000 +00:00 | 2018-05-23 16:47:09.228000 +00:00 |
What do you expect to happen?
I’m expecting to get 1 post and total sum 1
:
{
"posts": [{...}],
"total": 1
}
What is actually happening?
I’m actually getting 1 post and total sum 3
:
{
"posts": [{...}],
"total": 3
}
Scenario 2: Multiple Posts, multiple PostAttachments
Posts
table
id | title | createdAt | updatedAt |
---|---|---|---|
f8d68fc0-48b1-4e90-af73-c9a4dc577461 | Title1 | 2018-05-23 16:47:09.228000 +00:00 | 2018-05-23 16:47:09.228000 +00:00 |
01a528d2-8696-465f-8739-2b32bc52ceca | Title2 | 2018-05-23 16:47:09.228000 +00:00 | 2018-05-23 16:47:09.228000 +00:00 |
01a528d2-8696-465f-8739-2b32bc52cecb | Title3 | 2018-05-23 16:47:09.228000 +00:00 | 2018-05-23 16:47:09.228000 +00:00 |
PostAttachments
table
id | key | PostId | createdAt | updatedAt |
---|---|---|---|---|
410819f6-d1c4-44f4-9a52-edbac765e714 | Key1 | f8d68fc0-48b1-4e90-af73-c9a4dc577461 | 2018-05-23 16:47:09.228000 +00:00 | 2018-05-23 16:47:09.228000 +00:00 |
754ca095-4967-4472-a0f4-1a8e2d761a24 | Key2 | f8d68fc0-48b1-4e90-af73-c9a4dc577461 | 2018-05-23 16:47:09.228000 +00:00 | 2018-05-23 16:47:09.228000 +00:00 |
754ca095-4967-4472-a0f4-1a8e2d761a25 | Key3 | f8d68fc0-48b1-4e90-af73-c9a4dc577461 | 2018-05-23 16:47:09.228000 +00:00 | 2018-05-23 16:47:09.228000 +00:00 |
754ca095-4967-4472-a0f4-1a8e2d761a26 | Key4 | 01a528d2-8696-465f-8739-2b32bc52ceca | 2018-05-23 16:47:09.228000 +00:00 | 2018-05-23 16:47:09.228000 +00:00 |
754ca095-4967-4472-a0f4-1a8e2d761a27 | Key5 | 01a528d2-8696-465f-8739-2b32bc52ceca | 2018-05-23 16:47:09.228000 +00:00 | 2018-05-23 16:47:09.228000 +00:00 |
What do you expect to happen?
I’m expecting to get 3 posts and total sum 3
:
{
"posts": [{...}, {...}, {...}],
"total": 3
}
What is actually happening?
I’m actually getting 3 posts and total sum 6
:
{
"posts": [{...}, {...}, {...}],
"total": 6
}
I ran a few configurations through. It appears the count
calculations are based on each post multiplied by the maximum between 1
and the number of attachments per post, so:
- PostA * Math.max(1, 3 attachments) = 3
- PostB * Math.max(1, 2 attachments) = 2
- PostC * Math.max(1, 0 attachments) = 1
Dialect: postgres Dialect version: XXX Database version: 11.1 Sequelize version: 4.43.0 Tested with latest release: No (If yes, specify that version)
About this issue
- Original URL
- State: open
- Created 5 years ago
- Reactions: 33
- Comments: 16 (2 by maintainers)
My solution, which may break other queries but works for my application, is to add a
distinct
condition. The distinct param is only applied to the count query so it ensures that the correct document count is returned, whilst allowing the internal Sequelize aggregation of associations to continue as intended.Adding
distinct: 'Article.id'
to myArticle.findAndCountAll
query gives me the correct article count.Here’s an enhanced workaround that may not cover every case but hasn’t failed me so far:
HTH others. 💝
Although I agree this appears to be odd but
count
andfindAll
both are returning correct result.Let’s first talk about
findAll
, it joins two tables, find 6 rows, sequelize arrange them underattachments
so finally you get 3 array items. You can test this by passingraw: true
which will return 6 rows.Similarly
count
executes same query and see 6 rows, but it don’t need to arrange result intoattachments
(it can’t as db is returning row count directly) thus returns plain result of6
.So in your situation I would try these alternatives
count
, I already gotfindAll
result I can use that to show count.findAll
socount
can return result correctly regardless of scope used infindAll
@sushantdhiman I understand why it’s happening but I don’t think this would be the expected behavior for anyone unless they were including
{ raw: true }
.This is even more troublesome for the folks using
findAndCountAll
(as in the other issues I’ve linked to). Per the Sequelize docs:So, if I used
findAndCountAll
as the DB in-between for my paginated API query, I could be told there were 6 total records but only have 3 returned. Unfortunately, that does not make any sense from the API consumer standpoint [unless I eventually had 6 finalized records and I was limiting that page to just 3 😉 ].…although now I’m concerned how the
limit
andoffset
options would interact with association-expanded rows like this, too. 😬I had the same issue and my solution is used
distinct: true
andcol:
${DB.InventoryItem.name}.id` in options of findAndCountAll()The root cause must be resolved by remove table
left join
andleft outer join
to get match condition based from options when using subfunctionthis.count(countOptions)
in sequelizeadding
distinct: true
worked for me@JamesMGreene I 100% agree that the expected behaviour of the count value from findAndCountAll should be the actual number of documents that exist, and not how it is currently implemented.
If I have a document with 2 associations, the count is returning 2 but only 1 document is returned. If I delete one of the associations, then count and returned documents are 1.
I would expect that the count matches the total number of documents, not the total number of documents + total number of associations.
It’s work
I developed this workaround that addresses the issue for inclusions from
options.include
:However, as you can see in the code, it does NOT fix inclusions from the model’s active scope. It could but it would have to actually alter the scope itself, which is not really acceptable as it would negatively impact subsequent use of the scope (or the scoped model, anyway). 😭
The availability of a
beforeCountAfterOptions
hook (equivalent in concept to thebeforeFindAfterOptions
hook) would make this achievable but I can see that’s a bit more challenging to implement since this filters down into theaggregate
function.