将空字符串插入 SQL 时,可以使用 NULL 值代替。

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

Golang Insert NULL into sql instead of empty string

问题

我正在尝试使用golang向mysql数据库插入数据。在值为空字符串的情况下,我希望插入null。如何修改以下代码以插入null而不是空字符串?谢谢。

_, err := m.Db.Exec(`INSERT INTO 
                         visitor_events
                         (type, 
                          info, 
                          url_path, 
                          visitor_id, 
                          created_at, 
                          domain)
         			      VALUES
                          (?, ?, ?, ?, ?, ?)`,
                          m.SaveEventType(ve), ve.EventInfo, m.SaveURLPath(ve.UrlPath), ve.VisitorId, time.Now().UTC(), ve.Domain)
英文:

I'm trying to insert data into a mysql database using golang. In the case where my value takes on an empty string, I would want to insert a null. How can I adjust the following to insert nulls instead of empty string? Thanks.

_, err := m.Db.Exec(`INSERT INTO 
                         visitor_events
                         (type, 
                          info, 
                          url_path, 
                          visitor_id, 
                          created_at, 
                          domain)
         			      VALUES
                          (?, ?, ?, ?, ?, ?)`,
                          m.SaveEventType(ve), ve.EventInfo, m.SaveURLPath(ve.UrlPath), ve.VisitorId, time.Now().UTC(), ve.Domain)

答案1

得分: 84

在我的代码中,我有一个将字符串转换为 sql.NullString 的函数。

func NewNullString(s string) sql.NullString {
    if len(s) == 0 {
        return sql.NullString{}
    }
    return sql.NullString{
         String: s,
         Valid: true,
    }
}

然后,每当我使用 Exec 函数时,我会使用 NewNullString 函数将可能为 NULL 的字符串包装在数据库中。

db.Exec(`
  insert into
      users (first_name, last_name, email)
      values (?,?,?)`,
  firstName,
  lastName,
  NewNullString(email),
)
英文:

In my code I have a function that converts a string to sql.NullString

func NewNullString(s string) sql.NullString {
    if len(s) == 0 {
        return sql.NullString{}
    }
    return sql.NullString{
         String: s,
         Valid: true,
    }
}

Then whenever I am using Exec I wrap my strings that could be NULL in the DB with the NewNullString function.

db.Exec(`
  insert into
      users first_name, last_name, email
      values (?,?,?)`,
  firstName,
  lastName,
  NewNullString(email),
)

答案2

得分: 19

database/sql包中有一个NullString类型(文档),专门用于处理这种情况。

在数据库中,可以使用sql.NullString来替代字符串,以使其可为空。

你也可以在代码中使用*string来达到同样的效果。

无论哪种情况,问题都在于将可为空的字符串映射到不可为空的字符串,或者从不可为空的字符串映射到可为空的字符串。空字符串在技术上是一个值,所以如果你决定将空字符串转换为nil,你几乎总是需要像这样做:

nullableS := &s
if s == "" {
  nullableS = nil
}

另一种方法是在整个应用程序的模型中,使用*string而不是string

在数据库中,我一直采用空字符串和null等效的方法,将空字符串存储在数据库中,并使大多数列不可为空。

英文:

The database/sql package has a NullString type (docs) for just this situation.

Basically just use sql.NullString in place of strings where you want them to be nullable in db.

You could also use a *string in your code to the same effect.

The problem in either case is in mapping to/from a nullable string to a non-nullable string. The empty string is technically a value, so you will almost always have to do something like this if you decide empty string should be translated to nil:

nullableS := &s
if s == "" {
  nullableS = nil
}

The alternative would be to just use *string instead of string in your models throughout your app.

In databases, I have been taking the approach that empty string and null are equivalent, and just storing empty sting in the db, and making most columns non-nullable.

答案3

得分: 4

你可以在SQL查询中使用NULLIF函数。

NULLIF(?, ''):当你尝试插入空字符串时,它将返回NULL而不是空字符串。

了解更多关于NULLIF的信息:链接

英文:

You can also use NULLIF function in your SQL query.

NULLIF(?, ''): will return NULL instead of an empty string when you try to insert an empty string.

