Skip to content

Commit f849af3

Browse files
committed
Add docs about pre-loading multiple associations.
1 parent 995df0b commit f849af3

File tree

1 file changed

+61
-4
lines changed

1 file changed

+61
-4
lines changed

Sources/StructuredQueriesCore/Documentation.docc/Articles/QueryCookbook.md

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ table. For example, querying for all reminders lists along with an array of the
349349
list. We'd like to be able to query for this data and decode it into a collection of values
350350
from the following data type:
351351

352-
```struct
352+
```swift
353353
struct Row {
354354
let remindersList: RemindersList
355355
let reminders: [Reminder]
@@ -395,7 +395,7 @@ Another way to do this is to use the `@Selection` macro described above
395395
(<doc:QueryCookbook#Custom-selections>), along with a ``Swift/Decodable/JSONRepresentation`` of the
396396
collection of reminders you want to load for each list:
397397

398-
```struct
398+
```swift
399399
@Selection
400400
struct Row {
401401
let remindersList: RemindersList
@@ -414,17 +414,74 @@ columns of [primary keyed tables](<doc:PrimaryKeyedTables>):
414414

415415
```swift
416416
RemindersList
417-
.join(Reminder.all) { $0.id.eq($1.remindersListID) }
417+
.leftJoin(Reminder.all) { $0.id.eq($1.remindersListID) }
418418
.select {
419419
Row.Columns(
420420
remindersList: $0,
421-
reminders: $1.jsonGroupArray()
421+
reminders: #sql("\($1.jsonGroupArray(filter: $1.id.isNot(nil)))")
422422
)
423423
}
424424
```
425425

426+
> Note: There are 3 important things to note about this query:
427+
>
428+
> * Since not every reminders list will have a reminder associated with it, we are using a
429+
> ``Select/leftJoin(_:on:)``. That will make sure to select all lists no matter what.
430+
> * We are using `jsonGroupArray` to encode all reminders associated with a list into a JSON object
431+
> and bundle them into an array. And because it's possible to have no reminders in a list,
432+
> we further use the `filter` option to remove any NULL values from the array.
433+
> * And lastly, `$1` represents an optionalized table due to the left join, and hence the
434+
> `$1.jsonGroupArray(…)` expression actually returns an _optional_ array of reminders. But due
435+
> to how `json_group_array` works in SQL we can be guaranteed that it always returns an array,
436+
> and never NULL, and so we are using the `#sql` macro as a quick escape hatch to take
437+
> responsibility for the types in this expression.
438+
426439
This allows you to fetch all of the data in a single SQLite query and decode the data into a
427440
collection of `Row` values. There is an extra cost associated with decoding the JSON object,
428441
but that cost may be smaller than executing multiple SQLite requests and transforming the data
429442
into `Row` manually, not to mention the additional code you need to write and maintain to process
430443
the data.
444+
445+
It is even possible to load multiple associations at once. For example, suppose that there is a
446+
`Milestone` table that is associated with a `RemindersList`:
447+
448+
```swift
449+
@Table
450+
struct Milestone: Identifiable, Codable {
451+
let id: Int
452+
var title = ""
453+
var remindersListID: RemindersList.ID
454+
}
455+
```
456+
457+
And suppose you would like to fetch all `RemindersList`s along with the collection of all milestones
458+
and reminders associated with the list:
459+
460+
```struct
461+
@Selection
462+
struct Row {
463+
let remindersList: RemindersList
464+
@Column(as: [Milestone].JSONRepresentation.self)
465+
let milestons: [Milestone]
466+
@Column(as: [Reminder].JSONRepresentation.self)
467+
let reminders: [Reminder]
468+
}
469+
```
470+
471+
It is possible to do this using two left joins and two `jsonGroupArray`s:
472+
473+
```swift
474+
RemindersList
475+
.leftJoin(Reminder.all) { $0.id.eq($1.remindersListID) }
476+
.leftJoin(Milestone.all) { $0.id.eq($2.remindersListID) }
477+
.select {
478+
Row.Columns(
479+
remindersList: $0,
480+
reminders: #sql("\($1.jsonGroupArray(filter: $1.id.isNot(nil))"),
481+
reminders: #sql("\($2.jsonGroupArray(filter: $2.id.isNot(nil))")
482+
)
483+
}
484+
```
485+
486+
This will now load all reminders lists with all of their reminders and milestones in one single
487+
SQL query.

0 commit comments

Comments
 (0)