Lorsqu'il s'agit d'une relation SQL un-à-plusieurs ou plusieurs-à-plusieurs dans Golang, quelle est la meilleure façon (efficace, recommandée, "Go-like") de mapper les lignes à une structure?
En prenant l'exemple de configuration ci-dessous, j'ai essayé de détailler certaines approches avec les avantages et les inconvénients de chacun, mais je me demandais ce que la communauté recommande.
database/sql
et jmoiron/sqlx
Par souci de clarté, j'ai supprimé la gestion des erreurs
Modèles
type Tag struct {
ID int
Name string
}
type Item struct {
ID int
Tags []Tag
}
Base de données
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)
);
Approche 1 - Sélectionnez tous les articles, puis sélectionnez des étiquettes par article
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
}
Avantages
Inconvénients
Approche 2 - Construire la jointure SQL et parcourir les lignes manuellement
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,
})
}
Avantages
Inconvénients
Échec de l'approche 3 - analyse de la structure sqlx
Malgré l'échec, je veux inclure cette approche car je trouve que c'est mon objectif actuel d'efficacité associé à la simplicité de développement. Mon espoir était de définir explicitement la balise db
sur chaque champ struct sqlx
pourrait faire un scan de structure avancé
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")
Malheureusement, cela se traduit par missing destination name tag_id in *[]Item
ce qui me fait croire que le StructScan
n'est pas assez avancé pour parcourir récursivement les lignes (pas de critique - c'est un scénario compliqué)
Approche possible 4 - Agrégateurs de tableaux PostgreSQL et GROUP BY
Bien que je sois sûr que cela ne fonctionnera pas , j'ai inclus cette option non testée pour voir si elle pourrait être améliorée afin qu'elle peut fonctionner.
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")
Quand j'aurai du temps, j'essaierai de faire quelques expériences ici.
le sql en 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;
puis golang interroge ce sql:
select row_to_json(row)
from (
select * from item_tags
) row;
et démasquer pour aller struct:
pro:
postgres gère la relation des données. ajouter/mettre à jour des données avec les fonctions sql.
golang gère le modèle commercial et la logique.
c'est facile.
.
Je peux suggérer une autre approche que j'ai utilisée auparavant.
Vous créez un json des balises dans ce cas dans la requête et le renvoyez.
Avantages: Vous avez 1 appel à la base de données, qui agrège les données, et tout ce que vous avez à faire est d'analyser le json dans un tableau.
Inconvénients: C'est un peu moche. N'hésitez pas à me critiquer pour cela.
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
}
Edit: le code peut se comporter bizarrement, donc je trouve préférable d'utiliser un tableau de balises temporaires lors du déplacement au lieu d'utiliser la même structure.