过滤和排序 SQL 查询以重新创建嵌套结构

Posted

技术标签:

【中文标题】过滤和排序 SQL 查询以重新创建嵌套结构【英文标题】:Filtering and sorting an SQL query to recreate a nested struct 【发布时间】:2021-10-04 04:09:36 【问题描述】:

我是 Go 新手,我正在尝试从可以作为 JSON 有效负载发送的 SQL 查询中填充一个名为 Reliefworker 的结构。

基本上我有一个救济工作者,他可能被分配到许多社区,社区可以由多个地区组成。

我怀疑除了我想要的之外,还有一种聪明的方法可以做到这一点,它是一种原始解决方案,它将向 SQL(按社区)添加排序并创建一个函数来检测所添加的社区是否与前一个,在这种情况下,我将创建一个新的社区结构类型对象来附加。

type Reliefworker struct 
    Name     string `json:"name"`
    Communities  []Community `json:"community"`
    Deployment_id    string `json:"deployment_id"`
 

 type Community struct
    Name     string `json:"name"`
    community_id     string `json:"community_id"`
    Regions []Region `json:"regions"`


type Region struct
    Name     string `json:"name"`
    Region_id     string `json:"region_id"`
    Reconstruction_grant   string `json:"reconstruction_grant"`
    Currency string `json:"currency"`

目前我创建了一个结构,它反映了我在思考下一步行动时从 SQL 中实际得到的信息。也许这可能是一个很好的垫脚石,而不是尝试即时转换?

type ReliefWorker_community_region struct 
    Deployment_id        string
    Community_title      int
    Region_name          string
    Reconstruction_grant int


func GetReliefWorkers(deployment_id string) []Reliefworker 

    fmt.Printf("Confirm I have a deployment id:%v\n", deployment_id)

    rows, err := middleware.Db.Query("select deployment_id, community_title, region_name, reconstruction_grant WHERE Deployment_id=$1", brand_id)

    if err != nil 
        return
    

    for rows.Next() 
        reliefworker := Reliefworker
        err = rows.Scan(&deployment_id, &community_title, &region_name, &reconstruction_grant)
        if err != nil 
            return
        
    
    rows.Close()

    return

【问题讨论】:

全局状态不容易阅读。无论如何,可能有一种方法可以在一个查询中加载所有内容,但您可以按顺序加载。我假设您对外键和一对多策略有一些概念。加载救援人员数据,然后加载并解析其所有社区,最后为每个社区加载其区域。这样你就构建了结构体。 我很欣赏这些想法,尽管很抽象。我不完全确定外键如何帮助我,因为我目前正在从一个表中提取所有内容。您是否建议我使用更简单的 SQL 表来建立有助于构建嵌套 go struct 的外键关系?这是可能的,但考虑到你的想法,我需要更多的肉在骨头上。另外,如果它使事情变得更容易,我也不热衷于拥有一个全局状态。我的直觉是,有一种干净的方法可以使用 map 和 slice 函数来做到这一点,只需少量可靠的排序函数。 或许将这个问题重新命名为“Marshalling an SQL result set into an nested Go struct”更合适?如果是这样,其他人可能需要这样做,因为我之前被警告要重新命名问题以使其更贴切。 这可能只是我对使用某些 sql 数据库可以做什么的了解不够。如果您的方法不复制任何数据,那么它可能是最佳方法。如果您想要一种将数据库输出解组为结构的干净方法,您可能应该使用一些 ORM。虽然使用 orm 并不总是答案。据我了解,您应该能够直接修改数据库中的数据,仍然使用嵌套结构而不是关系数据库是不常见的(对我来说)。拥有关系的好处是您没有重复项,并且可以轻松添加和删除项目。 你让我走上了我相信的正确道路。它可能不会很漂亮,但我正在做一些事情 【参考方案1】:

我认为排序很有意义,原始解决方案可能是最有效的:

