How to LEFT JOIN two tables in Go using squirrel and handle nullable fields

huangapple go评论85阅读模式
英文:

How to LEFT JOIN two tables in Go using squirrel and handle nullable fields

问题

我对Go语言还不太熟悉,但是我可以帮你翻译你的代码和问题。以下是你提供的代码的翻译:

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"

我该如何进行LEFT JOIN,以便在UserLocations中找不到匹配项时忽略嵌套对象?

英文:

I am quite new to Go, and I'm working on a project in Go where I'm using squirrel for SQL queries. I have two tables, User and UserLocation, and I need to do a LEFT JOIN between them. Here are my structs:

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"`
}

The UserSlug field in the UserLocation table is a foreign key field that links to the User table's Slug field.

I'm trying to create a LEFT JOIN query that will return the User data along with any UserLocation data, if available. Here is the output format I need:

If the UserSlug is found in the UserLocation table, return the whole User JSON with a nested object for the location found:

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

However, if the UserSlug is not found in UserLocation, I want the result to not contain the location key at all:

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

I am trying to accomplish this with something like this:

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
}

but I keep getting the following error:

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

How can I do the LEFT JOIN in a way that allows me to ignore the nested object if a match is not found in UserLocations?

答案1

得分: 1

我不了解松鼠,所以我会给你一个答案,当你不想发送空字段时,你可以在结构体的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"`
}

关于你遇到的查询不起作用的问题,是因为你的User结构体中没有id字段,而数据库中有这个字段,它试图在你的结构体中找到这个字段。

所以我的解决方案是,如果你想发送你提到的响应,像这样:

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

你需要做的是不要使用左连接,因为你无法将左连接的结果映射到与数据库查询结果不完全相同的结构体中。所以你需要根据外键在循环中发出单独的查询,像这样:

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 resultLocation.Next() {
    var userLocation UserLocation
    resultLocation.ParseToStruct(&userLocation)
    // 在循环中使用 user.Location = append(user.Location, &userLocation)
    user.Location = &userLocation
  }
  users = append(users, user)
}

return users

虽然我知道这是非常糟糕的方法,但我还有另一种解决方案,假设每个用户只有一个位置:

只需创建一个新的自定义结构体,与连接查询的响应完全相同:

type UserJoinuserLocation struct {
  // 用户详情
  UserId         string      `db:"userId"`
  Slug           string      `db:"slug"`
  Name           string      `db:"name"`
  Label          null.String `db:"label"`

  // 用户位置详情
  UserLocationId string      `db:"userLocationId"`
  UserSlug       string      `db:"user_slug"`
  Latitude       float64     `db:"latitude"`
  Longitude      float64     `db:"longitude"`
}

确保字段是唯一的,如果你想更改字段名,还要在db标签中进行更改,并且不要忘记在SQL查询中为该名称设置别名。然后你可以轻松地将查询结果解析为你的自定义结构体,然后将该结构体映射到你的原始结构体,然后发送响应。

英文:

I don't know about squirrels so will give you one answer that when you don't want to send fields which are empty you can just put omitempty in the json tag of that field in struct like this

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"`
}

Now for the problem you are facing with your query not working is that your User struct does not have field id which is in database and it is trying to find this field in your struct.

So my solution for that is if you want to send the response you have mentioned like this

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

what you have to do is don't do the left join because you can't map the Left join results to the struct which is not identical to database query result so you have to fire separate queries in loop based on the foreign key like this
note: it's not the perfect code and it is just to explain the logic and it may have error(not syntax)

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

Although i know this is very bad way but i have one more solution in case you have single location for single user

Just make a new custome struct like this which is identicle to join query response

 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"`
   }

Make sure you have unique fields and if you want to change the field name then also change it in db tag and don't forget to put an alias for that name in sql query.
and then you can easily parse query response to your custome struct and then map that struct to your original struct and then send the response

huangapple
  • 本文由 发表于 2023年5月13日 03:35:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/76239437.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定