Initial commit of akmon project
This commit is contained in:
278
doc_zhipao/uts_supabase_count_methods_guide.md
Normal file
278
doc_zhipao/uts_supabase_count_methods_guide.md
Normal file
@@ -0,0 +1,278 @@
|
||||
# Supabase 查询数据数量的方法
|
||||
|
||||
## 概述
|
||||
|
||||
在 Supabase 中,有多种方法可以查询符合条件的数据数量。本文档将介绍各种方法的用法和适用场景。
|
||||
|
||||
## 方法一:使用 count + head 选项(推荐)
|
||||
|
||||
这是 Supabase 官方推荐的方法,使用 HEAD 请求只获取数量信息,不返回实际数据。
|
||||
|
||||
### JavaScript SDK 用法
|
||||
```javascript
|
||||
const { count, error } = await supabase
|
||||
.from('table')
|
||||
.select('*', { count: 'exact', head: true })
|
||||
.eq('status', 'active')
|
||||
```
|
||||
|
||||
### 我们的 UTS 实现
|
||||
```typescript
|
||||
const result = await supa
|
||||
.from('ak_assignments')
|
||||
.select('*', { count: 'exact', head: true })
|
||||
.eq('teacher_id', userId)
|
||||
.execute()
|
||||
|
||||
const count = result.count // 从 count 属性获取数量
|
||||
```
|
||||
|
||||
### 特点
|
||||
- ✅ **性能最佳**:使用 HEAD 请求,不传输数据
|
||||
- ✅ **网络开销小**:只返回 count,不返回行数据
|
||||
- ✅ **官方推荐**:符合 Supabase 最佳实践
|
||||
- ✅ **类型安全**:返回 `{ count: number }`
|
||||
|
||||
## 方法二:使用 count 选项(获取数据+数量)
|
||||
|
||||
当你既需要数据又需要数量时使用此方法。
|
||||
|
||||
```typescript
|
||||
const result = await supa
|
||||
.from('ak_assignments')
|
||||
.select('*', { count: 'exact' })
|
||||
.eq('teacher_id', userId)
|
||||
.limit(10)
|
||||
.execute()
|
||||
|
||||
const data = result.data // 实际数据
|
||||
const total = result.total // 总数量
|
||||
```
|
||||
|
||||
### 特点
|
||||
- ✅ **一次请求**:同时获取数据和总数
|
||||
- ✅ **适合分页**:支持 limit/offset 分页
|
||||
- ❌ **网络开销大**:返回完整数据
|
||||
|
||||
## 方法三:聚合函数(PostgREST)
|
||||
|
||||
使用 PostgreSQL 聚合函数直接在数据库层面计算。
|
||||
|
||||
```typescript
|
||||
// 注意:这个语法在当前版本可能不完全支持
|
||||
const result = await supa
|
||||
.from('ak_assignments')
|
||||
.select('id.count()')
|
||||
.eq('teacher_id', userId)
|
||||
.execute()
|
||||
|
||||
const count = result.data[0]?.count
|
||||
```
|
||||
|
||||
### 特点
|
||||
- ✅ **灵活性高**:支持复杂聚合
|
||||
- ❌ **语法复杂**:需要了解 PostgREST 语法
|
||||
- ❌ **兼容性**:部分客户端不完全支持
|
||||
|
||||
## Count 选项说明
|
||||
|
||||
### exact(精确计数)
|
||||
```typescript
|
||||
{ count: 'exact' }
|
||||
```
|
||||
- 执行 `COUNT(*)` 查询
|
||||
- 结果完全准确
|
||||
- 性能开销较大(大表)
|
||||
|
||||
### estimated(估算计数)
|
||||
```typescript
|
||||
{ count: 'estimated' }
|
||||
```
|
||||
- 使用 PostgreSQL 统计信息估算
|
||||
- 结果是近似值
|
||||
- 性能开销很小
|
||||
|
||||
### planned(预估计数)
|
||||
```typescript
|
||||
{ count: 'planned' }
|
||||
```
|
||||
- 使用查询计划器估算
|
||||
- 结果是预估值
|
||||
- 性能开销最小
|
||||
|
||||
## Head 模式详解
|
||||
|
||||
### HTTP 方法差异
|
||||
|
||||
**普通请求(GET):**
|
||||
```
|
||||
GET /rest/v1/table?select=*&count=exact
|
||||
```
|
||||
- 返回数据 + count
|
||||
- 传输完整行数据
|
||||
|
||||
**Head 请求(HEAD):**
|
||||
```
|
||||
HEAD /rest/v1/table?select=*&count=exact
|
||||
```
|
||||
- 只返回 headers(包含 count)
|
||||
- 不传输行数据
|
||||
|
||||
### 响应格式对比
|
||||
|
||||
**普通模式:**
|
||||
```typescript
|
||||
{
|
||||
data: [...], // 实际数据
|
||||
count: 42, // 总数
|
||||
total: 42,
|
||||
status: 200
|
||||
}
|
||||
```
|
||||
|
||||
**Head 模式:**
|
||||
```typescript
|
||||
{
|
||||
data: null, // head 模式不返回数据
|
||||
count: 42, // 从 headers 解析的总数
|
||||
total: 42,
|
||||
status: 200
|
||||
}
|
||||
```
|
||||
|
||||
## 实际应用示例
|
||||
|
||||
### 教师仪表板统计
|
||||
|
||||
```typescript
|
||||
const loadTeacherStats = async (teacherId: string) => {
|
||||
try {
|
||||
// 并行执行多个 count 查询
|
||||
const [totalAssignments, completedAssignments, pendingReview] = await Promise.all([
|
||||
// 总作业数
|
||||
supa.from('ak_assignments')
|
||||
.select('*', { count: 'exact', head: true })
|
||||
.eq('teacher_id', teacherId)
|
||||
.execute(),
|
||||
|
||||
// 已完成作业数
|
||||
supa.from('ak_assignments')
|
||||
.select('*', { count: 'exact', head: true })
|
||||
.eq('teacher_id', teacherId)
|
||||
.eq('status', 'completed')
|
||||
.execute(),
|
||||
|
||||
// 待批改作业数
|
||||
supa.from('ak_assignments')
|
||||
.select('*', { count: 'exact', head: true })
|
||||
.eq('teacher_id', teacherId)
|
||||
.eq('status', 'submitted')
|
||||
.execute()
|
||||
])
|
||||
|
||||
return {
|
||||
total: totalAssignments.count ?? 0,
|
||||
completed: completedAssignments.count ?? 0,
|
||||
pending: pendingReview.count ?? 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载统计失败:', error)
|
||||
return { total: 0, completed: 0, pending: 0 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 分页列表(数据+总数)
|
||||
|
||||
```typescript
|
||||
const loadAssignmentList = async (page: number, pageSize: number) => {
|
||||
const result = await supa
|
||||
.from('ak_assignments')
|
||||
.select('*', { count: 'exact' })
|
||||
.eq('teacher_id', userId)
|
||||
.order('created_at', { ascending: false })
|
||||
.range((page - 1) * pageSize, page * pageSize - 1)
|
||||
.execute()
|
||||
|
||||
return {
|
||||
data: result.data,
|
||||
total: result.total,
|
||||
hasMore: result.hasmore
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 性能建议
|
||||
|
||||
### 1. 大表查询使用 estimated
|
||||
```typescript
|
||||
// 大表(100万+行)使用估算
|
||||
const result = await supa
|
||||
.from('large_table')
|
||||
.select('*', { count: 'estimated', head: true })
|
||||
.execute()
|
||||
```
|
||||
|
||||
### 2. 频繁查询使用缓存
|
||||
```typescript
|
||||
const countCache = new Map()
|
||||
|
||||
const getCachedCount = async (key: string, queryFn: () => Promise<any>) => {
|
||||
if (countCache.has(key)) {
|
||||
return countCache.get(key)
|
||||
}
|
||||
|
||||
const result = await queryFn()
|
||||
countCache.set(key, result.count)
|
||||
|
||||
// 5分钟后清除缓存
|
||||
setTimeout(() => countCache.delete(key), 5 * 60 * 1000)
|
||||
|
||||
return result.count
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 添加适当索引
|
||||
```sql
|
||||
-- 为经常查询的条件添加索引
|
||||
CREATE INDEX idx_assignments_teacher_status ON ak_assignments(teacher_id, status);
|
||||
CREATE INDEX idx_users_role ON ak_users(role);
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
```typescript
|
||||
const safeGetCount = async (queryBuilder: any): Promise<number> => {
|
||||
try {
|
||||
const result = await queryBuilder.execute()
|
||||
|
||||
if (result.status >= 200 && result.status < 300) {
|
||||
return result.count ?? 0
|
||||
}
|
||||
|
||||
console.warn('Count 查询返回错误状态:', result.status)
|
||||
return 0
|
||||
} catch (error) {
|
||||
console.error('Count 查询异常:', error)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
const totalCount = await safeGetCount(
|
||||
supa.from('ak_assignments')
|
||||
.select('*', { count: 'exact', head: true })
|
||||
.eq('teacher_id', userId)
|
||||
)
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
对于 **只需要数量** 的场景:
|
||||
- ✅ **首选**:`select('*', { count: 'exact', head: true })`
|
||||
- ✅ **大表**:`select('*', { count: 'estimated', head: true })`
|
||||
|
||||
对于 **需要数据+数量** 的场景:
|
||||
- ✅ **首选**:`select('*', { count: 'exact' })`
|
||||
|
||||
这样的实现既保证了性能,又符合 Supabase 的最佳实践。
|
||||
Reference in New Issue
Block a user