Связь многие ко многим в Go
Один студент может слушать несколько курсов, и один курс могут слушать многие студенты. Двух таблиц с одним полем ссылки для этого мало: ни одна сторона не может хранить список номеров в одной строке без дублирования.
Связь многие ко многим держат третьей таблицей с двумя номерами. Каждая ее строка фиксирует одну пару студент-курс, а полный набор пар собирают из нескольких строк.
Создадим справочники и таблицу
student_course:
package main
import (
"database/sql"
"fmt"
"log"
"os"
_ "modernc.org/sqlite"
)
func main() {
os.Remove("shop.db")
db, err := sql.Open("sqlite", "shop.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
_, err = db.Exec("PRAGMA foreign_keys = ON")
if err != nil {
log.Fatal(err)
}
_, err = db.Exec(`
CREATE TABLE students (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
)
`)
if err != nil {
log.Fatal(err)
}
_, err = db.Exec(`
CREATE TABLE courses (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL
)
`)
if err != nil {
log.Fatal(err)
}
_, err = db.Exec(`
CREATE TABLE student_course (
student_id INTEGER NOT NULL,
course_id INTEGER NOT NULL,
FOREIGN KEY (student_id) REFERENCES students (id),
FOREIGN KEY (course_id) REFERENCES courses (id)
)
`)
if err != nil {
log.Fatal(err)
}
_, err = db.Exec(`INSERT INTO students (name) VALUES (?)`, "ann")
if err != nil {
log.Fatal(err)
}
_, err = db.Exec(`INSERT INTO courses (title) VALUES (?)`, "go")
if err != nil {
log.Fatal(err)
}
_, err = db.Exec(`INSERT INTO courses (title) VALUES (?)`, "sql")
if err != nil {
log.Fatal(err)
}
_, err = db.Exec(
`INSERT INTO student_course (student_id, course_id) VALUES (?, ?)`,
1, 1,
)
if err != nil {
log.Fatal(err)
}
_, err = db.Exec(
`INSERT INTO student_course (student_id, course_id) VALUES (?, ?)`,
1, 2,
)
if err != nil {
log.Fatal(err)
}
fmt.Println("links ready")
}
Имена и названия курсов достают двумя шагами соединения через таблицу связи:
package main
import (
"database/sql"
"fmt"
"log"
"os"
_ "modernc.org/sqlite"
)
func main() {
os.Remove("shop.db")
db, err := sql.Open("sqlite", "shop.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
_, err = db.Exec("PRAGMA foreign_keys = ON")
if err != nil {
log.Fatal(err)
}
_, err = db.Exec(`
CREATE TABLE students (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
)
`)
if err != nil {
log.Fatal(err)
}
_, err = db.Exec(`
CREATE TABLE courses (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL
)
`)
if err != nil {
log.Fatal(err)
}
_, err = db.Exec(`
CREATE TABLE student_course (
student_id INTEGER NOT NULL,
course_id INTEGER NOT NULL,
FOREIGN KEY (student_id) REFERENCES students (id),
FOREIGN KEY (course_id) REFERENCES courses (id)
)
`)
if err != nil {
log.Fatal(err)
}
_, err = db.Exec(`INSERT INTO students (name) VALUES (?)`, "ann")
if err != nil {
log.Fatal(err)
}
_, err = db.Exec(`INSERT INTO courses (title) VALUES (?)`, "go")
if err != nil {
log.Fatal(err)
}
_, err = db.Exec(
`INSERT INTO student_course (student_id, course_id) VALUES (?, ?)`,
1, 1,
)
if err != nil {
log.Fatal(err)
}
rows, err := db.Query(`
SELECT s.name, c.title
FROM students s
INNER JOIN student_course sc ON sc.student_id = s.id
INNER JOIN courses c ON sc.course_id = c.id
`)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var studentName string
var courseTitle string
err = rows.Scan(&studentName, &courseTitle)
if err != nil {
log.Fatal(err)
}
fmt.Println(studentName, courseTitle)
}
err = rows.Err()
if err != nil {
log.Fatal(err)
}
}
Даны таблицы:
CREATE TABLE students (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE courses (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL
);
CREATE TABLE student_course (
student_id INTEGER NOT NULL,
course_id INTEGER NOT NULL,
FOREIGN KEY (student_id) REFERENCES students (id),
FOREIGN KEY (course_id) REFERENCES courses (id)
);
Товар может относиться к нескольким категориям, и категория может включать много товаров. Опишите имена таблиц и столбцы связи без готового текста запроса выборки.
Даны заполненные students,
courses и student_course
из первого примера урока. Напишите запрос,
который выведет названия всех курсов
студента "ann".
Скажите, почему в student_course
обычно не хранят имя студента текстом,
а только его номер.