在Supabase JS查询中实现食谱场景的计数与范围分页
实现Supabase场景食谱的分页查询(含总计数)
我用Next.js + Supabase开发食谱网站,occasions表和recipes表通过recipes_occasions关联。目前能查询指定场景下的所有食谱,但需要实现分页功能——既要获取该场景下的食谱总数量,又能指定分页数据范围。当前代码如下,想在标记位置添加计数和范围控制:
.from("occasions") .select( `*, recipes_occasions( recipes( // 想在这里获取该场景下的食谱总数量 recipeId, title, slug, totalTime, yield, ingredients, recipes_categories ( categories(title, slug) ), recipes_diets ( diets(title, slug) ), recipes_cuisines ( cuisines(title, slug, code) ) ), )` ) .eq("slug", slug ) .range(0,4) // 想在这里指定动态的分页范围(比如range(12, 24)或range(200, 212))
解决方案:两种实现方式,无需强制使用RPC
方案一:拆分查询(快速实现)
分两次查询,分别获取总计数和分页数据,逻辑简单直接。
1. 查询场景下的食谱总数
单独查询关联表,拿到该场景对应的所有食谱数量:
// 假设recipes_occasions表用occasion_id关联occasions.id,根据你的实际字段调整 const countRes = await supabase .from('recipes_occasions') .select('id', { count: 'exact', head: true }) .eq('occasion_id', (await supabase.from('occasions').select('id').eq('slug', slug)).data[0].id); const totalRecipes = countRes.count;
如果recipes_occasions里直接存了occasion_slug,可以简化为:
const countRes = await supabase .from('recipes_occasions') .select('id', { count: 'exact', head: true }) .eq('occasion_slug', slug);
2. 查询分页后的食谱数据
根据前端传入的页码、每页条数,动态计算range的起止值:
const currentPage = 2; // 前端传入的当前页码 const perPage = 12; // 每页展示的食谱数 const start = (currentPage - 1) * perPage; const end = currentPage * perPage - 1; const { data: occasionData, error } = await supabase .from("occasions") .select( `*, recipes_occasions( recipes( recipeId, title, slug, totalTime, yield, ingredients, recipes_categories ( categories(title, slug) ), recipes_diets ( diets(title, slug) ), recipes_cuisines ( cuisines(title, slug, code) ) ) )` ) .eq("slug", slug) .order('recipes_occasions.recipes.recipeId') // 加排序保证分页顺序稳定 .range(start, end); // 动态传入分页范围
方案二:用RPC优化(一次请求拿全数据)
如果想减少网络请求,可以写PostgreSQL函数(RPC),一次性返回总计数和分页数据。
1. 在Supabase控制台创建RPC函数
打开Supabase的SQL编辑器,执行以下SQL(注意根据你的表字段调整关联逻辑):
CREATE OR REPLACE FUNCTION get_occasion_recipes(occasion_slug text, page_num int, per_page int) RETURNS TABLE( occasion_info occasions, total_count int, paginated_recipes json ) AS $$ DECLARE total int; start_offset int := (page_num - 1) * per_page; BEGIN -- 先获取总计数 SELECT COUNT(DISTINCT r.recipeId) INTO total FROM occasions o JOIN recipes_occasions ro ON o.id = ro.occasion_id JOIN recipes r ON ro.recipe_id = r.recipeId WHERE o.slug = occasion_slug; -- 获取分页后的食谱数据 RETURN QUERY SELECT o.*, total, json_agg( json_build_object( 'recipeId', r.recipeId, 'title', r.title, 'slug', r.slug, 'totalTime', r.totalTime, 'yield', r.yield, 'ingredients', r.ingredients, 'categories', (SELECT json_agg(json_build_object('title', c.title, 'slug', c.slug)) FROM recipes_categories rc JOIN categories c ON rc.category_id = c.id WHERE rc.recipe_id = r.recipeId), 'diets', (SELECT json_agg(json_build_object('title', d.title, 'slug', d.slug)) FROM recipes_diets rd JOIN diets d ON rd.diet_id = d.id WHERE rd.recipe_id = r.recipeId), 'cuisines', (SELECT json_agg(json_build_object('title', cu.title, 'slug', cu.slug, 'code', cu.code)) FROM recipes_cuisines rcu JOIN cuisines cu ON rcu.cuisine_id = cu.id WHERE rcu.recipe_id = r.recipeId) ) ) AS paginated_recipes FROM occasions o JOIN recipes_occasions ro ON o.id = ro.occasion_id JOIN recipes r ON ro.recipe_id = r.recipeId WHERE o.slug = occasion_slug ORDER BY r.recipeId LIMIT per_page OFFSET start_offset GROUP BY o.id; END; $$ LANGUAGE plpgsql SECURITY DEFINER;
2. 在Next.js中调用RPC
const { data, error } = await supabase.rpc('get_occasion_recipes', { occasion_slug: slug, page_num: 2, per_page: 12 }); // 返回的data结构包含:occasion_info(场景信息)、total_count(总食谱数)、paginated_recipes(分页食谱列表)
关键注意点
- 分页必须加
order by,否则每次返回的结果顺序可能不一致,导致分页混乱。 - 方案一适合快速迭代,方案二更适合数据量大、追求性能的场景。
- 所有表字段、关联逻辑请根据你的实际数据库结构调整,比如外键名、字段名。
内容的提问来源于stack exchange,提问作者Yesthe Cia
相关产品推荐
相关产品推荐

