如何在Go中使用squirrel左连接两个表并处理可空字段

fnatzsnv  于 2023-05-20  发布在  Go
关注(0)|答案(1)|浏览(240)

我对Go很陌生,我正在做一个Go项目,在那里我使用松鼠进行SQL查询。我有两个表,User和UserLocation,我需要在它们之间执行LEFT JOIN。以下是我的struct:

type User struct {
    Slug                string               `json:"slug" db:"slug"`
    Name                string               `json:"name" db:"name"`
    Label               null.String          `json:"label" db:"label"`
    *UserLocation
}
type UserLocation struct {
    Id            string      `json:"uuid" db:"id"`
    UserSlug      string      `json:"user_slug" db:"user_slug"`
    Latitude      float64     `json:"lat" db:"latitude"`
    Longitude     float64     `json:"lng" db:"longitude"`
}

UserLocation表中的UserSlug字段是链接到User表的Slug字段的外键字段。
我正在尝试创建一个LEFT JOIN查询,它将返回User数据沿着任何UserLocation数据(如果可用)。下面是我需要的输出格式:
如果在UserLocation表中找到了UserSlug,则返回整个User JSON,并为找到的位置返回一个嵌套对象:

{
    slug: "user_slug",
    name: "user name",
    label: "user label",
    location: {
        id: "some-uuid",
        user_slug: "user_slug",
        latitude: 0.123,
        longitude: 123.0
    }
}

但是,如果在UserLocation中找不到UserSlug,我希望结果根本不包含location键:

{
    slug: "user_slug",
    name: "user name",
    label: "user label"
}

我正试图用这样的东西来实现这一点:

import (
    "context"
    "database/sql"
    "strings"
    "time"

    "github.com/lib/pq"

    sq "github.com/Masterminds/squirrel"
    "github.com/pkg/errors"
)

type User struct {
    Slug                string               `json:"slug" db:"slug"`
    Name                string               `json:"name" db:"name"`
    Label               null.String          `json:"label" db:"label"`
    *UserLocation
}
type UserLocation struct {
Id            string      `json:"uuid" db:"id"`
UserSlug      string      `json:"user_slug" db:"user_slug"`
Latitude      float64     `json:"lat" db:"latitude"`
Longitude     float64     `json:"lng" db:"longitude"`
}

type UserListResponse struct {
    Users []User `json:"users"`
}

func (p *Postgres) ListUsers(
    ctx context.Context,
    r UserListRequest,
) (UserListResponse, error) {
    var response UserListResponse
    selectColumns := append(tableUserColumns, tableUserLocationColumns)

    qb := sq.Select(selectColumns...).
        From(tableUser)

    qb = qb.LeftJoin(tableUserLocation + " ON " +
        tableColumn(tableUser, columnUserSlug) + " = " + (tableColumn(tableUserLocation, columnUserLocationSlug)))

    q, args, err := qb.
        ToSql()

    if err != nil {
        return response, errors.Wrap(err, "select all query q parsing")
    }

    err = p.db.SelectContext(ctx, &Users, q, args...)
    if err != nil {
        return response, errors.Wrap(err, "select all query execution")
    }

    return storyapi.UserListResponse{
        Users:         Users
    }, nil
}

但我一直得到以下错误:

"select all query execution: missing destination name id in *[]User"

如果在UserLocations中没有找到匹配项,我如何以允许我忽略嵌套对象的方式执行LEFT JOIN?

r55awzrz

r55awzrz1#

我不知道松鼠,所以我会给予你一个答案,当你不想发送空字段时,你可以像这样在结构体中的那个字段的JSON标记中放置omitempty

type User struct {
  Slug                string               `json:"slug" db:"slug"`
  Name                string               `json:"name" db:"name"`
  Label               null.String          `json:"label" db:"label"`
  Location            *UserLocation        `json:"location,omitempty"`
}

type UserLocation struct {
  Id            string      `json:"uuid" db:"id"`
  UserSlug      string      `json:"user_slug" db:"user_slug"`
  Latitude      float64     `json:"lat" db:"latitude"`
  Longitude     float64     `json:"lng" db:"longitude"`
}

现在你面临的问题是你的查询不工作,你的用户结构没有字段id,这是在数据库中,它试图在你的结构中找到这个字段。
所以我的解决方案是如果你想发送你提到的这样的响应

{
  slug: "user_slug",
  name: "user name",
  label: "user label",
  location: {
      id: "some-uuid",
      user_slug: "user_slug",
      latitude: 0.123,
      longitude: 123.0
  }
}

你要做的是不要做左连接,因为你不能将左连接结果Map到与数据库查询结果不同的结构体,所以你必须基于外键在循环中触发单独的查询,如下所示:这不是完美的代码,它只是解释逻辑,它可能有错误(不是语法)

var users []User
resultUser:=db.Query("Select * from user_table")
for resultUser.Next() {
  var user User
  _:=resultUser.ParseToStruct(&user)
  resultLocation:=db.Query("Select * From user_location_table where user_slug=?",user.Slug)
  //if you have multiple locations do it in loop and also change struct accordingly to handle array of locations
  if resultLocation.Next(){
      var userLocation UserLocation
      resultLocation.ParseToStruct(&userLocation)
      //in case of loop user.Location=append(user.Location,&userLocation)
      user.Location=&userLocation
  }
  users=append(users,user)
}

return users

虽然我知道这是非常糟糕的方式,但我有一个解决方案的情况下,你有一个单一的位置为单一用户
只需创建一个新的custome结构体,就像这样,它可以连接查询响应

type UserJoinuserLocation struct{
      //User Details
      UserId              string               `db:"userId"`
      Slug                string               `db:"slug"`
      Name                string               `db:"name"`
      Label               null.String          `db:"label"`
      
      //User Location Details
      userLocationId string      `db:"userLocationId"`
      UserSlug       string      `db:"user_slug"`
      Latitude       float64     `db:"latitude"`
      Longitude      float64     `db:"longitude"`
   }

确保你有唯一的字段,如果你想改变字段名,然后也改变它在db标签,不要忘记把一个别名的名称在sql查询。然后你可以很容易地解析查询响应到你的custome结构,然后将该结构Map到你的原始结构,然后发送响应

相关问题