Initial commit of akmon project

This commit is contained in:
2026-01-20 08:04:15 +08:00
commit 77a2bab985
1309 changed files with 343305 additions and 0 deletions

View 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 的最佳实践。