SQL 工具 - SqlUtil¶
由来¶
在日常开发中,手动拼接 SQL 语句容易出错且代码冗长。SqlUtil 提供了常用的 SQL 语句生成方法,支持 SELECT、INSERT、UPDATE、DELETE 以及备份表创建。支持 MySQL、PostgreSQL、SQLite 三种方言。
方言支持¶
所有方法均接受 dialect 参数,可选值为 "mysql"(默认)、"postgresql"、"sqlite"。
特性 |
MySQL |
PostgreSQL |
SQLite |
|---|---|---|---|
标识符引用 |
|
|
|
分页 |
|
|
|
REPLACE INTO |
|
|
|
INSERT IGNORE |
|
|
|
ON DUPLICATE KEY |
|
|
|
VALUES 引用 |
|
|
|
方法¶
生成查询 SQL¶
from hutool import SqlUtil
# MySQL(默认)
SqlUtil.make_select_sql("user", ["name", "age"])
# 'SELECT `name`,`age` FROM `user`'
# 带条件和排序
SqlUtil.make_select_sql("user", "*", condition="age > 18", order_by=["id"])
# 'SELECT * FROM `user` WHERE age > 18 ORDER BY `id`'
# 分页查询
SqlUtil.make_select_sql("user", "*", page_no=2, page_size=10)
# 'SELECT * FROM `user` LIMIT 10, 10'
# PostgreSQL
SqlUtil.make_select_sql("user", "*", page_no=2, page_size=10, dialect="postgresql")
# 'SELECT * FROM "user" LIMIT 10 OFFSET 10'
# SQLite
SqlUtil.make_select_sql("user", ["name", "age"], dialect="sqlite")
# 'SELECT `name`,`age` FROM `user`'
生成插入 SQL¶
# 单条插入(MySQL)
SqlUtil.make_insert_sql("user", {"name": "Alice", "age": 20})
# 批量插入
data = [{"name": "Alice", "age": 20}, {"name": "Bob", "age": 30}]
SqlUtil.make_insert_sql("user", data)
# INSERT IGNORE(MySQL)
SqlUtil.make_insert_sql("user", {"name": "Alice"}, insert_ignore=True)
# INSERT IGNORE(PostgreSQL → ON CONFLICT DO NOTHING)
SqlUtil.make_insert_sql("user", {"name": "Alice"}, insert_ignore=True, dialect="postgresql")
# INSERT IGNORE(SQLite → INSERT OR IGNORE)
SqlUtil.make_insert_sql("user", {"name": "Alice"}, insert_ignore=True, dialect="sqlite")
# REPLACE INTO(MySQL)
SqlUtil.make_insert_sql("user", {"name": "Alice"}, auto_update=True)
# ON DUPLICATE KEY UPDATE(MySQL)
SqlUtil.make_insert_sql("user", {"name": "Alice", "age": 20}, update_columns=("name",))
# ON CONFLICT DO UPDATE SET(PostgreSQL,需指定 on_conflict)
SqlUtil.make_insert_sql(
"user", {"name": "Alice", "age": 20}, update_columns=("name",), on_conflict=("id",), dialect="postgresql"
)
# ON CONFLICT DO UPDATE SET(SQLite)
SqlUtil.make_insert_sql(
"user", {"name": "Alice", "age": 20}, update_columns=("name",), on_conflict=("id",), dialect="sqlite"
)
生成更新 SQL¶
# MySQL
SqlUtil.make_update_sql("user", {"name": "Bob"}, "id = 1")
# "UPDATE `user` SET `name`='Bob' WHERE id = 1;\n"
# PostgreSQL
SqlUtil.make_update_sql("user", {"name": "Bob"}, "id = 1", dialect="postgresql")
# 'UPDATE "user" SET "name"=\'Bob\' WHERE id = 1;\n'
生成删除 SQL¶
SqlUtil.make_delete_sql("user", "id = 1")
# 'DELETE FROM user WHERE id = 1'
创建备份表¶
# 使用默认日期
SqlUtil.create_bak_table_sql("user")
# 'create table user_20240115 as select * from user;\n'
# 指定日期
SqlUtil.create_bak_table_sql("user", bak_date="20240101")
# 'create table user_20240101 as select * from user;\n'
# 批量备份
SqlUtil.create_bak_table_sql(["t1", "t2"], bak_date="20240101")
值格式化¶
SqlUtil._format_sql_value 内部方法支持自动格式化以下类型:
类型 |
转换 |
|---|---|
|
用 |
|
转为 JSON 字符串 |
|
|
|
|
|
|
|
|
numpy 浮点类型 |
|
numpy 整数类型 |
|
备注
numpy 支持是可选的。安装了 numpy 时会自动识别 np.float16/32/64、np.int8/16/32/64 等类型并转为 Python 原生类型;未安装 numpy 时完全不影响使用。
链式调用 API¶
除了上述静态方法,SqlUtil 还提供了链式调用 API,通过 SqlUtil.select()、SqlUtil.insert() 等工厂方法创建 Builder 实例,配合 Q(条件组合)、F(字段引用)、ColumnType(列类型枚举)可以链式生成复杂 SQL 语句。
Q — 条件组合器¶
Q 支持 AND(&)、OR(|)、NOT(~) 组合条件。
from hutool import SqlUtil, Q
# 简单条件
Q(name="Alice") # → `name`='Alice'
# OR
Q(age__gt=18) | Q(role="admin")
# → (`age`>18 OR `role`='admin')
# AND(显式)
Q(status="active") & Q(dept="IT")
# → `status`='active' AND `dept`='IT'
# NOT
~Q(status="deleted")
# → NOT `status`='deleted'
# 复杂嵌套
condition = (Q(age__gte=18, status="active") | Q(role="admin")) & Q(dept__in=["IT", "HR"])
支持的操作符: __gt、__gte、__lt、__lte、__ne、__in、__not_in、__between、__like、__contains、__startswith、__endswith、__isnull
F — 字段引用¶
F 用于字段间比较、算术运算和聚合函数。
from hutool import SqlUtil, F
# 算术运算
F("balance") + 100 # → `balance`+100
F("stock") - 10 # → `stock`-10
F("price") * 1.5 # → `price`*1.5
# 聚合函数
F.count("*") # → COUNT(*)
F.sum("amount") # → SUM(`amount`)
F.avg("score") # → AVG(`score`)
F.max("price") # → MAX(`price`)
F.min("price") # → MIN(`price`)
SELECT 链式查询¶
from hutool import SqlUtil, Q, F
# 基础查询
sql = (
SqlUtil.select("user")
.columns("id", "name", "age")
.where(age__gt=18, status="active")
.order_by("-age", "name")
.limit(10)
.offset(20)
.sql()
)
# 不指定 columns → SELECT *
sql = SqlUtil.select("user").where(id=1).sql()
# Q 组合条件
sql = SqlUtil.select("user").where(Q(age__gt=18) | Q(role="admin")).where(~Q(status="deleted")).sql()
# F 字段引用
sql = SqlUtil.select("product").where(price__gt=F("cost") * 1.5).sql()
# 原始 SQL 片段
sql = SqlUtil.select("user").where("find_in_set(1, status)", Q(age__gt=18)).where_raw("YEAR(created_at) = 2024").sql()
# GROUP BY + HAVING
sql = SqlUtil.select("order").columns("user_id", "COUNT(*) AS cnt").group_by("user_id").having(cnt__gt=5).sql()
# DISTINCT
sql = SqlUtil.select("user").columns("city").distinct().sql()
# 分页
sql = SqlUtil.select("user").columns("*").page(2, 10).sql()
方法列表:
方法 |
说明 |
|---|---|
|
指定查询列,可传原始 SQL 表达式 |
|
添加条件(Q 对象、原始 SQL、field__op=value) |
|
添加原始 SQL WHERE 条件 |
|
排序,前缀 |
|
分页 |
|
按页码分页 |
|
去重 |
|
分组 |
|
HAVING 条件 |
INSERT 链式插入¶
# 基础插入
sql = SqlUtil.insert("user").values(name="Alice", age=20).sql()
# 批量插入
data = [{"name": "Alice", "age": 20}, {"name": "Bob", "age": 30}]
sql = SqlUtil.insert("user").values_list(data).sql()
# INSERT IGNORE
sql = SqlUtil.insert("user").values(name="Alice").ignore().sql()
# REPLACE INTO
sql = SqlUtil.insert("user").values(name="Alice").replace().sql()
# ON DUPLICATE KEY UPDATE(MySQL)
sql = SqlUtil.insert("user").values(name="Alice", age=20).do_update("name").sql()
# ON CONFLICT DO UPDATE SET(PostgreSQL)
sql = (
SqlUtil.insert("user", dialect="postgresql")
.values(name="Alice", age=20)
.on_conflict("id")
.do_update("name", "age")
.sql()
)
# ON CONFLICT DO NOTHING
sql = SqlUtil.insert("user", dialect="postgresql").values(name="Alice").do_nothing().sql()
UPDATE 链式更新¶
# 基础更新
sql = SqlUtil.update("user").set(name="Bob", age=30).where(id=1).sql()
# F 字段引用
sql = SqlUtil.update("account").set(balance=F("balance") + 100).where(id=1).sql()
# 原始 SET
sql = SqlUtil.update("account").set_raw("balance = balance + 100", "login_count = login_count + 1").where(id=1).sql()
# Q 条件
sql = SqlUtil.update("user").set(active=False).where(Q(status="banned") | Q(inactive_days__gt=365)).sql()
DELETE 链式删除¶
# 基础删除
sql = SqlUtil.delete("user").where(id=1).sql()
# 多条件
sql = SqlUtil.delete("user").where(status="inactive", created_at__lt="2020-01-01").sql()
# Q 条件
sql = SqlUtil.delete("user").where(Q(age__lt=18) | Q(status="banned")).sql()
# 原始 SQL
sql = SqlUtil.delete("user").where_raw("DATEDIFF(NOW(), created_at) > 365").sql()
CREATE TABLE 链式建表¶
使用 ColumnType 枚举自动适配不同方言的列类型。
from hutool import SqlUtil
from hutool.core.sql import ColumnType as CT
# MySQL
sql = (
SqlUtil.create_table("user")
.column("id", CT.SERIAL, primary_key=True)
.column("name", CT.VARCHAR, type_args="(100)", nullable=False)
.column("age", CT.INT, default=0)
.column("bio", CT.TEXT)
.column("active", CT.BOOLEAN, default=True)
.column("extra", CT.JSON)
.if_not_exists()
.engine("InnoDB")
.charset("utf8mb4")
.sql()
)
# PostgreSQL
sql = (
SqlUtil.create_table("user", dialect="postgresql")
.column("id", CT.SERIAL, primary_key=True)
.column("name", CT.VARCHAR, type_args="(100)", nullable=False)
.column("uid", CT.UUID)
.if_not_exists()
.sql()
)
# 复合主键
sql = (
SqlUtil.create_table("user_role")
.column("user_id", CT.INT)
.column("role_id", CT.INT)
.primary_key("user_id", "role_id")
.sql()
)
ColumnType 枚举值:
类型 |
MySQL |
PostgreSQL |
SQLite |
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
备注
ColumnType.VARCHAR、ColumnType.CHAR、ColumnType.DECIMAL 等需要参数的类型,通过 .column(name, CT.VARCHAR, type_args='(255)') 传入参数。