erpnext-database
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseERPNext Database Operations
ERPNext 数据库操作
Quick Overview
快速概览
Frappe provides three abstraction levels for database operations:
| Level | API | Usage |
|---|---|---|
| High-level ORM | | Document CRUD with validations |
| Mid-level Query | | Reading with filters |
| Low-level SQL | | Complex queries, reports |
RULE: Always use the highest abstraction level appropriate for your use case.
Frappe为数据库操作提供了三个抽象层级:
| 层级 | API | 用途 |
|---|---|---|
| 高层级ORM | | 带验证的文档增删改查 |
| 中层级查询 | | 带过滤条件的读取操作 |
| 低层级SQL | | 复杂查询、报表生成 |
规则:始终选择与你的使用场景最匹配的最高抽象层级。
Decision Tree
决策树
What do you want to do?
│
├─ Create/modify/delete document?
│ └─ frappe.get_doc() + .insert()/.save()/.delete()
│
├─ Get single document?
│ ├─ Changes frequently? → frappe.get_doc()
│ └─ Changes rarely? → frappe.get_cached_doc()
│
├─ List of documents?
│ ├─ With user permissions? → frappe.db.get_list()
│ └─ Without permissions? → frappe.get_all()
│
├─ Single field value?
│ ├─ Regular DocType → frappe.db.get_value()
│ └─ Single DocType → frappe.db.get_single_value()
│
├─ Direct update without triggers?
│ └─ frappe.db.set_value() or doc.db_set()
│
└─ Complex query with JOINs?
└─ frappe.qb (Query Builder) or frappe.db.sql()What do you want to do?
│
├─ Create/modify/delete document?
│ └─ frappe.get_doc() + .insert()/.save()/.delete()
│
├─ Get single document?
│ ├─ Changes frequently? → frappe.get_doc()
│ └─ Changes rarely? → frappe.get_cached_doc()
│
├─ List of documents?
│ ├─ With user permissions? → frappe.db.get_list()
│ └─ Without permissions? → frappe.get_all()
│
├─ Single field value?
│ ├─ Regular DocType → frappe.db.get_value()
│ └─ Single DocType → frappe.db.get_single_value()
│
├─ Direct update without triggers?
│ └─ frappe.db.set_value() or doc.db_set()
│
└─ Complex query with JOINs?
└─ frappe.qb (Query Builder) or frappe.db.sql()Most Used Patterns
最常用模式
Get Document
获取文档
python
undefinedpython
undefinedWith ORM (triggers validations)
With ORM (triggers validations)
doc = frappe.get_doc('Sales Invoice', 'SINV-00001')
doc = frappe.get_doc('Sales Invoice', 'SINV-00001')
Cached (faster for frequently accessed docs)
Cached (faster for frequently accessed docs)
doc = frappe.get_cached_doc('Company', 'My Company')
undefineddoc = frappe.get_cached_doc('Company', 'My Company')
undefinedList Query
列表查询
python
undefinedpython
undefinedWith user permissions
With user permissions
tasks = frappe.db.get_list('Task',
filters={'status': 'Open'},
fields=['name', 'subject'],
order_by='creation desc',
page_length=50
)
tasks = frappe.db.get_list('Task',
filters={'status': 'Open'},
fields=['name', 'subject'],
order_by='creation desc',
page_length=50
)
Without permissions
Without permissions
all_tasks = frappe.get_all('Task', filters={'status': 'Open'})
undefinedall_tasks = frappe.get_all('Task', filters={'status': 'Open'})
undefinedSingle Value
单个值查询
python
undefinedpython
undefinedSingle field
Single field
status = frappe.db.get_value('Task', 'TASK001', 'status')
status = frappe.db.get_value('Task', 'TASK001', 'status')
Multiple fields
Multiple fields
subject, status = frappe.db.get_value('Task', 'TASK001', ['subject', 'status'])
subject, status = frappe.db.get_value('Task', 'TASK001', ['subject', 'status'])
As dict
As dict
data = frappe.db.get_value('Task', 'TASK001', ['subject', 'status'], as_dict=True)
undefineddata = frappe.db.get_value('Task', 'TASK001', ['subject', 'status'], as_dict=True)
undefinedCreate Document
创建文档
python
doc = frappe.get_doc({
'doctype': 'Task',
'subject': 'New Task',
'status': 'Open'
})
doc.insert()python
doc = frappe.get_doc({
'doctype': 'Task',
'subject': 'New Task',
'status': 'Open'
})
doc.insert()Update Document
更新文档
python
undefinedpython
undefinedVia ORM (with validations)
Via ORM (with validations)
doc = frappe.get_doc('Task', 'TASK001')
doc.status = 'Completed'
doc.save()
doc = frappe.get_doc('Task', 'TASK001')
doc.status = 'Completed'
doc.save()
Direct (without validations) - use carefully!
Direct (without validations) - use carefully!
frappe.db.set_value('Task', 'TASK001', 'status', 'Completed')
---frappe.db.set_value('Task', 'TASK001', 'status', 'Completed')
---Filter Operators
过滤操作符
python
{'status': 'Open'} # =
{'status': ['!=', 'Cancelled']} # !=
{'amount': ['>', 1000]} # >
{'amount': ['>=', 1000]} # >=
{'status': ['in', ['Open', 'Working']]} # IN
{'date': ['between', ['2024-01-01', '2024-12-31']]} # BETWEEN
{'subject': ['like', '%urgent%']} # LIKE
{'description': ['is', 'set']} # IS NOT NULL
{'description': ['is', 'not set']} # IS NULLpython
{'status': 'Open'} # =
{'status': ['!=', 'Cancelled']} # !=
{'amount': ['>', 1000]} # >
{'amount': ['>=', 1000]} # >=
{'status': ['in', ['Open', 'Working']]} # IN
{'date': ['between', ['2024-01-01', '2024-12-31']]} # BETWEEN
{'subject': ['like', '%urgent%']} # LIKE
{'description': ['is', 'set']} # IS NOT NULL
{'description': ['is', 'not set']} # IS NULLQuery Builder (frappe.qb)
查询构建器(frappe.qb)
python
Task = frappe.qb.DocType('Task')
results = (
frappe.qb.from_(Task)
.select(Task.name, Task.subject)
.where(Task.status == 'Open')
.orderby(Task.creation, order='desc')
.limit(10)
).run(as_dict=True)python
Task = frappe.qb.DocType('Task')
results = (
frappe.qb.from_(Task)
.select(Task.name, Task.subject)
.where(Task.status == 'Open')
.orderby(Task.creation, order='desc')
.limit(10)
).run(as_dict=True)With JOIN
关联查询
python
SI = frappe.qb.DocType('Sales Invoice')
Customer = frappe.qb.DocType('Customer')
results = (
frappe.qb.from_(SI)
.inner_join(Customer)
.on(SI.customer == Customer.name)
.select(SI.name, Customer.customer_name)
.where(SI.docstatus == 1)
).run(as_dict=True)python
SI = frappe.qb.DocType('Sales Invoice')
Customer = frappe.qb.DocType('Customer')
results = (
frappe.qb.from_(SI)
.inner_join(Customer)
.on(SI.customer == Customer.name)
.select(SI.name, Customer.customer_name)
.where(SI.docstatus == 1)
).run(as_dict=True)Caching
缓存
Basics
基础用法
python
undefinedpython
undefinedSet/Get
Set/Get
frappe.cache.set_value('key', 'value')
value = frappe.cache.get_value('key')
frappe.cache.set_value('key', 'value')
value = frappe.cache.get_value('key')
With expiry
With expiry
frappe.cache.set_value('key', 'value', expires_in_sec=3600)
frappe.cache.set_value('key', 'value', expires_in_sec=3600)
Delete
Delete
frappe.cache.delete_value('key')
undefinedfrappe.cache.delete_value('key')
undefined@redis_cache Decorator
@redis_cache 装饰器
python
from frappe.utils.caching import redis_cache
@redis_cache(ttl=300) # 5 minutes
def get_dashboard_data(user):
return expensive_calculation(user)python
from frappe.utils.caching import redis_cache
@redis_cache(ttl=300) # 5 minutes
def get_dashboard_data(user):
return expensive_calculation(user)Invalidate cache
Invalidate cache
get_dashboard_data.clear_cache()
---get_dashboard_data.clear_cache()
---Transactions
事务
Framework manages transactions automatically:
| Context | Commit | Rollback |
|---|---|---|
| POST/PUT request | After success | On exception |
| Background job | After success | On exception |
框架会自动管理事务:
| 上下文 | 提交 | 回滚 |
|---|---|---|
| POST/PUT 请求 | 成功后 | 发生异常时 |
| 后台任务 | 成功后 | 发生异常时 |
Manual (rarely needed)
手动事务(极少需要)
python
frappe.db.savepoint('my_savepoint')
try:
# operations
frappe.db.commit()
except:
frappe.db.rollback(save_point='my_savepoint')python
frappe.db.savepoint('my_savepoint')
try:
# operations
frappe.db.commit()
except:
frappe.db.rollback(save_point='my_savepoint')Critical Rules
重要规则
1. NEVER Use String Formatting in SQL
1. 绝对不要在SQL中使用字符串格式化
python
undefinedpython
undefined❌ SQL Injection risk!
❌ SQL Injection risk!
frappe.db.sql(f"SELECT * FROM WHERE name = '{user_input}'")
tabUserfrappe.db.sql(f"SELECT * FROM WHERE name = '{user_input}'")
tabUser✅ Parameterized
✅ Parameterized
frappe.db.sql("SELECT * FROM WHERE name = %(name)s", {'name': user_input})
tabUserundefinedfrappe.db.sql("SELECT * FROM WHERE name = %(name)s", {'name': user_input})
tabUserundefined2. NEVER Commit in Controller Hooks
2. 绝对不要在控制器钩子中提交事务
python
undefinedpython
undefined❌ WRONG
❌ WRONG
def validate(self):
frappe.db.commit() # Never do this!
def validate(self):
frappe.db.commit() # Never do this!
✅ Framework handles commits
✅ Framework handles commits
undefinedundefined3. ALWAYS Paginate
3. 始终使用分页
python
undefinedpython
undefined✅ Always limit
✅ Always limit
docs = frappe.get_all('Sales Invoice', page_length=100)
undefineddocs = frappe.get_all('Sales Invoice', page_length=100)
undefined4. Avoid N+1 Queries
4. 避免N+1查询问题
python
undefinedpython
undefined❌ N+1 problem
❌ N+1 problem
for name in names:
doc = frappe.get_doc('Customer', name)
for name in names:
doc = frappe.get_doc('Customer', name)
✅ Batch fetch
✅ Batch fetch
docs = frappe.get_all('Customer', filters={'name': ['in', names]})
---docs = frappe.get_all('Customer', filters={'name': ['in', names]})
---Version Differences
版本差异
| Feature | v14 | v15 | v16 |
|---|---|---|---|
| Transaction hooks | ❌ | ✅ | ✅ |
| bulk_update | ❌ | ✅ | ✅ |
| Aggregate syntax | String | String | Dict |
| 特性 | v14 | v15 | v16 |
|---|---|---|---|
| 事务钩子 | ❌ | ✅ | ✅ |
| bulk_update | ❌ | ✅ | ✅ |
| 聚合语法 | 字符串 | 字符串 | 字典 |
v16 Aggregate Syntax
v16 聚合语法
python
undefinedpython
undefinedv14/v15
v14/v15
fields=['count(name) as count']
fields=['count(name) as count']
v16
v16
fields=[{'COUNT': 'name', 'as': 'count'}]
---fields=[{'COUNT': 'name', 'as': 'count'}]
---Reference Files
参考文件
See the folder for detailed documentation:
references/- methods-reference.md - All Database and Document API methods
- query-patterns.md - Filter operators and Query Builder syntax
- caching-patterns.md - Redis cache patterns and @redis_cache
- examples.md - Complete working examples
- anti-patterns.md - Common mistakes and how to avoid them
详见文件夹中的详细文档:
references/- methods-reference.md - 所有数据库与文档API方法
- query-patterns.md - 过滤操作符与查询构建器语法
- caching-patterns.md - Redis缓存模式与@redis_cache
- examples.md - 完整可运行示例
- anti-patterns.md - 常见错误及规避方法
Quick Reference
快速参考
| Action | Method |
|---|---|
| Get document | |
| Cached document | |
| New document | |
| Save document | |
| Insert document | |
| Delete document | |
| Get list | |
| Single value | |
| Single value | |
| Direct update | |
| Exists check | |
| Count records | |
| Raw SQL | |
| Query Builder | |
| 操作 | 方法 |
|---|---|
| 获取文档 | |
| 获取缓存文档 | |
| 创建新文档 | |
| 保存文档 | |
| 插入文档 | |
| 删除文档 | |
| 获取文档列表 | |
| 获取单个字段值 | |
| 获取单文档类型字段值 | |
| 直接更新字段 | |
| 检查文档是否存在 | |
| 统计记录数 | |
| 原生SQL查询 | |
| 查询构建器 | |