Вопрос
При работе с SQL-отношением один-ко-многим или многие-ко-многим в Golang, каков наилучший (эффективный, рекомендуемый, «подобный») способ отображения строкto struct?
Используя приведенный ниже пример настройки, я попытался подробно описать некоторые подходы с преимуществами и недостатками каждого из них, но мне было интересно, что рекомендует сообщество.
Требования
- Работает с PostgreSQL (может быть универсальным, но не включает специфические особенности MySQL / Oracle)
- Эффективность - Нет грубой форсировки каждой комбинации
- Без ORM - в идеале использовать только
database/sql
и jmoiron/sqlx
Пример
Для ясности я удалил обработку ошибок
Модели
type Tag struct {
ID int
Name string
}
type Item struct {
ID int
Tags []Tag
}
База данных
CREATE TABLE item (
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
);
CREATE TABLE tag (
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR(160),
item_id INT REFERENCES item(id)
);
Подход 1 - Выберите все элементы, затем выберите теги для каждого элемента
var items []Item
sqlxdb.Select(&items, "SELECT * FROM item")
for i, item := range items {
var tags []Tag
sqlxdb.Select(&tags, "SELECT * FROM tag WHERE item_id = $1", item.ID)
items[i].Tags = tags
}
Плюсы
Минусы
- Неэффективно при увеличении количества запросов к базе данных, пропорционально количеству элементов.
Подход 2. Построение SQL-соединения и циклического перемещения по строкам вручную
var itemTags = make(map[int][]Tag)
var items = []Item{}
rows, _ := sqlxdb.Queryx("SELECT i.id, t.id, t.name FROM item AS i JOIN tag AS t ON t.item_id = i.id")
for rows.Next() {
var (
itemID int
tagID int
tagName string
)
rows.Scan(&itemID, &tagID, &tagName)
if tags, ok := itemTags[itemID]; ok {
itemTags[itemID] = append(tags, Tag{ID: tagID, Name: tagName,})
} else {
itemTags[itemID] = []Tag{Tag{ID: tagID, Name: tagName,}}
}
}
for itemID, tags := range itemTags {
items = append(Item{
ID: itemID,
Tags: tags,
})
}
Плюсы
- Один вызов базы данных и курсор, который можно циклически перебирать, не занимая слишком много памяти
Минусы
- Сложный и сложный для разработки с несколькими объединениями и множеством атрибутов в структуре
- Не слишком производительный;Больше использования памяти и времени обработки по сравнению с большим количеством сетевых вызовов
Неудачный подход 3 - sqlx struct scan
Несмотря на неудачу, я хочу включить этот подход, как я нахожуэто будет моей текущей целью эффективности в сочетании с простотой разработки.Я надеялся, что, явно установив тег db
для каждого поля структуры, sqlx
может выполнить некоторое расширенное сканирование структуры
var items []Item
sqlxdb.Select(&items, "SELECT i.id AS item_id, t.id AS tag_id, t.name AS tag_name FROM item AS i JOIN tag AS t ON t.item_id = i.id")
К сожалению, эта ошибка выглядела как missing destination name tag_id in *[]Item
, что заставило меня поверить, что StructScan
недостаточно продвинут, чтобы рекурсивно перебирать строки (без критики - это сложный сценарий)
Возможный подход 4 - агрегаторы массивов PostgreSQL и GROUP BY
Пока яуверен, что это будет не работать. Я включил эту непроверенную опцию, чтобы посмотреть, можно ли ее улучшить, чтобы она могла работать.
var items = []Item{}
sqlxdb.Select(&items, "SELECT i.id as item_id, array_agg(t.*) as tags FROM item AS i JOIN tag AS t ON t.item_id = i.id GROUP BY i.id")
Когда у меня есть времяЯ постараюсь провести несколько экспериментов здесь.