英文:
Bypass sql null value problems in Go
问题
我想使用Go语言为一个现有的广泛使用null值的数据库创建一个API。Go语言不会将null值扫描为空字符串(或等效值),因此我需要实现一个解决方法。
我发现的解决方法都不令我满意。实际上,我之所以寻找一种动态语言,就是因为这个问题,但Go语言有一些吸引人的地方,如果可能的话,我想继续使用它。以下是我不满意的解决方法:
- 不在数据库中使用null值。这种方法不适用,因为数据库是预先存在的,我没有干涉其结构的自由。数据库比我的应用程序更重要,而不是相反。
- 在SQL查询中,在数据到达我的应用程序之前,使用COALESCE、ISNULL等函数将null值转换为空字符串(或等效值)。这种方法不适用,因为有许多字段和表。除了一些明显的字段(主键、姓氏)之外,我不确定哪些字段可以可靠地不给我一个null值,所以我将在所有地方防御性地混乱我的SQL查询。
- 使用sql.NullString、sql.NullInt64、sql.NullFloat64等类型,在将null值转换为空字符串(或等效值)之前,将其作为中间步骤转换为目标类型。这与上述问题相同,只是我在我的Go代码中混乱,而不是在我的SQL查询中。
- 使用*指针和[]byte的组合,将每个项目扫描到一个内存位置,而不将其提交给特定类型(除了[]byte),然后以某种方式处理原始数据。但是,要对数据进行有意义的处理,您必须将其转换为更有用的形式,然后又回到了sql.NullString或if x==nil{handle it},这又一次发生在我需要处理的任何字段的情况下。因此,我们再次面临混乱、凌乱、容易出错的代码,并且我一直在重复自己,而不是在编码中保持DRY原则(Don't Repeat Yourself)。
- 寻求Go语言的ORM库的帮助。好吧,我确实这样做了,但令我惊讶的是,它们都没有解决这个问题。
- 制作自己的辅助包,将所有null字符串转换为空字符串,将null整数转换为0,将null浮点数转换为0.00,将null布尔值转换为false等,并将其作为从SQL驱动程序扫描的过程的一部分,得到常规的字符串、整数、浮点数和布尔值。
不幸的是,如果第6种方法是解决方案,我没有相关的专业知识。我怀疑解决方案可能涉及类似于"如果要扫描的项的目标类型是字符串,则将其设置为sql.NullString并从中提取一个空字符串。但是,如果要扫描的项是整数,则将其设置为NullInt64并从中获取零。但是如果...(等等)"的内容。
我是否漏掉了什么?谢谢。
英文:
I want to use Go to make an API for an existing database that uses null values extensively. Go will not scan nulls to empty strings (or equivalent), and so I need to implement a workaround.
The workarounds I have discovered have left me unsatisfied. In fact I went looking for a dynamic language because of this problem, but Go has certain attractions and I would like to stick with it if possible. Here are the workarounds that did not satisfy:
-
Don't use nulls in the database. Unsuitable because the database is pre-existing and I do not have liberty to interfere with its structure. The database is more important than my app, not the other way around.
-
In sql queries, use COALESCE, ISNULL, etc to convert nulls to empty strings (or equiv) before the data gets to my app. Unsuitable because there are many fields and many tables. Apart from a couple of obvious ones (primary key, surname), I don't know for sure which fields can be relied upon not to give me a null value, so I would be defensively cluttering my sql queries everywhere.
-
Use sql.NullString, sql.NullInt64, sql.NullFloat64, etc to convert nulls to empty strings (or equiv) as an intermediate step before settling them into their destination type. This suffers from the same problem as above, only I am cluttering my Go code instead of my sql queries.
-
Use a combination of *pointers and []byte, to scan each item in to a memory location without committing it to a particular type (other than []byte), and then somehow work with the raw data. But to do something meaningful with the data you have to convert it to something more useful, and then you are back to sql.Nullstring or if x==nil{handle it}, and this again is happening on a case by case basis for any field that I need to work with. So, again, we are looking at cluttered, messy, error-prone code and I'm repeating myself all the time instead of being DRY in my coding.
-
Look to the Go ORM libraries for help. Well I did that, but to my surprise none of them tackle this issue.
-
Make my own helper package to convert all null strings to "", null ints to 0, null floats to 0.00, null bools to false, etc, and make it part of the process of scanning in from the sql driver, resulting in regular, normal strings, ints, floats and bools.
Unfortunately if 6 is the solution, I do not have the expertise. I suspect the solution would involve something like "if the intended type of the item to be scanned to is a string, make it an sql.NullString and extract an empty string from it. But if the item to be scanned to is an int, make it a NullInt64 and get a zero from that. But if ...(etc)"
Is there anything I have missed? Thank you.
答案1
得分: 7
使用指针作为SQL扫描目标变量的方法使得数据可以被扫描进来,并且可以在其中进行操作(在检查是否为nil的情况下),然后将其编组为JSON,以便从API发送出去,而无需在各个地方放置大量的sql.NullString、sql.NullFloat64等。空值会通过编组后的JSON奇迹般地保留并发送出去(请参见底部的Fathername)。在另一端,客户端可以使用JavaScript处理空值,因为它更适合处理它们。
func queryToJson(db *sql.DB) []byte {
rows, err := db.Query(
"select mothername, fathername, surname from fams" +
"where surname = ?", "Nullfather"
)
defer rows.Close()
type record struct {
Mname, Fname, Surname *string // the key: use pointers
}
records := []record{}
for rows.Next() {
var r record
err := rows.Scan(r.Mname, r.Fname, r.Surname) // no need for "&"
if err != nil {
log.Fatal(err)
}
fmt.Println(r)
records = append(records, r)
}
j, err := json.Marshal(records)
if err != nil {
log.Fatal(err)
}
return j
}
j := queryToJson(db)
fmt.Println(string(j)) // [{"Mothername":"Mary", "Fathername":null, "Surname":"Nullfather"}]
希望这个翻译对你有帮助!
英文:
The use of pointers for the sql-scanning destination variables enables the data to be scanned in, worked with (subject to checking if != nil) and marshalled to json, to be sent out from the API, without having to put hundreds of sql.Nullstring, sql.Nullfloat64 etc everywhere. Nulls are miraculously preserved and sent out through the marshalled json. (See Fathername at the bottom). At the other end, the client can work with the nulls in javascript which is better equipped to handle them.
func queryToJson(db *sql.DB) []byte {
rows, err := db.Query(
"select mothername, fathername, surname from fams" +
"where surname = ?", "Nullfather"
)
defer rows.Close()
type record struct {
Mname, Fname, Surname *string // the key: use pointers
}
records := []record{}
for rows.Next() {
var r record
err := rows.Scan(r.Mname, r.Fname, r.Surname) // no need for "&"
if err != nil {
log.Fatal(err)
}
fmt.Println(r)
records = append(records, r)
}
j, err := json.Marshal(records)
if err != nil {
log.Fatal(err)
}
return j
}
j := queryToJson(db)
fmt.Println(string(j)) // [{"Mothername":"Mary", "Fathername":null, "Surname":"Nullfather"}]
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论