在golang中扫描SQL中的NULL值

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

Scan SQL NULL values in golang

问题

使用标准的database/sql Row.Scan()方法时,我在处理行中的空值时遇到了问题。如果不考虑行中可能存在的空值,可能会出现扫描错误,例如<nil> -> *string。这在使用LEFT JOIN查询或定义不完整的表缺少NO NULL列约束时非常常见。

可以使用sql.NullXXX类型(例如sql.NullInt64)来扫描行中可能的空值,然后检查该值是否为.Valid。然而,这些类型不实现JSON序列化,因此需要更多的逻辑和编码。

为了解决这个问题,是在SQL查询中使用COALESCE()函数来处理列值,还是在Golang代码中进行额外的编码,哪种方法更好呢?

英文:

 Using standard database/sql Row.Scan() I have a problem with null values in the row. Without considering possible null values in a row, I can get scan errors like <nil> -> *string. This is quite common using LEFT JOIN queries or weak defined tables missing NO NULL column constraints.

There are sql.NullXXX types (e.g. sql.NullInt64) which can be used to scan possible null value from a row, and then check if the value is .Valid. However, these types don’t implement JSON marshalling, and those more logic and coding is required.

To solve this, is it better approach to COALESCE() column values in SQL query or do some extra coding in golang code?

答案1

得分: 9

你可以使用sql包提供的类型别名(如NullInt64、NullString等)来处理空值。使用别名的好处有两个:一是可以扫描空值并将其获取到golang结构中,二是可以将该结构序列化为JSON格式。

请看下面的示例代码:

// NullInt64是sql.NullInt64数据类型的别名
type NullInt64 sql.NullInt64

// Scan实现了NullInt64的Scanner接口
func (ni *NullInt64) Scan(value interface{}) error {
    var i sql.NullInt64
    if err := i.Scan(value); err != nil {
        return err
    }
    // 如果值为nil,则将Valid设置为false
    if reflect.TypeOf(value) == nil {
        *ni = NullInt64{i.Int64, false}
    } else {
        *ni = NullInt64{i.Int64, true}
    }
    return nil
}

// MarshalJSON用于NullInt64
func (ni *NullInt64) MarshalJSON() ([]byte, error) {
    if !ni.Valid {
        return []byte("null"), nil
    }
    return json.Marshal(ni.Int64)
}

请参考这篇文章,它对于处理golang中的空值以及如何在JSON中使用它非常有帮助。

英文:

You can use aliases for types provided by sql package such as (NullInt64, NullString etc...). There are two advantages of using that, one you can scan null values and can get in golang structure and second you can marshal that struct in JSON.

Please, look at the sample code:

// NullInt64 is an alias for sql.NullInt64 data type
type NullInt64 sql.NullInt64

// Scan implements the Scanner interface for NullInt64
func (ni *NullInt64) Scan(value interface{}) error {
    var i sql.NullInt64
    if err := i.Scan(value); err != nil {
        return err
    }
    // if nil the make Valid false
    if reflect.TypeOf(value) == nil {
        *ni = NullInt64{i.Int64, false}
    } else {
        *ni = NullInt64{i.Int64, true}
    }
    return nil
}

// MarshalJSON for NullInt64
func (ni *NullInt64) MarshalJSON() ([]byte, error) {
    if !ni.Valid {
        return []byte("null"), nil
    }
    return json.Marshal(ni.Int64)
}

Please, have a look at this article, it would be very helpful about handling null values in golang and how to use it in JSON.

答案2

得分: 4

作为一种选择,您可以实现自定义数据类型,以匹配JSON Marshaler接口。之后,您将能够使用常规标签对您的结构进行编组。

请参考以下示例:

type UserTitleType sql.NullString

func (s UserTitleType) MarshalJSON() ([]byte, error) {
    if s.Valid {
        return json.Marshal(s.String)
    }
    return jsonNull, nil
}

type User struct {
    Id    int64         `json:"id"`
    Title UserTitleType `json:"title"`
}
英文:

As an option you can implement your custom data type that would match the JSON Marshaler interface. After that you would be able to marshal your struct using regular tags.

Check the example:

type UserTitleType sql.NullString

func (s UserTitleType) MarshalJSON() ([]byte, error) {
    if s.Valid {
        return json.Marshal(s.String)
    }
    return jsonNull, nil
}