Learn more about NULLIF: link

答案4

得分: 0

如果你想避免创建N个NewNullType函数,最好使用pgx和pgtypes以及Value()函数:

https://github.com/jackc/pgtype

https://github.com/jackc/pgtype/blob/master/text.go

示例(未经测试)

type User struct {
    email      pgtype.Text `json:"email"`
    firstName  pgtype.Text `json:"first_name"`
}

func InsertUser(u User) error {
    // --> 从u中获取SQL值
    var err error
    email, err := u.email.Value() // 参考 https://github.com/jackc/pgtype/blob/4db2a33562c6d2d38da9dbe9b8e29f2d4487cc5b/text.go#L174
    if err != nil {
        return err
    }
    firstName, err := d.firstName.Value()
    if err != nil {
        return err
    }
    // ...

    sql := `INSERT INTO users (email, first_name) VALUES ($1, $2)`
    conn, err := pgx.Connect(ctx, "DATABASE_URL")
    defer conn.Close(ctx)
    tx, err := conn.Begin()
    defer tx.Rollback(ctx)
    // --> 使用之前获取的SQL值执行查询
    _, err = tx.Exec(ctx, sql, email, firstName)
    // 处理错误
    }
    err = tx.Commit(ctx)
    // 处理错误
    return nil
}
英文:

If you want to avoid creation N NewNullType functions, you'd better off using pgx & pgtypes along with the Value() func:

https://github.com/jackc/pgtype

https://github.com/jackc/pgtype/blob/master/text.go

example (untested)

type User struct {
    email   pgtype.Text `json:"email"`
    firstName  pgtype.Text `json:"first_name"`
}

func InsertUser(u User) error {
    // --> get SQL values from u
    var err error
    email, err := u.email.Value() // see https://github.com/jackc/pgtype/blob/4db2a33562c6d2d38da9dbe9b8e29f2d4487cc5b/text.go#L174
    if err != nil {
        return err
    }
    firstName, err := d.firstName.Value()
    if err != nil {
        return err
    }
    // ...

    sql := `INSERT INTO users (email, first_name) VALUES ($1, $2)`
    conn, err := pgx.Connect(ctx, "DATABASE_URL")
    defer conn.Close(ctx)
    tx, err := conn.Begin()
    defer tx.Rollback(ctx)
    // --> exec your query using the SQL values your get earlier
    _, err = tx.Exec(ctx, sql, email, firstName)
    // handle error
    }
    err = tx.Commit(ctx)
    // handle error
    return nil
}

答案5

得分: 0

示例,添加NULL值:

/*示例:
  ParamReplacingMode = 0  // 不替换参数
  isql:=InitSql(db,"SELECT * FROM table WHERE price+vat>:Sum and country=:C") // 通过Oracle
  AddParam(isql,":C", "USA")                                                  // AddParams的顺序不受限制,可以以任意顺序添加参数
  AddParam(isql,":Sum", 130.5)
  res,err:= SqlQuery(isql)                                                    //结果:db.Query("SELECT * FROM table WHERE price+vat>:Sum and country=:C", 130.5,"USA")
or
  ParamReplacingMode = 1  // MySQL  - 将参数替换为"?"
  isql:=InitSql(db,"SELECT * FROM table WHERE price+vat>:Sum and country=:C") // 通过Oracle转换为MySQL
  AddParam(isql,":C", "USA")                                                  // AddParams的顺序不受限制,可以以任意顺序添加参数
  AddParam(isql,":Sum", 130.5)
  res,err:= SqlQuery(isql)                                                    //结果:db.Query("SELECT * FROM table WHERE price+vat>? and country=?", 130.5,"USA") //将参数替换为"?"
or
  ParamReplacingMode = 0 // 不替换参数
  isql:=InitSql(db,"SELECT * FROM table WHERE price+vat>$1 and country=$2")   // 通过Postgre
  AddParam(isql,"$1", 130.5)
  AddParam(isql,"$2", "USA")                                                  // AddParams的顺序不受限制,可以以任意顺序添加参数
  res,err:= SqlQuery(isql)                                                    //结果:db.Query("SELECT * FROM table WHERE price+vat>$1 and country=$2", 130.5,"USA")
or
  ParamReplacingMode = 2 // 将Oracle转换为Postgre,将参数替换为"<$Number>"
  isql:=InitSql(db,"SELECT * FROM table WHERE price+vat>:Sum and country=:C") // 通过Oracle转换为Postgre
  AddParam(isql,":C","USA")
  AddParam(isql,":Sum",130.5)
  res,err:= SqlQuery(isql)                                                    //结果:db.Query("SELECT * FROM table WHERE price+vat>$1 and country=$2", 130.5,"USA")

  SqlExec()与SqlQuery()类似,但调用db.Exec(...)
  
  示例,添加NULL值:
  isql:=InitSql(db,"INSERT INTO table (id, name) VALUES (:ID,:Name)")
  AddParam(isql, ":ID", 1)
  AddParam(isql, ":Name", nil)
  res,err:= SqlExec(isql)
*/

