Golang中Sq:类型安全的SQL查询构建器与结构体映射工具
Golang中Sq:类型安全的SQL查询构建器与结构体映射工具
bokwoon95/go-structured-query
Go 语言类型安全的 SQL 构建器和结构体映射器。在 GitHub 上为 bokwoon95/go-structured-query 开发做出贡献。
在 Go 中直接使用 SQL(通过 database/sql)简单直接。然而 database/sql 本身存在一些缺陷。以下是你可能希望使用 sq 查询构建器(它是 database/sql 的一个薄包装)的一些原因:
database/sql 查询容易因拼写错误而出错,并且如果在迁移中更改了列名或类型,重构起来会很繁琐。
sq从你的表生成结构体定义,以便你可以以类型安全的方式引用表列。- 如果迁移更改了列名或类型,它会导致你的应用程序在编译时出错,直到你更改代码中的所有相关部分。
database/sql 查询涉及大量样板代码,尤其是在将列扫描到结构体字段时。
sq允许你在映射器函数中声明式地将表列映射到结构体字段,并在查询中重复使用它。- 是的,就像
sqlx一样。不同的是,sqlx涉及编写基于字符串的结构体注解,这仍然容易产生拼写错误。sq使用它从你的表生成的类型安全定义。 - 此外,
sqlx仍然需要你手动写出要 SELECT 的列,它自动化的唯一事情是将选定的列映射到结构体字段。 sq的映射器函数同时服务于 SELECT 列和将它们映射到结构体字段的目的,这就像是增强版的sqlx。- 更多信息
- 是的,就像
database/sql(和 sqlx)不处理动态构建 SQL 查询。
sq是一个查询构建器,因此它处理构建查询。看看它能做什么。
查询示例
SELECT
-- SQL
SELECT u.user_id, u.name, u.email, u.created_at
FROM public.users AS u
WHERE u.name = 'Bob';
// Go
u := tables.USERS().As("u") // table is code generated
var user User
var users []User
err := sq.
From(u).
Where(u.NAME.EqString("Bob")).
Selectx(func(row *sq.Row) {
user.UserID = row.Int(u.USER_ID)
user.Name = row.String(u.NAME)
user.Email = row.String(u.EMAIL)
user.CreatedAt = row.Time(u.CREATED_AT)
}, func() {
users = append(users, user)
}).
Fetch(db)
if err != nil {
// handle error
}
INSERT
-- SQL
INSERT INTO public.users (name, email)
VALUES ('Bob', 'bob@email.com'), ('Alice', 'alice@email.com'), ('Eve', 'eve@email.com');
// Go
u := tables.USERS().As("u") // table is code generated
users := []User{
{Name: "Bob", Email: "bob@email.com"},
{Name: "Alice", Email: "alice@email.com"},
{Name: "Eve ", Email: "eve@email.com"},
}
rowsAffected, err := sq.
InsertInto(u).
Valuesx(func(col *sq.Column) {
for _, user := range users {
col.SetString(u.NAME, user.Name)
col.SetString(u.EMAIL, user.Email)
}
}).
Exec(db, sq.ErowsAffected)
if err != nil {
// handle error
}
UPDATE
-- SQL
UPDATE public.users
SET name = 'Bob', password = 'qwertyuiop'
WHERE email = 'bob@email.com';
// Go
u := tables.USERS().As("u") // table is code generated
user := User{
Name: "Bob",
Email: "bob@email.com",
Password: "qwertyuiop",
}
rowsAffected, err := sq.
Update(u).
Setx(func(col *sq.Column) {
col.SetString(u.NAME, user.Name)
col.SetString(u.PASSWORD, user.Password)
}).
Where(u.EMAIL.EqString(user.Email)).
Exec(db, sq.ErowsAffected)
if err != nil {
// handle error
}
DELETE
-- SQL
DELETE FROM public.users AS u
USING public.user_roles AS ur
JOIN public.user_roles_students AS urs ON urs.user_role_id = ur.user_role_id
WHERE u.user_id = ur.user_id AND urs.team_id = 15;
// Go
u := tables.USERS().As("u") // tables are code generated
ur := tables.USER_ROLES().As("ur") // tables are code generated
urs := tables.USER_ROLES_STUDENTS().As("urs") // tables are code generated
rowsAffected, err := sq.
DeleteFrom(u).
Using(ur).
Join(urs, urs.USER_ROLE_ID.Eq(ur.USER_ROLE_ID)).
Where(
u.USER_ID.Eq(ur.USER_ID),
urs.TEAM_ID.EqInt(15),
).
Exec(db, sq.ErowsAffected)
if err != nil {
// handle error
}
欲了解更多信息,请查看 基础。
如需查询示例列表,请查看 查询构建。
更多关于Golang中Sq:类型安全的SQL查询构建器与结构体映射工具的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于Golang中Sq:类型安全的SQL查询构建器与结构体映射工具的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
sq 确实是一个优秀的类型安全 SQL 构建器,它通过代码生成解决了 database/sql 在类型安全和重构方面的痛点。以下是一些关键优势的补充说明:
类型安全的列引用
通过生成的表结构体,列引用在编译时就能发现错误:
// 错误示例:如果列名在数据库迁移中已更改,这里会编译失败
err := sq.From(u).
Where(u.OLD_COLUMN_NAME.EqString("value")). // 编译错误:OLD_COLUMN_NAME 不存在
Fetch(db)
动态查询构建
sq 支持复杂的动态查询构建,这是原生 database/sql 难以实现的:
func buildDynamicQuery(filters map[string]interface{}) sq.SelectQuery {
u := tables.USERS()
query := sq.From(u)
// 动态添加 WHERE 条件
if name, ok := filters["name"]; ok {
query = query.Where(u.NAME.EqString(name.(string)))
}
if email, ok := filters["email"]; ok {
query = query.Where(u.EMAIL.EqString(email.(string)))
}
return query
}
// 使用动态查询
query := buildDynamicQuery(map[string]interface{}{
"name": "Bob",
"email": "bob@email.com",
})
批量操作优化
sq 的 Valuesx 方法特别适合批量操作,性能优于循环执行单条 SQL:
// 批量插入性能优化
users := []User{/* 大量用户数据 */}
rowsAffected, err := sq.
InsertInto(u).
Valuesx(func(col *sq.Column) {
for _, user := range users {
col.SetString(u.NAME, user.Name)
col.SetString(u.EMAIL, user.Email)
col.SetTime(u.CREATED_AT, time.Now())
}
}).
Exec(db, sq.ErowsAffected)
复杂 JOIN 查询
sq 的类型安全特性在复杂 JOIN 查询中尤为有用:
// 多表 JOIN 查询
u := tables.USERS().As("u")
p := tables.PROFILES().As("p")
o := tables.ORDERS().As("o")
err := sq.
From(u).
Join(p, p.USER_ID.Eq(u.USER_ID)).
LeftJoin(o, o.USER_ID.Eq(u.USER_ID)).
Where(
u.ACTIVE.IsBool(true),
p.COUNTRY.EqString("US"),
o.STATUS.EqString("completed"),
).
Selectx(func(row *sq.Row) {
// 类型安全的字段访问
userID := row.Int(u.USER_ID)
userName := row.String(u.NAME)
profileBio := row.StringNull(p.BIO)
orderAmount := row.FloatNull(o.AMOUNT)
}, func() {
// 处理每一行
}).
Fetch(db)
子查询支持
sq 天然支持子查询,保持类型安全:
// 使用子查询
subquery := sq.
Select(tables.ORDERS().USER_ID).
From(tables.ORDERS()).
Where(tables.ORDERS().AMOUNT.GtFloat(1000))
err := sq.
From(u).
Where(u.USER_ID.In(subquery)).
Selectx(func(row *sq.Row) {
// 选择高价值用户
}, func() {
// 处理结果
}).
Fetch(db)
sq 的设计确实解决了 Go 中 SQL 操作的几个核心问题:类型安全、重构友好性和动态查询构建。它的代码生成方式虽然增加了构建步骤,但换来了更好的开发体验和更少的运行时错误。

