Классы данных

data class Grade(
    val studentId: UUID,
    val value: Int? = null,
    val date: LocalDate? = null,
)

data class Course(
    val name: String,
    val grades: List<Grade> = emptyList(),
    val id: UUID? = UUID.randomUUID()
)

Таблица курсов

object CourseTable : IdTable<UUID>("courses") {
    override val id = uuid("id").entityId().uniqueIndex()
    val name = varchar("name", 512)
}

Таблица оценок

object GradeTable : IdTable<UUID>("grades") {
    override val id = uuid("id").entityId().uniqueIndex()
    val value = integer("value").nullable()
    val date = date("date").nullable()
    val course: Column<EntityID<UUID>> = 
      reference("course", CourseTable)
    val student: Column<EntityID<UUID>> = 
      reference("students", StudentTable)
}

Запись и чтение курса

fun UpdateBuilder<Number>.save(course: Course) {
    this[CourseTable.id] = course.id ?: UUID.randomUUID()
    this[CourseTable.name] = course.name
}

fun ResultRow.toCourse() = Course(
    id = this[CourseTable.id].value,
    name = this[CourseTable.name]
)

Добавление оценок

fun Course.addGrade(grade: Grade) {
    check(id != null)
    GradeTable.insert {
        it[id] = UUID.randomUUID()
        it[value] = grade.value
        it[date] = grade.date
        it[course] = this@addGrade.id
        it[student] = grade.studentId
    }
}

Чтение курса целиком

fun readCourse(id: UUID? = null): List<Course> {
    val query = (CourseTable innerJoin GradeTable)
        .selectAll()

    id?.let {
        query.andWhere {
            CourseTable.id eq it
        }
    }

Чтение курса целиком

return query.groupBy { it[CourseTable.id] }
  .map {
    val grades = it.value.map {
    Grade(
      it[GradeTable.student].value,
      it[GradeTable.value],
      it[GradeTable.date])}
    Course(
      it.value[0][CourseTable.name],
      grades,
      it.key.value)}

Чтение курса целиком

fun setGrade(courseId: UUID, studentId: UUID, newValue: Int) {
  GradeTable.update({
    GradeTable.course eq courseId and
    (GradeTable.student eq studentId)
  }) {
    it[value] = newValue
    it[date] = LocalDate.now()
  }
}

Добавление курсов

val courses = listOf("Math", "Phys", "History").map {
  Course(it)
}
CourseTable.batchInsert(courses) {
  save(it)
}

INSERT INTO COURSES (ID, "NAME") VALUES ('...', 'Math')
INSERT INTO COURSES (ID, "NAME") VALUES ('...', 'Phys')
INSERT INTO COURSES (ID, "NAME") VALUES ('...', 'History')

Добавление оценок

dbCourse.map { course ->
  dbStudents.map { student ->
    check(student.id != null)
    course.addGrade(Grade(student.id))
  }
}

INSERT INTO GRADES (COURSE, "DATE", ID, STUDENTS, "VALUE") 
  VALUES ('969bd1a9-c11a-433c-a0e8-a5d0afdd3af7', NULL, 
  '6a4334f1-1354-44ba-b85d-35a15d1fc691', 
  'a15a8387-ff26-497a-af7f-044ff5749343', NULL)

Изменение оценок

val pennyId = dbStudents.find { it.name == "Penny" }?.id!!
val mathId = dbCourse.find { it.name == "Math" }?.id!!
setGrade(mathId, pennyId, 5)

UPDATE GRADES SET "VALUE"=5, "DATE"='2023-05-01' 
  WHERE (GRADES.COURSE = '969bd1a9-c11a-433c-a0e8-a5d0afdd3af7') 
  AND (GRADES.STUDENTS = 'a15a8387-ff26-497a-af7f-044ff5749343')

Чтение курса целиком

println(readCourse(mathId))

SELECT COURSES.ID, COURSES."NAME", GRADES.ID, 
  GRADES."VALUE", GRADES."DATE", GRADES.COURSE, GRADES.STUDENTS 
  FROM COURSES INNER JOIN GRADES 
  ON COURSES.ID = GRADES.COURSE 
  WHERE COURSES.ID = '969bd1a9-c11a-433c-a0e8-a5d0afdd3af7'