Files
akmon/doc_zhipao/uts_supabase_count_methods_guide.md
2026-01-20 08:04:15 +08:00

279 lines
6.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 的最佳实践。