source

Golang 빈 문자열 대신 SQL에 NULL 삽입

manysource 2023. 7. 29. 08:35

Golang 빈 문자열 대신 SQL에 NULL 삽입

나는 골랑을 사용하여 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)

내 코드에서 나는 문자열을 다음으로 변환하는 함수를 가지고 있습니다.

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

그럼 제가 사용할 때마다ExecDB에서 NULL일 수 있는 문자열을 다음과 같이 래핑합니다.NewNullString기능.

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

database/sql패키지에 다음과 같습니다.NullString이 상황에 대해 type(입력하세요.

기본적으로 그냥 사용합니다.sql.NullStringDB에서 null로 설정할 문자열 대신.

사용할 수도 있습니다.*string당신의 코드에서 같은 취지로.

두 경우 모두 null 가능 문자열에서 null이 아닌 문자열로 매핑하는 데 문제가 있습니다.빈 문자열은 기술적으로 값이므로 빈 문자열을 0으로 변환해야 한다면 거의 항상 다음과 같은 작업을 수행해야 합니다.

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

다른 대안은 그냥 사용하는 것입니다.*string대신에string앱 전체에 걸쳐 모델에서 사용할 수 있습니다.

데이터베이스에서 저는 빈 문자열과 null이 동일하다는 접근 방식을 취해 왔으며, 빈 문자열을 DB에 저장하고 대부분의 열을 null로 만들 수 없습니다.

사용할 수도 있습니다.NULLIFSQL 쿼리의 함수입니다.

NULLIF(?, '')빈 문자열을 삽입하려고 하면 빈 문자열 대신 NULL을 반환합니다.

NULLIF에 대해 자세히 알아보기: 링크

N개의 NewNullType 함수를 만들지 않으려면 Value() 함수와 함께 pgx & pgtype을 사용하는 것이 좋습니다.

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 {
    // --> 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
}

예: NULL 값 추가:

/*Exaples:
  ParamReplacingMode = 0  // no replacing params
  isql:=InitSql(db,"SELECT * FROM table WHERE price+vat>:Sum and country=:C") // by Oracle
  AddParam(isql,":C", "USA")                                                  // the order for AddParams is not bound, you can add params any order
  AddParam(isql,":Sum", 130.5)
  res,err:= SqlQuery(isql)                                                    //result: db.Query("SELECT * FROM table WHERE price+vat>:Sum and country=:C", 130.5,"USA")
or
  ParamReplacingMode = 1  // MySQL  - replacing params to "?"
  isql:=InitSql(db,"SELECT * FROM table WHERE price+vat>:Sum and country=:C") // by Oracle convert to Mysql
  AddParam(isql,":C", "USA")                                                  // the order for AddParams is not bound, you can add params any order
  AddParam(isql,":Sum", 130.5)
  res,err:= SqlQuery(isql)                                                    //result: db.Query("SELECT * FROM table WHERE price+vat>? and country=?", 130.5,"USA") //replacing params to "?"
or
  ParamReplacingMode = 0 //no replacing params
  isql:=InitSql(db,"SELECT * FROM table WHERE price+vat>$1 and country=$2")   // by Postgre
  AddParam(isql,"$1", 130.5)
  AddParam(isql,"$2", "USA")                                                  // the order for AddParams is not bound, you can add params any order
  res,err:= SqlQuery(isql)                                                    //result: db.Query("SELECT * FROM table WHERE price+vat>$1 and country=$2", 130.5,"USA")
or
  ParamReplacingMode = 2 // mode Oracle to Postgre, replacing params to <$Number>
  isql:=InitSql(db,"SELECT * FROM table WHERE price+vat>:Sum and country=:C") // by Oracle convert to Postgre
  AddParam(isql,":C","USA")
  AddParam(isql,":Sum",130.5)
  res,err:= SqlQuery(isql)                                                    //result: db.Query("SELECT * FROM table WHERE price+vat>$1 and country=$2", 130.5,"USA")

  SqlExec() is similar as SqlQuery(), but call db.Exec(...)

  Example , add NULL value:
  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, no replacing params
    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" // no replacing params
    case Mysql:
        return "Mysql"
    case Postgre:
        return "Postgre"
    }
    return "unknown"
}

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 >= 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 { // unknow
        _, 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] //char after param
                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("Different count of params %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: //to 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: //to 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...)
}

언급URL : https://stackoverflow.com/questions/40266633/golang-insert-null-into-sql-instead-of-empty-string