type User struct {
    Id    int64         `json:"id"`
    Title UserTitleType `json:"title"`
}

答案3

得分: 0

// Scan实现了Scanner接口。
func (n *NullInt64) Scan(value interface{}) error {
var ok bool
if value == nil {
n.Int64, n.Valid = 0, false
return nil
}

n.Valid = true
n.Int64, ok = value.(int64)
if !ok {
    return errors.New("转换错误")
}
return nil

}

// Value实现了driver Valuer接口。
func (n NullInt64) Value() (driver.Value, error) {
if !n.Valid {
return nil, nil
}
return n.Int64, nil
}

英文:
// Scan implements the Scanner interface.
func (n *NullInt64) Scan(value interface{}) error {
	var ok bool
	if value == nil {
		n.Int64, n.Valid = 0, false
		return nil
	}
	
	n.Valid = true
	n.Int64, ok = value.(int64)
	if !ok {
		return errors.New("convert error")
	}
	return nil
}

// Value implements the driver Valuer interface.
func (n NullInt64) Value() (driver.Value, error) {
	if !n.Valid {
		return nil, nil
	}
	return n.Int64, nil
}

答案4

得分: 0

我实现了这个方法,它允许你扫描行,其中空值被扫描为零值,不过你需要将rows.Scan(args...)替换为RowScan(rows, args...)
此外,这个实现只支持字符串、整数和布尔类型。如果你需要其他类型,可以从sql.Nullxxx中添加。

func RowScan(row *sql.Rows, args ...interface{}) error {

	scanArgs := make([]interface{}, len(args))

	for i, arg := range args {

		switch reflect.ValueOf(arg).Elem().Kind() {

		case reflect.Int64, reflect.Int:
			scanArgs[i] = &sql.NullInt64{}
		case reflect.Bool:
			scanArgs[i] = &sql.NullBool{}
		case reflect.String:
			scanArgs[i] = &sql.NullString{}
		default:
			return errors.New("不支持的类型")
		}
	}
	err := row.Scan(scanArgs...)

	if err != nil {
		return err
	}

	for i, sArg := range scanArgs {

		switch reflect.ValueOf(sArg).Elem().Field(0).Kind() {

		case reflect.Int64, reflect.Int:
			reflect.ValueOf(args[i]).Elem().SetInt(scanArgs[i].(*sql.NullInt64).Int64)
		case reflect.Bool:
			reflect.ValueOf(args[i]).Elem().SetBool(scanArgs[i].(*sql.NullBool).Bool)
		case reflect.String:
			reflect.ValueOf(args[i]).Elem().SetString(scanArgs[i].(*sql.NullString).String)
		default:
			return errors.New("不支持的类型")
		}
	}
	return nil
}
英文:

I implemented this approach that allows you to scan the rows, where nil values are scanned as zero-values, although you will have to replace rows.Scan(args...) with RowScan(rows, args...).
Also, this implementation only supports string, int, and bool. You may add additional types if you need them from sql.Nullxxx

func RowScan(row *sql.Rows, args ...interface{}) error {

	scanArgs := make([]interface{}, len(args))

	for i, arg := range args {

		switch reflect.ValueOf(arg).Elem().Kind() {

		case reflect.Int64, reflect.Int:
			scanArgs[i] = &sql.NullInt64{}
		case reflect.Bool:
			scanArgs[i] = &sql.NullBool{}
		case reflect.String:
			scanArgs[i] = &sql.NullString{}
		default:
			return errors.New("unsupported type")
		}
	}
	err := row.Scan(scanArgs...)

	if err != nil {
		return err
	}

	for i, sArg := range scanArgs {

		switch reflect.ValueOf(sArg).Elem().Field(0).Kind() {

		case reflect.Int64, reflect.Int:
			reflect.ValueOf(args[i]).Elem().SetInt(scanArgs[i].(*sql.NullInt64).Int64)
		case reflect.Bool:
			reflect.ValueOf(args[i]).Elem().SetBool(scanArgs[i].(*sql.NullBool).Bool)
		case reflect.String:
			reflect.ValueOf(args[i]).Elem().SetString(scanArgs[i].(*sql.NullString).String)
		default:
			return errors.New("unsupported type")
		}
	}
	return nil
}

huangapple
  • 本文由 发表于 2017年6月21日 16:03:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/44670212.html
匿名

发表评论

匿名网友

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

确定