type (
	TisqlMode int32
	TisqlAt   struct {
		ParamName string
		Pos       int
		ParamVal  any
	}

	Tisql struct {
		Sql       string
		ResultSql string
		DB        *sql.DB
		Params    map[string]any
		At        []TisqlAt
	}
)

const (
	Oracle  TisqlMode = iota //0,不替换参数
	Mysql                    //1,"SELECT * FROM table WHERE price+vat>:Sum and country=:C" -> db.Query("SELECT * FROM table WHERE price+vat>? and country=?", 130.5,"USA")
	Postgre                  //2,"SELECT * FROM table WHERE price+vat>:Sum and country=:C" -> db.Query("SELECT * FROM table WHERE price+vat>$1 and country=$2", 130.5,"USA")
)

func (s TisqlMode) String() string {
	switch s {
	case Oracle:
		return "Oracle" // 不替换参数
	case Mysql:
		return "Mysql"
	case Postgre:
		return "Postgre"
	}
	return "unknown"
}

var ParamReplacingMode TisqlMode = -1 //-1 = 未知,0 = 不替换参数,1 = 转换为MySQL,2 = 转换为Postgre

func indexAt(pStr, pSubStr string, pos int) int { //从位置开始索引
	if pos >= len(pStr) {
		return -1
	}
	if pos < 0 {
		pos = 0
	}
	idx := strings.Index(pStr[pos:], pSubStr)
	if idx > -1 {
		idx += pos
	}
	return idx
}

func InitSql(db *sql.DB, sql string) *Tisql {
	if ParamReplacingMode < 0 { // 未知
		_, err := db.Exec("?")
		if err != nil {
			s := strings.ToLower(fmt.Sprint(err))
			if indexAt(s, "mysql", 0) > 0 {
				ParamReplacingMode = 1
			} else {
				ParamReplacingMode = 0
			}
		}
	}
	var isql Tisql
	isql.Sql = sql
	isql.DB = db
	isql.Params = make(map[string]any)
	return &isql
}

func AddParam(isql *Tisql, pParam string, pValue any) {
	isql.Params[pParam] = pValue
}

