Structured syntax for join on json array column #152
-
| For a left join and selection, are there structured operator(s) for filtering on json-encoded array of foreign keys? (Or is Tagged/_CodableJSONRepresentation just missing a conditional conformance?) Example to-many relationship: @Table
struct Exercise {
	let id: Tagged<Self, String>
        ...
	@Column(as: [BodyRegion.ID].JSONRepresentation.self)
	var trained: [BodyRegion.ID]
}
@Table
struct BodyRegion {
	let id: Tagged<Self, String>
	var name: String
}When composing a join and selection for a detail view, I had expected something like  
 @Selection
struct DetailScreenSelection: Identifiable, Sendable {
	var id: Exercise.ID { exercise.id }
	let exercise: Exercise
	@Column(as: [BodyRegion].JSONRepresentation.self) let trained: [BodyRegion]
}
Exercise
	.leftJoin(BodyRegion.all) { exercise, region in 
	      region.id.in(exercise.trained) // Error: TableColumn<Exercise, _CodableJSONRepresentation<Array<Tagged<BodyRegion, String>>>> does not conform to Sequence
	 }
	.select { exercise, region in
		Selection_Detail(
			exercise: exercise,
			regions: region.jsonGroupArray()
		)
	} | 
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 1 reply
-
| Hi @importRyan, your tables are not modeled the correct way for SQL. A parent table does not hold onto all of its child values. Instead, the child table has a foreign key pointing to its parent: @Table
struct Exercise: Identifiable {
  let id: String
}
@Table
struct BodyRegion: Codable {
  let id: String
  var name: String
  let exerciseID: Exercise.ID
}Then you can define a  @Selection
struct DetailScreenSelection {
  let exercise: Exercise
  @Column(as: [BodyRegion].JSONRepresentation.self)
  let trained: [BodyRegion]
}And then the query to select into this data type can be done simply as: func query() {
  _ = Exercise
    .group(by: \.id)
    .leftJoin(BodyRegion.all) { $0.id.eq($1.exerciseID) }
    .select {
      DetailScreenSelection.Columns(
        exercise: $0,
        trained: $1.jsonGroupArray()
      )
    }
}Note that you need a  I know it may seem weird to model things like this if you are used to SwiftData / Core Data, where parent models literally hold onto all of their child models, but it comes with a lot of benefits. You can see some examples of modeling schemas like this in the SharingGRDB demo apps, such as this one for the reminders app. | 
Beta Was this translation helpful? Give feedback.
-
| Gotcha. I misinterpreted the @column documentation as adding a layer beyond normal for forming relationships. Relationships go in a separate table: neither  | 
Beta Was this translation helpful? Give feedback.
Hi @importRyan, your tables are not modeled the correct way for SQL. A parent table does not hold onto all of its child values. Instead, the child table has a foreign key pointing to its parent:
Then you can define a
@Selectionthat selects all exercises along with their trained body regions like so:And then the query to select into this data type can be done simply as: