【问题标题】:How do I handle nil return values from database?如何处理来自数据库的 nil 返回值?
【发布时间】:2015-02-21 06:20:56
【问题描述】:

我正在编写一个基本程序来从数据库表中读取值并在表中打印。桌子上有一个古老的程序。该行中的某些字段是可选的,当我尝试将它们作为字符串读取时,出现以下错误:

panic: sql: Scan error on column index 2: unsupported driver -> Scan pair: <nil> -> *string

在阅读其他类似问题的问题后,我想出了以下代码来处理 nil 值。该方法在实践中效果很好。我得到纯文本和空字符串的值,而不是 nil 值。

但是,我有两个担忧:

  1. 这看起来效率不高。我需要处理 25 多个这样的字段,这意味着我将它们中的每一个都读取为字节并转换为字符串。太多的函数调用和转换。两个结构来处理数据等等......
  2. 代码看起来很难看。它已经看起来很复杂,有 2 个字段,当我到 25+ 时变得不可读

我做错了吗?是否有更好/更清洁/高效/惯用的 golang 方式从数据库中读取值?

我很难相信像 Go 这样的现代语言不会优雅地处理数据库返回。

提前致谢!

代码 sn-p:

// DB read format
type udInfoBytes struct {
  id                     []byte
  state                  []byte
}

// output format
type udInfo struct {
  id                     string
  state                  string
}

func CToGoString(c []byte) string {
  n := -1
  for i, b := range c {
    if b == 0 {
      break
    }
    n = i
  }
  return string(c[:n+1])
}

func dbBytesToString(in udInfoBytes) udInfo {

  var out udInfo
  var s string
  var t int

  out.id = CToGoString(in.id)
  out.state = stateName(in.state)
  return out
}

func GetInfo(ud string) udInfo {

  db := getFileHandle()
  q := fmt.Sprintf("SELECT id,state FROM Mytable WHERE id='%s' ", ud)

  rows, err := db.Query(q)
  if err != nil {
    log.Fatal(err)
  }
  defer rows.Close()
  ret := udInfo{}
  r := udInfoBytes{}
  for rows.Next() {
    err := rows.Scan(&r.id, &r.state)

    if err != nil {
      log.Println(err)
    }
    break
  }
  err = rows.Err()
  if err != nil {
    log.Fatal(err)
  }

  ret = dbBytesToString(r)
  defer db.Close()
  return ret
}

编辑:

我想要类似下面的东西,我不必担心处理 NULL 并自动将它们读取为空字符串。

// output format
type udInfo struct {
  id                     string
  state                  string
}

func GetInfo(ud string) udInfo {

  db := getFileHandle()
  q := fmt.Sprintf("SELECT id,state FROM Mytable WHERE id='%s' ", ud)

  rows, err := db.Query(q)
  if err != nil {
    log.Fatal(err)
  }
  defer rows.Close()
  r := udInfo{}

  for rows.Next() {
    err := rows.Scan(&r.id, &r.state)

    if err != nil {
      log.Println(err)
    }
    break
  }
  err = rows.Err()
  if err != nil {
    log.Fatal(err)
  }

  defer db.Close()
  return r
}

【问题讨论】:

  • 感谢您的回复。我看到大多数解决方案都建议使用 sql.NullString 或数据库特定库。我希望有一些通用的东西可以在“database/sql”上运行。暂时我将使用 sql.NullString 解决方案,因为我必须通过 ODBC 连接到远程 sqlserver 数据库(我知道,很痛苦)。

标签: database go


【解决方案1】:

有不同的类型来处理来自数据库的null 值,例如sql.NullBoolsql.NullFloat64 等。

例如:

 var s sql.NullString
 err := db.QueryRow("SELECT name FROM foo WHERE id=?", id).Scan(&s)
 ...
 if s.Valid {
    // use s.String
 } else {
    // NULL value
 }

【讨论】:

  • 这仍然不能阻止我从一种类型复制到另一种类型的麻烦。在我看来,对于 25+ 个值中的每一个,我必须从 NullString 类型复制到 String 类型。虽然,它确实帮助我摆脱了这些功能,这是受欢迎的。所以谢谢你。
  • 是的,您必须处理 Null 值。在您的特定情况下,您可以为此编写一些包装器。此外,您可能会发现有用的 github.com/jmoiron/sqlx 包,它可以简化您的任务。
【解决方案2】:

go 的 database/sql 包句柄类型指针。

package main

import (
    "database/sql"
    "fmt"
    _ "github.com/mattn/go-sqlite3"
    "log"
)

func main() {
    db, err := sql.Open("sqlite3", ":memory:")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    _, err = db.Exec("create table foo(id integer primary key, value text)")
    if err != nil {
        log.Fatal(err)
    }
    _, err = db.Exec("insert into foo(value) values(null)")
    if err != nil {
        log.Fatal(err)
    }
    _, err = db.Exec("insert into foo(value) values('bar')")
    if err != nil {
        log.Fatal(err)
    }
    rows, err := db.Query("select id, value from foo")
    if err != nil {
        log.Fatal(err)
    }
    for rows.Next() {
        var id int
        var value *string
        err = rows.Scan(&id, &value)
        if err != nil {
            log.Fatal(err)
        }
        if value != nil {
            fmt.Println(id, *value)
        } else {
            fmt.Println(id, value)
        }
    }
}

你应该得到如下所示:

1 <nil>
2 bar

【讨论】:

    【解决方案3】:

    另一种解决方案是使用 COALESCE 函数在 SQL 语句本身中处理此问题(尽管并非所有数据库都支持此功能)。

    例如,您可以改用:

    q := fmt.Sprintf("SELECT id,COALESCE(state, '') as state FROM Mytable WHERE id='%s' ", ud)
    

    如果它在数据库中存储为 NULL,这将有效地为“状态”提供一个空字符串的默认值。

    【讨论】:

      【解决方案4】:

      处理这些空值的两种方法:

      使用 sql.NullString

      if value.Valid { 
         return value.String
      } 
      

      使用*字符串

      if value != nil {
         return *value
      }
      

      https://medium.com/@raymondhartoyo/one-simple-way-to-handle-null-database-value-in-golang-86437ec75089

      【讨论】:

        【解决方案5】:

        我已经开始使用 MyMySql 驱动程序,因为它使用了比 std 库更好的接口。

        https://github.com/ziutek/mymysql

        然后,我将数据库查询包装成简单易用的函数。这是一个这样的功能:

        import "github.com/ziutek/mymysql/mysql"
        import _ "github.com/ziutek/mymysql/native"
        
        // Execute a prepared statement expecting multiple results.
        func Query(sql string, params ...interface{}) (rows []mysql.Row, err error) {
            statement, err := db.Prepare(sql)
            if err != nil {
                return
            }
            result, err := statement.Run(params...)
            if err != nil {
                return
            }
            rows, err = result.GetRows()
            return
        }
        

        使用它就像这个sn-p一样简单:

        rows, err := Query("SELECT * FROM table WHERE column = ?", param)
        
        for _, row := range rows {
            column1 = row.Str(0)
            column2 = row.Int(1)
            column3 = row.Bool(2)
            column4 = row.Date(3)
            // etc...
        }
        

        请注意用于强制转换为特定值的漂亮行方法。 Null 由库处理,规则记录在此处:

        https://github.com/ziutek/mymysql/blob/master/mysql/row.go

        【讨论】:

          猜你喜欢
          • 2013-11-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-05-05
          • 1970-01-01
          • 2023-03-14
          • 1970-01-01
          • 2018-09-11
          相关资源
          最近更新 更多