Supabase数据查询 #
一、基础查询 #
1.1 查询所有数据 #
typescript
// 查询所有列
const { data, error } = await supabase
.from('users')
.select('*')
// 查询指定列
const { data, error } = await supabase
.from('users')
.select('id, name, email')
// 查询并计数
const { data, error, count } = await supabase
.from('users')
.select('*', { count: 'exact' })
console.log(`Total users: ${count}`)
1.2 SQL查询 #
sql
-- 查询所有
SELECT * FROM users;
-- 查询指定列
SELECT id, name, email FROM users;
-- 别名
SELECT name AS user_name, email AS user_email FROM users;
-- 去重
SELECT DISTINCT category FROM products;
-- 限制结果
SELECT * FROM users LIMIT 10;
二、条件过滤 #
2.1 等于过滤 #
typescript
// 等于
const { data } = await supabase
.from('users')
.select('*')
.eq('id', 1)
// 不等于
const { data } = await supabase
.from('users')
.select('*')
.neq('status', 'deleted')
2.2 比较过滤 #
typescript
// 大于
const { data } = await supabase
.from('products')
.select('*')
.gt('price', 100)
// 大于等于
const { data } = await supabase
.from('products')
.select('*')
.gte('price', 100)
// 小于
const { data } = await supabase
.from('products')
.select('*')
.lt('price', 100)
// 小于等于
const { data } = await supabase
.from('products')
.select('*')
.lte('price', 100)
2.3 范围过滤 #
typescript
// 范围
const { data } = await supabase
.from('products')
.select('*')
.gte('price', 50)
.lte('price', 100)
// IN 查询
const { data } = await supabase
.from('products')
.select('*')
.in('category', ['electronics', 'books'])
// NOT IN
const { data } = await supabase
.from('products')
.select('*')
.not('category', 'in', '("deleted","archived")')
2.4 模糊匹配 #
typescript
// LIKE 模糊匹配
const { data } = await supabase
.from('users')
.select('*')
.like('name', '%John%')
// ILIKE 不区分大小写
const { data } = await supabase
.from('users')
.select('*')
.ilike('name', '%john%')
// 前缀匹配
const { data } = await supabase
.from('users')
.select('*')
.startsWith('email', 'admin')
// 后缀匹配
const { data } = await supabase
.from('users')
.select('*')
.endsWith('email', '@gmail.com')
2.5 空值判断 #
typescript
// IS NULL
const { data } = await supabase
.from('users')
.select('*')
.is('deleted_at', null)
// IS NOT NULL
const { data } = await supabase
.from('users')
.select('*')
.not('deleted_at', 'is', null)
2.6 数组过滤 #
typescript
// 包含元素
const { data } = await supabase
.from('posts')
.select('*')
.contains('tags', ['javascript'])
// 包含任意元素
const { data } = await supabase
.from('posts')
.select('*')
.overlaps('tags', ['javascript', 'typescript'])
2.7 JSON过滤 #
typescript
// JSON包含
const { data } = await supabase
.from('products')
.select('*')
.contains('metadata', { featured: true })
// JSON路径
const { data } = await supabase
.from('products')
.select('*')
.eq('metadata->>color', 'red')
三、排序 #
3.1 基础排序 #
typescript
// 升序
const { data } = await supabase
.from('products')
.select('*')
.order('price', { ascending: true })
// 降序
const { data } = await supabase
.from('products')
.select('*')
.order('created_at', { ascending: false })
// 多字段排序
const { data } = await supabase
.from('products')
.select('*')
.order('category', { ascending: true })
.order('price', { ascending: false })
3.2 NULL值排序 #
typescript
// NULL值排在最后
const { data } = await supabase
.from('products')
.select('*')
.order('deleted_at', { ascending: true, nullsFirst: false })
// NULL值排在最前
const { data } = await supabase
.from('products')
.select('*')
.order('deleted_at', { ascending: true, nullsFirst: true })
3.3 SQL排序 #
sql
-- 升序
SELECT * FROM products ORDER BY price ASC;
-- 降序
SELECT * FROM products ORDER BY created_at DESC;
-- 多字段排序
SELECT * FROM products
ORDER BY category ASC, price DESC;
-- NULL处理
SELECT * FROM products
ORDER BY deleted_at NULLS LAST;
四、分页 #
4.1 LIMIT和OFFSET #
typescript
// 限制结果数量
const { data } = await supabase
.from('products')
.select('*')
.limit(10)
// 分页
const page = 2
const pageSize = 10
const { data } = await supabase
.from('products')
.select('*')
.range((page - 1) * pageSize, page * pageSize - 1)
// 或使用limit和offset
const { data } = await supabase
.from('products')
.select('*')
.limit(pageSize)
.offset((page - 1) * pageSize)
4.2 游标分页 #
typescript
// 基于ID的游标分页
async function getProducts(cursor?: number, limit = 10) {
let query = supabase
.from('products')
.select('*')
.order('id', { ascending: true })
.limit(limit)
if (cursor) {
query = query.gt('id', cursor)
}
const { data, error } = await query
const nextCursor = data?.length
? data[data.length - 1].id
: null
return { data, nextCursor }
}
4.3 分页计数 #
typescript
// 获取总数和分页数据
const page = 1
const pageSize = 10
const { data, error, count } = await supabase
.from('products')
.select('*', { count: 'exact' })
.range((page - 1) * pageSize, page * pageSize - 1)
const totalPages = Math.ceil((count || 0) / pageSize)
console.log({
data,
page,
pageSize,
total: count,
totalPages
})
五、关联查询 #
5.1 一对一关联 #
typescript
// 查询用户及其资料
const { data } = await supabase
.from('users')
.select(`
id,
name,
email,
profiles (
bio,
avatar_url
)
`)
// 结果
// [
// {
// id: 1,
// name: 'John',
// email: 'john@example.com',
// profiles: {
// bio: 'Developer',
// avatar_url: '...'
// }
// }
// ]
5.2 一对多关联 #
typescript
// 查询用户及其文章
const { data } = await supabase
.from('users')
.select(`
id,
name,
posts (
id,
title,
created_at
)
`)
// 限制关联数据数量
const { data } = await supabase
.from('users')
.select(`
id,
name,
posts (count)
`)
5.3 多层关联 #
typescript
// 查询文章、作者和评论
const { data } = await supabase
.from('posts')
.select(`
id,
title,
author:users (
id,
name,
email
),
comments (
id,
content,
created_at,
user:users (
id,
name
)
)
`)
5.4 关联过滤 #
typescript
// 过滤关联数据
const { data } = await supabase
.from('users')
.select(`
id,
name,
posts!inner (
id,
title
)
`)
.eq('posts.published', true)
// 使用inner join
const { data } = await supabase
.from('users')
.select(`
id,
name,
posts!inner (id, title)
`)
.is('posts.deleted_at', null)
5.5 SQL关联查询 #
sql
-- INNER JOIN
SELECT u.name, p.title
FROM users u
INNER JOIN posts p ON u.id = p.author_id;
-- LEFT JOIN
SELECT u.name, p.title
FROM users u
LEFT JOIN posts p ON u.id = p.author_id;
-- 多表关联
SELECT
p.title,
u.name AS author,
c.content AS comment
FROM posts p
JOIN users u ON p.author_id = u.id
LEFT JOIN comments c ON p.id = c.post_id;
六、聚合查询 #
6.1 计数 #
typescript
// 计数
const { count } = await supabase
.from('users')
.select('*', { count: 'exact', head: true })
// 分组计数
const { data } = await supabase
.from('products')
.select('category')
6.2 SQL聚合 #
sql
-- 计数
SELECT COUNT(*) FROM users;
SELECT COUNT(*) FROM users WHERE status = 'active';
-- 分组计数
SELECT category, COUNT(*)
FROM products
GROUP BY category;
-- 求和
SELECT SUM(total) FROM orders;
SELECT user_id, SUM(total)
FROM orders
GROUP BY user_id;
-- 平均值
SELECT AVG(price) FROM products;
-- 最大最小值
SELECT MAX(price), MIN(price) FROM products;
-- HAVING过滤
SELECT category, COUNT(*) as count
FROM products
GROUP BY category
HAVING COUNT(*) > 10;
6.3 使用RPC聚合 #
sql
-- 创建聚合函数
CREATE OR REPLACE FUNCTION get_product_stats()
RETURNS JSON AS $$
DECLARE
result JSON;
BEGIN
SELECT json_build_object(
'total', (SELECT COUNT(*) FROM products),
'avg_price', (SELECT AVG(price) FROM products),
'max_price', (SELECT MAX(price) FROM products),
'min_price', (SELECT MIN(price) FROM products),
'by_category', (
SELECT json_agg(row_to_json(t))
FROM (
SELECT category, COUNT(*) as count, AVG(price) as avg_price
FROM products
GROUP BY category
) t
)
) INTO result;
RETURN result;
END;
$$ LANGUAGE plpgsql;
typescript
// 调用聚合函数
const { data } = await supabase.rpc('get_product_stats')
七、复杂查询 #
7.1 子查询 #
sql
-- 子查询
SELECT * FROM products
WHERE price > (SELECT AVG(price) FROM products);
-- IN子查询
SELECT * FROM users
WHERE id IN (SELECT DISTINCT author_id FROM posts);
-- EXISTS子查询
SELECT * FROM users u
WHERE EXISTS (
SELECT 1 FROM posts p
WHERE p.author_id = u.id
);
7.2 CTE (Common Table Expression) #
sql
-- WITH子句
WITH active_users AS (
SELECT * FROM users
WHERE last_login > NOW() - INTERVAL '30 days'
),
user_stats AS (
SELECT user_id, COUNT(*) as post_count
FROM posts
GROUP BY user_id
)
SELECT
u.name,
COALESCE(s.post_count, 0) as posts
FROM active_users u
LEFT JOIN user_stats s ON u.id = s.user_id;
7.3 窗口函数 #
sql
-- 排名
SELECT
name,
price,
RANK() OVER (ORDER BY price DESC) as price_rank
FROM products;
-- 分区排名
SELECT
name,
category,
price,
RANK() OVER (PARTITION BY category ORDER BY price DESC) as category_rank
FROM products;
-- 移动平均
SELECT
created_at::DATE as date,
amount,
AVG(amount) OVER (
ORDER BY created_at
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) as moving_avg
FROM orders;
八、全文搜索 #
8.1 基础全文搜索 #
sql
-- 创建全文索引
CREATE INDEX idx_posts_search ON posts
USING GIN (to_tsvector('english', title || ' ' || content));
-- 全文搜索
SELECT * FROM posts
WHERE to_tsvector('english', title || ' ' || content)
@@ to_tsquery('english', 'javascript & react');
-- 使用plainto_tsquery简化
SELECT * FROM posts
WHERE to_tsvector('english', title || ' ' || content)
@@ plainto_tsquery('english', 'javascript react');
8.2 客户端全文搜索 #
typescript
// 使用RPC进行全文搜索
const { data } = await supabase.rpc('search_posts', {
query: 'javascript react'
})
sql
-- 创建搜索函数
CREATE OR REPLACE FUNCTION search_posts(search_query TEXT)
RETURNS SETOF posts AS $$
BEGIN
RETURN QUERY
SELECT *
FROM posts
WHERE to_tsvector('english', title || ' ' || content)
@@ plainto_tsquery('english', search_query)
ORDER BY ts_rank(
to_tsvector('english', title || ' ' || content),
plainto_tsquery('english', search_query)
) DESC;
END;
$$ LANGUAGE plpgsql;
九、查询优化 #
9.1 使用索引 #
sql
-- 查看查询计划
EXPLAIN ANALYZE SELECT * FROM products WHERE category = 'electronics';
-- 创建索引
CREATE INDEX idx_products_category ON products(category);
-- 复合索引
CREATE INDEX idx_products_category_price ON products(category, price DESC);
9.2 优化建议 #
text
查询优化建议
├── 只查询需要的列
├── 使用索引
├── 避免SELECT *
├── 合理使用LIMIT
├── 使用EXPLAIN分析
├── 避免N+1查询
└── 使用连接池
十、实战示例 #
10.1 产品列表查询 #
typescript
interface ProductFilters {
category?: string
minPrice?: number
maxPrice?: number
search?: string
sortBy?: 'price' | 'created_at' | 'name'
sortOrder?: 'asc' | 'desc'
page?: number
pageSize?: number
}
async function getProducts(filters: ProductFilters) {
const {
category,
minPrice,
maxPrice,
search,
sortBy = 'created_at',
sortOrder = 'desc',
page = 1,
pageSize = 20
} = filters
let query = supabase
.from('products')
.select('*', { count: 'exact' })
// 应用过滤
if (category) {
query = query.eq('category', category)
}
if (minPrice !== undefined) {
query = query.gte('price', minPrice)
}
if (maxPrice !== undefined) {
query = query.lte('price', maxPrice)
}
if (search) {
query = query.ilike('name', `%${search}%`)
}
// 排序
query = query.order(sortBy, {
ascending: sortOrder === 'asc'
})
// 分页
const offset = (page - 1) * pageSize
query = query.range(offset, offset + pageSize - 1)
const { data, error, count } = await query
return {
data,
error,
pagination: {
page,
pageSize,
total: count,
totalPages: Math.ceil((count || 0) / pageSize)
}
}
}
10.2 用户活动统计 #
typescript
async function getUserStats(userId: string) {
const { data, error } = await supabase.rpc('get_user_stats', {
user_id: userId
})
return data
}
sql
CREATE OR REPLACE FUNCTION get_user_stats(user_id UUID)
RETURNS JSON AS $$
DECLARE
result JSON;
BEGIN
SELECT json_build_object(
'posts_count', (SELECT COUNT(*) FROM posts WHERE author_id = user_id),
'comments_count', (SELECT COUNT(*) FROM comments WHERE user_id = user_id),
'total_views', (SELECT COALESCE(SUM(view_count), 0) FROM posts WHERE author_id = user_id),
'recent_posts', (
SELECT json_agg(row_to_json(t))
FROM (
SELECT id, title, created_at
FROM posts
WHERE author_id = user_id
ORDER BY created_at DESC
LIMIT 5
) t
)
) INTO result;
RETURN result;
END;
$$ LANGUAGE plpgsql;
十一、总结 #
查询操作要点:
| 操作 | 方法 |
|---|---|
| 选择列 | select(‘col1, col2’) |
| 过滤 | eq, neq, gt, gte, lt, lte, in, like |
| 排序 | order(‘column’) |
| 分页 | range(), limit(), offset() |
| 关联 | select(‘table1, table2(cols)’) |
| 计数 | select(‘*’, { count: ‘exact’ }) |
下一步,让我们学习数据更新!
最后更新:2026-03-28