func GetReliefWorkers(deployment_id string) []Reliefworker 
    // Added sort to query
    q := "select worker_name, community_title, region_name, reconstruction_grant WHERE deployment_id=? ORDER BY community_title"    
    rows, err := middleware.Db.Query(q, deployment_id)
    if err != nil 
        return
    
    defer rows.Close() // So rows get closed even on an error
    c := Community // To keep track of the current community
    cmatrix := [][]string[]string  // Matrix of communities and workers
    communities := []Community // List of communities
    workers := make(map[string]Reliefworker) // Map of workers
    var ccount int // Index of community in lists
    for rows.Next() 
        w := ReliefworkerDeployment_id: deployment_id
        r := Region
        var ctitle string  // For comparison later
        err = rows.Scan(&w.Name, &ctitle, &r.Name, &r.Reconstruction_grant)
        if err != nil 
            return
        
        if ctitle != c.Name 
            communities = append(communities, c)
            c = Community
            c.Name = ctitle
            ccount++
            cmatrix = append(cmatrix, []string)
        
        c.Regions = append(c.Regions, r)
        cmatrix[ccount] = append(cmatrix[ccount], w.Name)
        workers[w.Name] = w
    
    for i, c := range communities 
        for _, id := range cmatrix[i] 
            w := workers[id] // To avoid error 
            w.Communities = append(w.Communities, c)
            workers[id] = w
        
    
    out := []Reliefworker
    for _, w := range workers 
        out = append(out, w)
    
    return out

尽管为社区、区域和工作人员创建单独的表可能更有意义,然后使用 JOIN 查询它们:https://www.w3schools.com/sql/sql_join_inner.asp

更新:既然您只想检索一个Reliefworker,那么这样的方法有用吗?

type ReliefWorker struct 
    Name        string      `json:"name"`
    Communities []Community `json:"community"`


type Community struct 
    Name    string   `json:"name"`
    Regions []Region `json:"regions"`


type Region struct 
    Name                 string `json:"name"`
    Region_id            string `json:"region_id"`
    Reconstruction_grant int    `json:"reconstruction_grant"`
    Currency             string `json:"currency"`


func GetReliefWorkers(deployment_id string) Reliefworker 
    reliefworker := Reliefworker
    communities := make(map[string]Community)
    rows, err := middleware.Db.Query("select name, community_title, region_name, region_id, reconstruction_grant WHERE Deployment_id=$1", deployment_id)
    if err != nil 
        if err == sql.ErrNoRows 
            fmt.Printf("No records for ReliefWorker:%v\n", deployment_id)
        
        panic(err)
    
    defer rows.Close()
    for rows.Next() 
        c := Community
        r := Region
        err = rows.Scan(&reliefworker.Name, &c.Name, &r.Name, &r.Region_id, &r.Reconstruction_grant)
        if err != nil 
            panic(err)
        
        if _, ok := communities[c.Name]; ok 
            c = communities[c.Name]
        
        c.Regions = append(c.Regions, r)
        communities[c.Name] = c
    
    for _, c := range commmunities 
        reliefworker.Communities = append(reliefworker.Communities, c)
    
    return reliefworker

【讨论】:

如果 sql 行包含一个空的社区标题,我相信,会发生一些奇怪的事情,因为如果首先出现,那么 ctitle != c.Name 将返回 false。没有? 这里是communities = append(communities, c),它将在第一次周围的 if 语句为真时附加一个空社区。​​span> 是的,如果没有空白社区标题,它会先附加一个空对象,但这没关系,它只会在最后一个循环中跳过。不过,关于空白标题的好点,我会进行编辑。 另一个版本,在 Absentbird play.golang.org/p/fi8z_0xvkww 的帮助下大大改进,感谢他的审阅和 cmets。 感谢 mh-cbon,我非常感谢您的努力。这很好用,尽管在未使用的行上没有以下行:cmatrix := [][]string[]stringvar ccount int // Index of community in lists【参考方案2】:

好的,我的粗略解决方案不包含@Absentbird 展示的一点点智能,但我是来学习的。

@Absentbird 我喜欢您使用地图和多维数组来保存社区和工人矩阵。我将在周末专注于将这部分作为我的武器库。

