Эффективное отображение базы данных один-ко-многим-ко-многим для структурирования в Голанге - PullRequest
0 голосов
/ 09 февраля 2019

Вопрос

При работе с 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")

Когда у меня есть времяЯ постараюсь провести несколько экспериментов здесь.

Ответы [ 2 ]

0 голосов
/ 10 февраля 2019

sql в postgres:

create schema temp;
set search_path = temp;
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)
);

create view item_tags as
select id,
  (
          select
            array_to_json(array_agg(row_to_json(taglist.*))) as array_to_json
          from (
                select tag.name, tag.id
                 from tag
                         where item_id = item.id
               ) taglist ) as tags
from item ;


-- golang query this maybe 
select  row_to_json(row)
from (
    select * from item_tags
) row;

, затем golang запросит этот sql:

select  row_to_json(row)
from (
    select * from item_tags
) row;

и unmarshall go struct:

pro:

  1. postgres управляет отношением данных.Добавить / обновить данные с помощью функций sql.

  2. golang управлять бизнес-моделью и логикой.

это простой способ.

.

0 голосов
/ 10 февраля 2019

Я могу предложить другой подход, который я использовал ранее.

Вы делаете json тегов в этом случае в запросе и возвращаете его.

Плюсы : у вас есть 1 вызов в БД, который объединяет данные, и все, что вам нужно сделать, - это проанализировать json в массив.

Минусы : Это немного некрасиво.Не стесняйтесь меня за это.

type jointItem struct {
  Item 
  ParsedTags string
  Tags []Tag `gorm:"-"`
}

var jointItems []*jointItem
db.Raw(`SELECT 
  items.*, 
  (SELECT CONCAT(
            '[', 
             GROUP_CONCAT(
                  JSON_OBJECT('id', id,
                             'name', name 
                  )
             ), 
            ']'
         )) as parsed_tags 
   FROM items`).Scan(&jointItems)

for _, o := range jointItems {
var tempTags []Tag
   if err := json.Unmarshall(o.ParsedTags, &tempTags) ; err != nil {
      // do something
   }
  o.Tags = tempTags
}


Редактировать: код может вести себя странно, поэтому я считаю, что лучше использовать массив временных тегов при перемещении, а не использовать ту же структуру.

...