将一对多关系检索到JSON sql pure,Golang,Performance

时间:2016-07-13 00:30:02

标签: json postgresql go

假设我有以下结构,它是映射表。

type Publisher struct{
   ID int       `db:"id"`
   Name  string `db:"name"`
   Books []*Book  
}

type Book struct {
   ID int       `db:"id"`
   Name string  `db:"name"`
   PublisherID   `db:"publisher_id"` 
}

那么,如果我想要检索所有相关图书的所有发布者,我希望获得JSON这样的内容:

[ //Publisher 1 
  {
    "id" : "10001", 
    "name":"Publisher1",
    "books" : [
       { "id":321,"name": "Book1"}, 
       { "id":333,"name": "Book2"}
    ]
  },
  //Publisher 2
  {
    "id" : "10002", 
    "name":"Slytherin Publisher",
    "books" : [
       { "id":4021,"name": "Harry Potter and the Chamber of Secrets"}, 
       { "id":433,"name": "Harry Potter and the Order of the Phoenix"}
    ]
  },
]

所以我使用以下结构来检索与Publisher相关的所有查询

type PublisherRepository struct{
   Connection *sql.DB
}
// GetEbooks return all the books related with a publisher
func (r *PublisherRepository) GetBooks(idPublisher int) []*Book {
    bs := make([]Book,0)
    sql := "SELECT * FROM books b WHERE b.publisher_id =$1 "
    row, err := r.Connection.Query(sql,idPublisher)
    if err != nil {
      //log
    }
    for rows.Next() {
      b := &Book{}
      rows.Scan(&b.ID, &b.Name, &b.PublisherID)
      bs := append(bs,b)
    }
    return bs
}
func (r *PublisherRepository) GetAllPublishers() []*Publisher {
    sql := "SELECT * FROM publishers"
    ps := make([]Publisher,0)
    rows, err := r.Connection.Query(sql)
    if err != nil { 
       // log 
    }
    for rows.Next() {
       p := &Publisher{}
       rows.Scan(&p.ID,&p.Name)
       // Is this the best way? 
       books := r.GetBooks(p.ID)
       p.Books  = books
    }
    return ps

}

所以,这是我的问题

  1. 检索具有最佳效果的所有publisher的最佳方法是什么,因为for内的for不是最佳解决方案,如果我&#39 ; 200个出版商和每个出版商的平均出版物有100本书。

  2. 是GoLang惯用语PublisherRepository还是有其他方法来创建一些用纯sql来管理实体事务的方法?

2 个答案:

答案 0 :(得分:3)

1)不好这将是每次迭代的sql请求。所以这里的解决方案不会为每个发布者提出额外的请求:

func (r *PublisherRepository) GetAllPublishers() []*Publisher {
    sql := "SELECT * FROM publishers"
    ps := make(map[int]*Publisher)
    rows, err := connection.Query(sql)
    if err != nil { 
       // log 
    }
    for rows.Next() {
       p := &Publisher{}
       rows.Scan(&p.ID,&p.Name)
       ps[p.ID] = p
    }

    sql = "SELECT * FROM books"
    rows, err := connection.Query(sql)
    if err != nil {
      //log
    }
    for rows.Next() {
      b := &Book{}
      rows.Scan(&b.ID, &b.Name, &b.PublisherID)

      ps[b.PublisherID].Books = append(ps[b.PublisherID].Books, b)
    }

    // you might choose to keep the map as a return value, but otherwise:

    // preallocate memory for the slice
    publishers := make([]*Publisher, 0, len(ps))
    for _, p := range ps {
        publishers = append(publishers, p)
    }

    return publishers
}

2)除非您只创建一次PublisherRepository,否则创建和关闭大量连接可能是一个坏主意。根据你的sql客户端实现,我建议(并且还看到许多其他go数据库客户端)为整个服务器建立一个连接。池是由许多sql客户端在内部完成的,这就是你应该检查你的sql客户端的原因。 如果您的sql客户端库在内部汇集,则使用全局变量进行"连接" (如果在内部完成池化,它实际上不是一个连接):

connection *sql.DB

func New () *PublisherRepository {
    repo := &PublisherRepository{}
    return repo.connect()
}

type PublisherRepository struct{
}

func (r *PublisherRepository) connect() *PublisherRepository {
    // open new connection if connection is nil 
    // or not open (if there is such a state)
    // you can also check "once.Do" if that suits your needs better
    if connection == nil {
        // ...
    }
    return r
}

因此,每次创建新的PublisherRepository时,它都只会检查连接是否已存在。如果你使用once.Do,go只会创建"连接"一旦你完成了它。

如果你有其他结构也将使用连接,你需要一个连接变量的全局位置或(甚至更好)为sql客户端编写一个小包装器包,然后在所有结构中使用

答案 1 :(得分:0)

  1. 在您的情况下,最简单的方法是在查询中使用json_agg。像这里http://sqlfiddle.com/#!15/97c41/4(sqlfiddle很慢所以这里是截图http://i.imgur.com/hxMPkUa.png)不是很友好(如果你想对书籍做点什么,你需要解组查询结果数据)但是在一个查询中的所有书籍都是你想要没有for循环。

  2. 正如@TehSphinX所说,最好有一个全球db连接。

  3. 但在实施奇怪的查询之前,我建议您先考虑一下:为什么需要在一个API查询中返回发布者及其图书的完整列表?我无法想象网络或移动应用中的情况,您的想法可能是一个很好的决定。通常,您只需向用户显示发布者列表,然后用户选择一个,并向您显示此发布者的书籍列表。这对您和您的用户来说是“双赢”的情况 - 您可以进行简单的查询,您的用户只需获得他们实际需要的小数据集,而无需支付不必要的流量/浪费浏览器内存。正如您所说,可以有200个出版商拥有100本书,我确信您的用户不需要在一个请求中加载20000本书。当然,如果你不是想让你的API更容易被盗窃。

    即使您为每个发布商提供了类似预览的简短书籍列表,您也应该考虑发布商的分页和/或针对此案例的书籍数据的非规范化(在publishers表格中添加一列JSON格式的书籍简短列表。)