func paramOrder(isql *Tisql, pCheckParamCount bool) error {
	var at TisqlAt
	isql.ResultSql = isql.Sql
	t := ""
	b := strings.ToLower(isql.Sql) + " "
	mMode := ParamReplacingMode
	var p, p1, p2 int
	for name, v := range isql.Params {
		p1 = 0
		for p1 >= 0 {
			p = indexAt(b, strings.ToLower(name), p1)
			if p < 0 {
				p1 = -1
				continue
			} else {
				p2 = p + len(name)
				t = b[p2 : p2+1] //参数后的字符
				if indexAt(" :,;!?%$<>^*+-/()[]{}=|'`\r\n\t", t, 0) < 0 {
					p1 = p + 1
					continue
				}
				p1 = -1
			}
		}
		if p >= 0 {
			at.Pos = p
			at.ParamVal = v
			at.ParamName = name
			isql.At = append(isql.At, at)
		}
	}
	if pCheckParamCount && len(isql.At) != len(isql.Params) {
		return fmt.Errorf("参数数量不同 %d / %d", len(isql.At), len(isql.Params))
	}
	if len(isql.At) > 1 {
		sort.Slice(isql.At,
			func(i, j int) bool {
				return isql.At[i].Pos < isql.At[j].Pos
			})
	}
	mLen := len(isql.Sql)
	switch mMode {
	case 1: //转换为MySQL
		{
			p1, p2, s := 0, 0, ""
			for _, at := range isql.At {
				p2 = at.Pos
				if p2 >= 0 && p2 <= mLen {
					if p2 > p1 {
						s += isql.Sql[p1:p2] + "?"
					}
					p1 = p2 + len(at.ParamName)
				}
			}
			if p1 < len(isql.Sql) {
				s += isql.Sql[p1:len(isql.Sql)]
			}
			isql.ResultSql = s
		}
	case 2: //转换为Postgre
		{
			p1, p2, s := 0, 0, ""
			for i, at := range isql.At {
				p2 = at.Pos
				if p2 >= 0 && p2 <= mLen {
					if p2 > p1 {
						s += isql.Sql[p1:p2] + "$" + fmt.Sprint(i+1)
					}
					p1 = p2 + len(at.ParamName)
				}
			}
			if p1 < len(isql.Sql) {
				s += isql.Sql[p1:len(isql.Sql)]
			}
			isql.ResultSql = s
		}
	}
	return nil
}

func ParamsStr(isql *Tisql) string {
	s := ""
	for i, at := range isql.At {
		s += "[" + fmt.Sprint(i+1) + ". " + at.ParamName + "=\"" + fmt.Sprint(at.ParamVal) + "\"]"
	}
	return s
}
func SqlStr(isql *Tisql) string {
	s := "SQL:[" + isql.ResultSql + "]"
	if len(isql.At) > 0 {
		s += " Params:" + ParamsStr(isql)
	}
	return s
}

func SqlExec(isql *Tisql, opt ...bool) (sql.Result, error) {
	checkParamCount := false
	if len(opt) > 0 {
		checkParamCount = opt[0]
	}
	err := paramOrder(isql, checkParamCount)
	if err != nil {
		return nil, err
	}
	mLen := len(isql.At)
	mVal := make([]any, mLen)
	for i := range mVal {
		mVal[i] = isql.At[i].ParamVal
	}
	return isql.DB.Exec(isql.ResultSql, mVal...)
}

func SqlQuery(isql *Tisql, opt ...bool) (*sql.Rows, error) {
	checkParamCount := false
	if len(opt) > 0 {
		checkParamCount = opt[0]
	}
	err := paramOrder(isql, checkParamCount)
	if err != nil {
		return nil, err
	}
	mLen := len(isql.At)
	mVal := make([]any, mLen)
	for i := range mVal {
		mVal[i] = isql.At[i].ParamVal
	}
	return isql.DB.Query(isql.ResultSql, mVal...)
}
英文:

Example , add NULL value :

