mcpField
F
frappe-syntax-query-builder

frappe-syntax-query-builder

github.com/impertio-studio数据与数据库198113 次安装30 周前更新
代码仓库

关于 frappe-syntax-query-builder

Frappe Query Builder(frappe.qb)的用法参考,面向需要在 Frappe 中构造数据库查询的开发者。它介绍查询构造、数据操作及跨数据库兼容模式,适用于复杂查询场景。

主要功能

  1. 01构造 SELECT 查询
  2. 02连接与聚合查询
  3. 03插入、更新和删除
  4. 04条件筛选与排序
  5. 05跨数据库函数映射

适用场景

  1. 01编写包含 JOIN 的查询
  2. 02汇总并分组数据库记录
  3. 03以参数化方式执行数据修改
  4. 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.md
functions-reference.md
migration-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
操作模式
SELECTfrappe.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("*"))
INSERTfrappe.qb.into("DocType").columns("f1", "f2").insert("v1", "v2")
UPDATEfrappe.qb.update("DocType").set("field", "value").where(...)
DELETEfrappe.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)
)
MariaDBPostgreSQL使用 ImportMapper
GROUP_CONCATSTRING_AGG是
MATCH...AGAINSTTO_TSVECTOR是
LocateStrpos是
Timestamp基于 Extract自动处理

反模式

  1. 绝不要将 qb 查询传给 frappe.db.sql() ——这会绕过参数化
  2. 绝不要对 DocType 使用 Table() ——应使用 DocType()(会添加 tab 前缀)
  3. 绝不要忘记 .run() ——不调用它会得到查询对象,而不是结果
  4. 绝不要在 frappe.get_all(fields=[...]) 中使用原始 SQL 字符串 ——应使用字典语法
  5. 始终为特定数据库的函数使用 ImportMapper
  6. 需要字典时,始终链式调用 .run(as_dict=True) ——默认返回元组

版本差异

功能v14v15v16
核心 qb API引入是是
ImportMapper是是是
SQLite 后端----已添加
掩码字段----已添加
Union 查询(.walk)--已添加是
子查询执行----已添加

参考文件

评论

支持 Markdown