我可以接受并调整@Absentbird 的解决方案,一旦我找到解决方案,为什么它会在workers[id].Communities = append(workers[id].Communities, c) 行出现错误“无法分配给 struct field workers[id].Communities in mapcompilerUnaddressableFieldAssign”

首先道歉,因为我必须纠正两件事。首先,我只需要返回 ReliefWorkers(不是 ReliefWorkers 数组)。其次,ReliefWorker 结构体不需要包含 Deployment_id,因为我已经知道了。

我是 Go 新手,因此非常感谢有关我可以做些什么来更好地利用该语言并编写更简洁的代码的反馈。

我的结构和解决方案目前如下:

type ReliefWorker struct 
    Name        string      `json:"name"`
    Communities []Community `json:"community"`


type Community struct 
    Name    string   `json:"name"`
    Regions []Region `json:"regions"`


type Region struct 
    Name      string `json:"name"`
    Region_id string `json:"region_id"`
    Reconstruction_grant        int    `json:"reconstruction_grant"`
    Currency  string `json:"currency"`


type ReliefWorker_community_region struct 
    Name    string
    Community_title string
    Region_name     string
    Reconstruction_grant  int


func GetReliefWorkers(deployment_id string) Reliefworker 

    var reliefworker Reliefworker
    var communitiesOnly []string
    var name string
    var allReliefWorkerData []ReliefWorker_community_region
    rows, err := middleware.Db.Query("select name, community_title, region_name, reconstruction_grant WHERE Deployment_id=$1", deployment_id)

    for rows.Next() 
        reliefWorker_community_region := ReliefWorker_community_region
        err = rows.Scan(&reliefWorker_community_region.Name, &reliefWorker_community_region.Community_title, &reliefWorker_community_region.Region_name, &reliefWorker_community_region.Reconstruction_grant)
        if err != nil 
            panic(err)
        
        name = reliefWorker_community_region.Name
        allReliefWorkerData = append(allReliefWorkerData, reliefWorker_community_region)
        communitiesOnly = append(communitiesOnly, reliefWorker_community_region.Community_title)  //All communities go in here, even duplicates, will will create a unique set later
    
    rows.Close()

    if err != nil 
        if err == sql.ErrNoRows 
            fmt.Printf("No records for ReliefWorker:%v\n", deployment_id)
        
        panic(err)
    

    var unique []string  //Use this to create a unique index of communities

    for _, v := range communitiesOnly 
        skip := false
        for _, u := range unique 
            if v == u 
                skip = true
                break
            
        
        if !skip 
            unique = append(unique, v)
        
    
    fmt.Println(unique)

    reliefworker.Name = name
    var community Community
    var communities []Community

    for _, v := range unique 
        community.Name = v
        communities = append(communities, community)
    


// Go through each record from the database held within allReliefWorkerData and grab every region belonging to a Community, when done append it to Communities and finally append that to ReliefWorker

    for j, u := range communities 
        var regions []Region

        for i, v := range allReliefWorkerData 

            if v.Community_title == u.Name 
                var region Region
                region.Name = v.Region_name
                region.Reconstruction_grant = v.Reconstruction_grant
                regions = append(regions, region)
            
        
        communities[j].Regions = regions

    
    reliefworker.Communities = communities
    return reliefworker

【讨论】:

我相信我已经通过更新解决了我的解决方案中的错误。 如果这一切都进入一个救援人员对象,这行得通吗? play.golang.org/p/Qidlk6_msGb 我在原始答案中编辑了一个改进的变体。

以上是关于过滤和排序 SQL 查询以重新创建嵌套结构的主要内容,如果未能解决你的问题,请参考以下文章

UNION表时如何在Bigquery中重新排列/重新排序嵌套的重复列

如何获取将在 PHPMyAdmin 中重新创建 sql 表的查询

extjs 嵌套数据网格过滤器和重新加载在 viewModel 上不起作用

sql怎么对列重新排序

React页面不会在url查询更改时重新呈现

嵌套可重新排序的列表?