/*Exaples:
ParamReplacingMode = 0  // no replacing params
isql:=InitSql(db,&quot;SELECT * FROM table WHERE price+vat&gt;:Sum and country=:C&quot;) // by Oracle
AddParam(isql,&quot;:C&quot;, &quot;USA&quot;)                                                  // the order for AddParams is not bound, you can add params any order
AddParam(isql,&quot;:Sum&quot;, 130.5)
res,err:= SqlQuery(isql)                                                    //result: db.Query(&quot;SELECT * FROM table WHERE price+vat&gt;:Sum and country=:C&quot;, 130.5,&quot;USA&quot;)
or
ParamReplacingMode = 1  // MySQL  - replacing params to &quot;?&quot;
isql:=InitSql(db,&quot;SELECT * FROM table WHERE price+vat&gt;:Sum and country=:C&quot;) // by Oracle convert to Mysql
AddParam(isql,&quot;:C&quot;, &quot;USA&quot;)                                                  // the order for AddParams is not bound, you can add params any order
AddParam(isql,&quot;:Sum&quot;, 130.5)
res,err:= SqlQuery(isql)                                                    //result: db.Query(&quot;SELECT * FROM table WHERE price+vat&gt;? and country=?&quot;, 130.5,&quot;USA&quot;) //replacing params to &quot;?&quot;
or
ParamReplacingMode = 0 //no replacing params
isql:=InitSql(db,&quot;SELECT * FROM table WHERE price+vat&gt;$1 and country=$2&quot;)   // by Postgre
AddParam(isql,&quot;$1&quot;, 130.5)
AddParam(isql,&quot;$2&quot;, &quot;USA&quot;)                                                  // the order for AddParams is not bound, you can add params any order
res,err:= SqlQuery(isql)                                                    //result: db.Query(&quot;SELECT * FROM table WHERE price+vat&gt;$1 and country=$2&quot;, 130.5,&quot;USA&quot;)
or
ParamReplacingMode = 2 // mode Oracle to Postgre, replacing params to &lt;$Number&gt;
isql:=InitSql(db,&quot;SELECT * FROM table WHERE price+vat&gt;:Sum and country=:C&quot;) // by Oracle convert to Postgre
AddParam(isql,&quot;:C&quot;,&quot;USA&quot;)
AddParam(isql,&quot;:Sum&quot;,130.5)
res,err:= SqlQuery(isql)                                                    //result: db.Query(&quot;SELECT * FROM table WHERE price+vat&gt;$1 and country=$2&quot;, 130.5,&quot;USA&quot;)
SqlExec() is similar as SqlQuery(), but call db.Exec(...)
Example , add NULL value:
isql:=InitSql(db,&quot;INSERT INTO table (id, name) VALUES (:ID,:Name)&quot;)
AddParam(isql, &quot;:ID&quot;, 1)
AddParam(isql, &quot;:Name&quot;, nil)
res,err:= SqlExec(isql)
*/
type (
TisqlMode int32
TisqlAt   struct {
ParamName string
Pos       int
ParamVal  any
}
Tisql struct {
Sql       string
ResultSql string
DB        *sql.DB
Params    map[string]any
At        []TisqlAt
}
)
const (
Oracle  TisqlMode = iota //0, no replacing params
Mysql                    //1, &quot;SELECT * FROM table WHERE price+vat&gt;:Sum and country=:C&quot; -&gt; db.Query(&quot;SELECT * FROM table WHERE price+vat&gt;? and country=?&quot;, 130.5,&quot;USA&quot;)
Postgre                  //2, &quot;SELECT * FROM table WHERE price+vat&gt;:Sum and country=:C&quot; -&gt; db.Query(&quot;SELECT * FROM table WHERE price+vat&gt;$1 and country=$2&quot;, 130.5,&quot;USA&quot;)
)
func (s TisqlMode) String() string {
switch s {
case Oracle:
return &quot;Oracle&quot; // no replacing params
case Mysql:
return &quot;Mysql&quot;
case Postgre:
return &quot;Postgre&quot;
}
return &quot;unknown&quot;
}
var ParamReplacingMode TisqlMode = -1 //-1 = unknown,  0 = no replacing params,  1 = to MySql,  2 = to Postgre
func indexAt(pStr, pSubStr string, pos int) int { //Index from position
if pos &gt;= len(pStr) {
return -1
}
if pos &lt; 0 {
pos = 0
}
idx := strings.Index(pStr[pos:], pSubStr)
if idx &gt; -1 {
idx += pos
}
return idx
}
func InitSql(db *sql.DB, sql string) *Tisql {
if ParamReplacingMode &lt; 0 { // unknow
_, err := db.Exec(&quot;?&quot;)
if err != nil {
s := strings.ToLower(fmt.Sprint(err))
if indexAt(s, &quot;mysql&quot;, 0) &gt; 0 {
ParamReplacingMode = 1
} else {
ParamReplacingMode = 0
}
}
}
var isql Tisql
isql.Sql = sql
isql.DB = db
isql.Params = make(map[string]any)
return &amp;isql
}
func AddParam(isql *Tisql, pParam string, pValue any) {
isql.Params[pParam] = pValue
}
func paramOrder(isql *Tisql, pCheckParamCount bool) error {
var at TisqlAt
isql.ResultSql = isql.Sql
t := &quot;&quot;
b := strings.ToLower(isql.Sql) + &quot; &quot;
mMode := ParamReplacingMode
var p, p1, p2 int
for name, v := range isql.Params {
p1 = 0
for p1 &gt;= 0 {
p = indexAt(b, strings.ToLower(name), p1)
if p &lt; 0 {
p1 = -1
continue
} else {
p2 = p + len(name)
t = b[p2 : p2+1] //char after param
if indexAt(&quot; :,;!?%$&lt;&gt;^*+-/()[]{}=|&#39;`\&quot;\r\n\t&quot;, t, 0) &lt; 0 {
p1 = p + 1
continue
}
p1 = -1
}
}
if p &gt;= 0 {
at.Pos = p
at.ParamVal = v
at.ParamName = name
isql.At = append(isql.At, at)
}
}
if pCheckParamCount &amp;&amp; len(isql.At) != len(isql.Params) {
return fmt.Errorf(&quot;Different count of params %d / %d&quot;, len(isql.At), len(isql.Params))
}
if len(isql.At) &gt; 1 {
sort.Slice(isql.At,
func(i, j int) bool {
return isql.At[i].Pos &lt; isql.At[j].Pos
})
}
mLen := len(isql.Sql)
switch mMode {
case 1: //to Mysql
{
p1, p2, s := 0, 0, &quot;&quot;
for _, at := range isql.At {
p2 = at.Pos
if p2 &gt;= 0 &amp;&amp; p2 &lt;= mLen {
if p2 &gt; p1 {
s += isql.Sql[p1:p2] + &quot;?&quot;
}
p1 = p2 + len(at.ParamName)
}
}
if p1 &lt; len(isql.Sql) {
s += isql.Sql[p1:len(isql.Sql)]
}
isql.ResultSql = s
}
case 2: //to Postgre
{
p1, p2, s := 0, 0, &quot;&quot;
for i, at := range isql.At {
p2 = at.Pos
if p2 &gt;= 0 &amp;&amp; p2 &lt;= mLen {
if p2 &gt; p1 {
s += isql.Sql[p1:p2] + &quot;$&quot; + fmt.Sprint(i+1)
}
p1 = p2 + len(at.ParamName)
}
}
if p1 &lt; len(isql.Sql) {
s += isql.Sql[p1:len(isql.Sql)]
}
isql.ResultSql = s
}
}
return nil
}
func ParamsStr(isql *Tisql) string {
s := &quot;&quot;
for i, at := range isql.At {
s += &quot;[&quot; + fmt.Sprint(i+1) + &quot;. &quot; + at.ParamName + &quot;=\&quot;&quot; + fmt.Sprint(at.ParamVal) + &quot;\&quot;]&quot;
}
return s
}
func SqlStr(isql *Tisql) string {
s := &quot;SQL:[&quot; + isql.ResultSql + &quot;]&quot;
if len(isql.At) &gt; 0 {
s += &quot; Params:&quot; + ParamsStr(isql)
}
return s
}
func SqlExec(isql *Tisql, opt ...bool) (sql.Result, error) {
checkParamCount := false
if len(opt) &gt; 0 {
checkParamCount = opt[0]
}
err := paramOrder(isql, checkParamCount)
if err != nil {
return nil, err
}
mLen := len(isql.At)
mVal := make([]any, mLen)
for i := range mVal {
mVal[i] = isql.At[i].ParamVal
}
return isql.DB.Exec(isql.ResultSql, mVal...)
}
func SqlQuery(isql *Tisql, opt ...bool) (*sql.Rows, error) {
checkParamCount := false
if len(opt) &gt; 0 {
checkParamCount = opt[0]
}
err := paramOrder(isql, checkParamCount)
if err != nil {
return nil, err
}
mLen := len(isql.At)
mVal := make([]any, mLen)
for i := range mVal {
mVal[i] = isql.At[i].ParamVal
}
return isql.DB.Query(isql.ResultSql, mVal...)
}

huangapple
  • 本文由 发表于 2016年10月26日 23:44:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/40266633.html
匿名

发表评论

匿名网友

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

确定