GRDB.swift: Wrong association aggregate count when using HasManyThrough, HasMany and Self Joins
I have a Task
record which has a HasManyThrough
association (many-to-many connection) with a Tag
record. The Task
record also has a HasMany
association (one-to-many connection) to other Task
records using Self Joins. Thus, the Task
record has two types: project
and subtask
:
struct Task: Identifiable {
var id = UUID()
var projectId: UUID?
var title: String
var status: TaskStatus
}
extension Task: TableRecord {
// Projects and tasks.
static let subtasks = hasMany(Task.self, key: "subtasks")
static let project = belongsTo(Task.self, key: "project")
}
I want to select all projects and associated tags and subtasks. The following works as expected:
let request = Task
.all()
.including(optional: Task.project)
.including(all: Task.subtasks)
.including(all: Task.tags)
.annotated(with: Task.tags.count)
I also want to include the total count of subtasks. Thus, I have added annotated()
below:
let request = Task
.all()
.including(optional: Task.project)
.including(all: Task.subtasks)
.including(all: Task.tags)
.annotated(with: Task.subtasks.count.forKey("totalSubtaskCount"))
totalSubtaskCount
is correct. However, I also want to include the total count of active subtasks. To achieve this, I have tried to use sum()
:
let request = Task
.all()
.including(optional: Task.project)
.including(all: Task.subtasks)
.including(all: Task.tags)
.annotated(with: Task.subtasks.count.forKey("totalSubtaskCount"))
.annotated(with: Task.subtasks.sum(Task.Columns.status == TaskStatus.Active).forKey("remainingSubtaskCount"))
If the number of associated tags is equal to 1, totalSubtaskCount
is correct. However, if the number of associated tags is > 1, the sum is multiplied by the number of associated tags. For example, if a project has 3 associated subtasks and 2 associated tags, totalSubtaskCount
becomes 3*2=6
when the correct number is 3.
Environment
**GRDB flavor(s): GRDB + GRDBCombine **GRDB version: 5.0.0-beta.2 **Installation method: SPM **Xcode version: 11.5 beta 2 (11N605f) **Swift version: 5.2.2 **Platform(s) running GRDB: iOS **macOS version running Xcode: 10.15.4 (19E287)
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Comments: 22 (14 by maintainers)
Sorry for being quiet for so long, @groue. Due to a new permanent job, I havn’t had time to do any iOS development since May. Hopefully I will be able to continue on my project (where I use GRDB) later. I’ll promise to give it a new try when I get back in business 😃
Sorry for the confusion, @groue! Here is the minimum schema:
Task.swift:
Tag.swift:
TaskTag.swift:
And here is a a minimum request:
My original code is very complex, so this is cut-and-paste (that is, not complied). Let me know if you need anything more!
Hello @simensol. Thanks for investigating a little more.
You are right, I think
count
better expresses the need of your application. It’s easier to read than the sum of 0/1.But this sum of 0/1 is equivalent to a filtered count. There is no good reason for this sum to be wrongly computed.
The way
count
is implemented (using the DISTINCT sql modifier) is a sign that something is wrong in the way aggregates are computed. I know that some tests will break if I remove thatDISTINCT
. But there is no such equivalent ofDISTINCT
forsum
and other mathematical aggregates. Conclusion: we have a plain bug that needs to be fixed. #778 is an early stage pull request that fixes it.That seems reasonable, @groue. If you make any changes to the beta, I can test it on my database. Here is a simplified version of the sql query produced by GRDB for my initial solution:
The above query produces
remainingSubtaskCount
which are multiplied by the number of associated tags (tagCount
).I have experienced much more trouble in my years using vanilla sqlite, so when I say that GRDB makes my sqlite life fun, its no understatement 😉 When started with GRDB I often wrote my own queries. However, after learning more, most of these queries are now replaced by GRDB.
As for the mental model, I think the documentation does quite well. At least compared to other libraries I have worked with. Especially the figures in The Types of Associations section have been invaluable to me ❤️
I will keep your 3 suggestions in mind. Thanks, again!