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

6.4 KiB
Raw Permalink Blame History

Supabase 查询数据数量的方法

概述

在 Supabase 中,有多种方法可以查询符合条件的数据数量。本文档将介绍各种方法的用法和适用场景。

方法一:使用 count + head 选项(推荐)

这是 Supabase 官方推荐的方法,使用 HEAD 请求只获取数量信息,不返回实际数据。

JavaScript SDK 用法

const { count, error } = await supabase
  .from('table')
  .select('*', { count: 'exact', head: true })
  .eq('status', 'active')

我们的 UTS 实现

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 选项(获取数据+数量)

当你既需要数据又需要数量时使用此方法。

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 聚合函数直接在数据库层面计算。

// 注意:这个语法在当前版本可能不完全支持
const result = await supa
  .from('ak_assignments')
  .select('id.count()')
  .eq('teacher_id', userId)
  .execute()

const count = result.data[0]?.count

特点

  • 灵活性高:支持复杂聚合
  • 语法复杂:需要了解 PostgREST 语法
  • 兼容性:部分客户端不完全支持

Count 选项说明

exact精确计数

{ count: 'exact' }
  • 执行 COUNT(*) 查询
  • 结果完全准确
  • 性能开销较大(大表)

estimated估算计数

{ count: 'estimated' }
  • 使用 PostgreSQL 统计信息估算
  • 结果是近似值
  • 性能开销很小

planned预估计数

{ count: 'planned' }
  • 使用查询计划器估算
  • 结果是预估值
  • 性能开销最小

Head 模式详解

HTTP 方法差异

普通请求GET

GET /rest/v1/table?select=*&count=exact
  • 返回数据 + count
  • 传输完整行数据

Head 请求HEAD

HEAD /rest/v1/table?select=*&count=exact
  • 只返回 headers包含 count
  • 不传输行数据

响应格式对比

普通模式:

{
  data: [...],      // 实际数据
  count: 42,        // 总数
  total: 42,
  status: 200
}

Head 模式:

{
  data: null,       // head 模式不返回数据
  count: 42,        // 从 headers 解析的总数
  total: 42,
  status: 200
}

实际应用示例

教师仪表板统计

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 }
  }
}

分页列表(数据+总数)

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

// 大表100万+行)使用估算
const result = await supa
  .from('large_table')
  .select('*', { count: 'estimated', head: true })
  .execute()

2. 频繁查询使用缓存

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. 添加适当索引

-- 为经常查询的条件添加索引
CREATE INDEX idx_assignments_teacher_status ON ak_assignments(teacher_id, status);
CREATE INDEX idx_users_role ON ak_users(role);

错误处理

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