能否用sqlc实现灵活动态查询?含过滤、分页与PATCH需求
使用sqlc实现高级查询:过滤、分页与PATCH操作
我正在开发一个项目,打算使用sqlc。我很喜欢这个工具,但找不到实现基础CRUD之外的高级查询示例。我需要实现带WHERE子句的过滤查询、分页参数传递,以及PATCH更新操作。
之前用sqlx实现的方式(如下)写起来繁琐,测试麻烦,多个实体复用逻辑会有大量冗余代码,还容易出错,希望通过sqlc的代码生成来封装这些逻辑。
原sqlx实现方式
1. 过滤查询
var args []string var vals []interface{} count := 1 if filter.Name != nil { args = append(args, fmt.Sprintf("name=$%d", count)) vals = append(vals, &filter.Name) count++ } if filter.OrgType != nil { args = append(args, fmt.Sprintf("org_type=$%d", count)) vals = append(vals, &filter.OrgType) count++ } if filter.DeliveryArea != nil { args = append(args, fmt.Sprintf("delivery_area=$%d", count)) vals = append(vals, &filter.DeliveryArea) count++ } if filter.Status != nil { args = append(args, fmt.Sprintf("status=$%d", count)) vals = append(vals, &filter.Status) count++ } query := "select * from contractors" if count != 1 { query = fmt.Sprintf("%s where %s", query, strings.Join(args, " and ")) } // 后续添加limit和offset...
2. PATCH更新操作
var args []string var vals []interface{} count := 1 if payload.Name != nil { args = append(args, fmt.Sprintf("name=$%d", count)) vals = append(vals, payload.Name) count++ } if payload.ContractNum != nil { args = append(args, fmt.Sprintf("contract_num=$%d", count)) vals = append(vals, payload.ContractNum) count++ } if payload.DeliveryArea != nil { args = append(args, fmt.Sprintf("delivery_area=$%d", count)) vals = append(vals, payload.DeliveryArea) count++ } if count == 1 { log.Ctx(ctx).Error().Msg("nothing to update") return fmt.Errorf("nothing to update") } query := fmt.Sprintf("update contractors set %s where id=$%d", strings.Join(args, ", "), id) // 执行查询...
sqlc实现方案
sqlc支持通过SQL模板和条件参数实现动态查询/更新,完全替代手动SQL拼接,同时保证类型安全和代码复用。
1. 带过滤与分页的查询
在你的SQL文件中定义如下查询:
-- name: ListContractors :many SELECT * FROM contractors WHERE (sqlc.narg('name') IS NULL OR name = sqlc.arg('name')) AND (sqlc.narg('org_type') IS NULL OR org_type = sqlc.arg('org_type')) AND (sqlc.narg('delivery_area') IS NULL OR delivery_area = sqlc.arg('delivery_area')) AND (sqlc.narg('status') IS NULL OR status = sqlc.arg('status')) LIMIT sqlc.arg('limit') OFFSET sqlc.arg('offset');
sqlc会自动生成对应的Go结构体和函数:
type ListContractorsParams struct { Name *string OrgType *string DeliveryArea *string Status *string Limit int32 Offset int32 } func (q *Queries) ListContractors(ctx context.Context, params ListContractorsParams) ([]Contractor, error) { // 自动生成的参数绑定与查询执行逻辑 }
使用方式:只需传入有值的过滤字段和分页参数,sqlc会自动忽略NULL参数对应的条件:
// 辅助函数:生成指针类型 func ptr[T any](v T) *T { return &v } // 调用示例 params := ListContractorsParams{ Name: ptr("张三"), Limit: 10, Offset: 0, } contractors, err := db.ListContractors(ctx, params)
2. PATCH动态更新操作
同样在SQL文件中定义更新语句,利用COALESCE保留原有字段值:
-- name: UpdateContractor :execrows UPDATE contractors SET name = COALESCE(sqlc.narg('name'), name), contract_num = COALESCE(sqlc.narg('contract_num'), contract_num), delivery_area = COALESCE(sqlc.narg('delivery_area'), delivery_area) WHERE id = sqlc.arg('id');
sqlc生成的Go代码:
type UpdateContractorParams struct { ID int64 Name *string ContractNum *string DeliveryArea *string } func (q *Queries) UpdateContractor(ctx context.Context, params UpdateContractorParams) (int64, error) { // 自动生成的更新逻辑 }
使用方式:传入要更新的字段和ID,未传入的字段会保留原值。如果没有任何字段更新,执行后影响行数为0,可在业务代码中判断:
params := UpdateContractorParams{ ID: 123, DeliveryArea: ptr("朝阳区"), } rowsAffected, err := db.UpdateContractor(ctx, params) if err != nil { return err } if rowsAffected == 0 { return fmt.Errorf("nothing to update") }
关键优势
- 无需手动拼接SQL,彻底避免SQL注入风险
- 类型安全:生成的结构体与数据库字段严格对应
- 代码复用:多个实体可复用相同的SQL模板逻辑
- 测试简化:只需验证业务参数,无需测试SQL拼接逻辑
内容的提问来源于stack exchange,提问作者nrgx
相关产品推荐
相关产品推荐

