关于 frappe-syntax-query-builder
Frappe Query Builder(frappe.qb)的用法参考,面向需要在 Frappe 中构造数据库查询的开发者。它介绍查询构造、数据操作及跨数据库兼容模式,适用于复杂查询场景。
主要功能
- 01构造 SELECT 查询
- 02连接与聚合查询
- 03插入、更新和删除
- 04条件筛选与排序
- 05跨数据库函数映射
适用场景
- 01编写包含 JOIN 的查询
- 02汇总并分组数据库记录
- 03以参数化方式执行数据修改
- 04让查询适配 MariaDB 与 PostgreSQL
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ impertio-studio/ frappe_claude_skill_package --skill frappe-syntax-query-builder安装后在 Claude Code 中输入/frappe-syntax-query-builder确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/cross-db-patterns.mdfunctions-reference.mdmigration-from-sql.md详细介绍
Frappe 查询构建器(frappe.qb)
快速参考
from frappe.query_builder import DocType, Field
from frappe.query_builder.functions import Count, Sum, IfNull
from frappe.query_builder.custom import ConstantColumn, GROUP_CONCAT
from frappe.query_builder.terms import SubQuery
from frappe.query_builder.utils import ImportMapper, db_type_is
from pypika.terms import Case, ValueWrapper
from pypika import CustomFunction, Order
| 操作 | 模式 |
|---|---|
| SELECT | frappe.qb.from_("DocType").select("field1", "field2") |
| WHERE | .where(Field("status") == "Open") |
| ORDER | .orderby("creation", order=Order.desc) |
| LIMIT | .limit(10).offset(0) |
| JOIN | .left_join(dt2).on(dt2.name == dt1.parent) |
| COUNT | .select(Count("*")) |
| INSERT | frappe.qb.into("DocType").columns("f1", "f2").insert("v1", "v2") |
| UPDATE | frappe.qb.update("DocType").set("field", "value").where(...) |
| DELETE | frappe.qb.from_("DocType").delete().where(...) |
| RUN | .run()(元组)/ .run(as_dict=True)(字典) |
决策树
Need to query the database?
│
├─ Simple get/list → frappe.db.get_value(), frappe.get_all()
│ (See frappe-core-database skill)
│
├─ Complex query with joins/aggregates/subqueries → frappe.qb ✓
│
├─ Very complex SQL not expressible in qb → frappe.db.sql()
│ (ALWAYS use parameterized values: frappe.db.sql(query, values))
│
└─ Need cross-DB compatibility → frappe.qb + ImportMapper ✓
Using frappe.qb?
│
├─ Table reference → DocType("Sales Order") — NEVER use Table()
├─ Field reference → dt.field_name or Field("field_name")
├─ Execute → .run() for tuples, .run(as_dict=True) for dicts
├─ DB-specific function → ImportMapper({db_type_is.MARIADB: X, db_type_is.POSTGRES: Y})
└─ Get SQL string → query.get_sql() — NEVER pass to frappe.db.sql()
核心模式
使用 DocType 查询
# ALWAYS use DocType() for table references — adds "tab" prefix
so = frappe.qb.DocType("Sales Order")
soi = frappe.qb.DocType("Sales Order Item")
orders = (
frappe.qb.from_(so)
.select(so.name, so.customer, so.grand_total)
.where(so.status == "To Deliver and Bill")
.where(so.docstatus == 1)
.orderby(so.creation, order=Order.desc)
.limit(20)
.run(as_dict=True)
)
JOIN
result = (
frappe.qb.from_(so)
.left_join(soi).on(soi.parent == so.name)
.select(so.name, so.customer, soi.item_code, soi.qty)
.where(so.docstatus == 1)
.where(soi.item_code.like("ITEM-%"))
.run(as_dict=True)
)
聚合
from frappe.query_builder.functions import Count, Sum
gl = frappe.qb.DocType("GL Entry")
result = (
frappe.qb.from_(gl)
.select(gl.account, Sum(gl.debit).as_("total_debit"), Count("*").as_("entries"))
.where(gl.docstatus == 1)
.groupby(gl.account)
.run(as_dict=True)
)
# Shortcut aggregation methods
total = frappe.qb.sum("GL Entry", "debit", filters={"account": "Sales"})
max_qty = frappe.qb.max("Stock Ledger Entry", "actual_qty", filters={"item_code": "ITEM-001"})
INSERT / UPDATE / DELETE
# INSERT
frappe.qb.into("Activity Log").columns("user", "action").insert("admin", "login").run()
# UPDATE
customer = frappe.qb.DocType("Customer")
(frappe.qb.update(customer)
.set(customer.status, "Active")
.where(customer.name == "CUST-001")
.run())
# DELETE
frappe.qb.from_("Error Log").delete().where(Field("creation") < "2024-01-01").run()
筛选
dt = frappe.qb.DocType("Sales Order")
# Equality
.where(dt.status == "Open")
# OR (pipe operator)
.where((dt.status == "Open") | (dt.status == "Draft"))
# AND (chain .where() calls)
.where(dt.status == "Open")
.where(dt.docstatus == 1)
# LIKE
.where(dt.customer.like("CUST-%"))
# IN
.where(dt.status.isin(["Open", "Draft"]))
# BETWEEN (bracket syntax)
.where(dt.creation[start_date:end_date])
# NULL checks
.where(dt.email.isnotnull())
.where(dt.phone.isnull())
# Comparison
.where(dt.grand_total > 1000)
.where(dt.grand_total >= 500)
.where(dt.status != "Cancelled")
跨数据库兼容性
from frappe.query_builder.utils import ImportMapper, db_type_is
from frappe.query_builder.custom import GROUP_CONCAT, STRING_AGG
# ImportMapper selects correct function per database
GroupConcat = ImportMapper({
db_type_is.MARIADB: GROUP_CONCAT,
db_type_is.POSTGRES: STRING_AGG,
})
dt = frappe.qb.DocType("Has Role")
result = (
frappe.qb.from_(dt)
.select(dt.parent, GroupConcat(dt.role))
.groupby(dt.parent)
.run(as_dict=True)
)
| MariaDB | PostgreSQL | 使用 ImportMapper |
|---|---|---|
GROUP_CONCAT | STRING_AGG | 是 |
MATCH...AGAINST | TO_TSVECTOR | 是 |
Locate | Strpos | 是 |
Timestamp | 基于 Extract | 自动处理 |
反模式
- 绝不要将 qb 查询传给
frappe.db.sql()——这会绕过参数化 - 绝不要对 DocType 使用
Table()——应使用DocType()(会添加tab前缀) - 绝不要忘记
.run()——不调用它会得到查询对象,而不是结果 - 绝不要在
frappe.get_all(fields=[...])中使用原始 SQL 字符串 ——应使用字典语法 - 始终为特定数据库的函数使用
ImportMapper - 需要字典时,始终链式调用
.run(as_dict=True)——默认返回元组
版本差异
| 功能 | v14 | v15 | v16 |
|---|---|---|---|
| 核心 qb API | 引入 | 是 | 是 |
| ImportMapper | 是 | 是 | 是 |
| SQLite 后端 | -- | -- | 已添加 |
| 掩码字段 | -- | -- | 已添加 |
| Union 查询(.walk) | -- | 已添加 | 是 |
| 子查询执行 | -- | -- | 已添加 |
参考文件
评论
支持 Markdown