Supabase数据插入 #

一、基础插入 #

1.1 使用SQL插入 #

sql
-- 插入单条数据
INSERT INTO users (name, email)
VALUES ('John Doe', 'john@example.com');

-- 插入并返回
INSERT INTO users (name, email)
VALUES ('Jane Doe', 'jane@example.com')
RETURNING *;

-- 插入多条数据
INSERT INTO users (name, email)
VALUES 
    ('User 1', 'user1@example.com'),
    ('User 2', 'user2@example.com'),
    ('User 3', 'user3@example.com')
RETURNING *;

1.2 使用客户端插入 #

typescript
// 插入单条数据
const { data, error } = await supabase
  .from('users')
  .insert({
    name: 'John Doe',
    email: 'john@example.com'
  })
  .select()

// data: [{ id: 1, name: 'John Doe', email: 'john@example.com', ... }]

// 插入多条数据
const { data, error } = await supabase
  .from('users')
  .insert([
    { name: 'User 1', email: 'user1@example.com' },
    { name: 'User 2', email: 'user2@example.com' },
    { name: 'User 3', email: 'user3@example.com' }
  ])
  .select()

1.3 插入选项 #

typescript
// 默认返回插入的数据
const { data, error } = await supabase
  .from('users')
  .insert({ name: 'John', email: 'john@example.com' })
  .select()

// 指定返回列
const { data, error } = await supabase
  .from('users')
  .insert({ name: 'John', email: 'john@example.com' })
  .select('id, name')

// 不返回数据
const { error } = await supabase
  .from('users')
  .insert({ name: 'John', email: 'john@example.com' })

二、批量插入 #

2.1 SQL批量插入 #

sql
-- 批量插入
INSERT INTO products (name, price, category)
VALUES
    ('Product 1', 19.99, 'electronics'),
    ('Product 2', 29.99, 'electronics'),
    ('Product 3', 39.99, 'clothing'),
    ('Product 4', 49.99, 'clothing'),
    ('Product 5', 59.99, 'books');

-- 从查询结果插入
INSERT INTO archived_orders (order_id, customer_id, total)
SELECT id, customer_id, total
FROM orders
WHERE created_at < NOW() - INTERVAL '1 year';

2.2 客户端批量插入 #

typescript
// 批量插入
const products = Array.from({ length: 100 }, (_, i) => ({
  name: `Product ${i + 1}`,
  price: Math.random() * 100,
  category: ['electronics', 'clothing', 'books'][i % 3]
}))

const { data, error } = await supabase
  .from('products')
  .insert(products)
  .select()

// 大批量插入建议分批
async function batchInsert(items: any[], batchSize = 100) {
  const results = []
  
  for (let i = 0; i < items.length; i += batchSize) {
    const batch = items.slice(i, i + batchSize)
    const { data, error } = await supabase
      .from('products')
      .insert(batch)
      .select()
    
    if (error) throw error
    results.push(...data)
  }
  
  return results
}

三、Upsert操作 #

3.1 SQL Upsert #

sql
-- ON CONFLICT DO UPDATE
INSERT INTO users (email, name)
VALUES ('john@example.com', 'John Updated')
ON CONFLICT (email) 
DO UPDATE SET 
    name = EXCLUDED.name,
    updated_at = NOW();

-- ON CONFLICT DO NOTHING
INSERT INTO users (email, name)
VALUES ('john@example.com', 'John Doe')
ON CONFLICT (email) DO NOTHING;

3.2 客户端Upsert #

typescript
// 基于唯一键upsert
const { data, error } = await supabase
  .from('users')
  .upsert({
    email: 'john@example.com',
    name: 'John Updated'
  })
  .select()

// 指定唯一键
const { data, error } = await supabase
  .from('users')
  .upsert(
    { email: 'john@example.com', name: 'John' },
    { onConflict: 'email' }
  )
  .select()

// 批量upsert
const { data, error } = await supabase
  .from('products')
  .upsert([
    { id: 1, name: 'Updated Product 1', price: 99.99 },
    { id: 2, name: 'Updated Product 2', price: 149.99 }
  ])
  .select()

3.3 Upsert选项 #

typescript
// ignoreDuplicates: 忽略重复,不更新
const { data, error } = await supabase
  .from('users')
  .upsert(
    { email: 'john@example.com', name: 'John' },
    {
      onConflict: 'email',
      ignoreDuplicates: true
    }
  )

// 默认行为是更新重复数据

四、插入关联数据 #

4.1 插入带外键的数据 #

typescript
// 先获取用户ID
const { data: user } = await supabase
  .from('users')
  .select('id')
  .eq('email', 'john@example.com')
  .single()

// 插入文章
const { data, error } = await supabase
  .from('posts')
  .insert({
    title: 'My First Post',
    content: 'Hello World',
    author_id: user.id
  })
  .select()

4.2 使用SQL事务插入关联数据 #

sql
-- 使用CTE插入关联数据
WITH new_user AS (
    INSERT INTO users (name, email)
    VALUES ('John Doe', 'john@example.com')
    RETURNING id
)
INSERT INTO profiles (user_id, bio)
SELECT id, 'New user bio' FROM new_user;

五、插入JSON数据 #

5.1 插入JSONB #

typescript
// 插入JSON数据
const { data, error } = await supabase
  .from('products')
  .insert({
    name: 'Laptop',
    price: 999.99,
    attributes: {
      color: 'silver',
      weight: 1.5,
      specs: {
        cpu: 'M1 Pro',
        ram: 16,
        storage: 512
      }
    },
    tags: ['electronics', 'computer', 'apple']
  })
  .select()

5.2 SQL插入JSON #

sql
INSERT INTO products (name, price, attributes, tags)
VALUES (
    'Laptop',
    999.99,
    '{"color": "silver", "specs": {"cpu": "M1 Pro"}}'::jsonb,
    ARRAY['electronics', 'computer']
);

六、插入数组数据 #

6.1 插入数组 #

typescript
// 插入数组字段
const { data, error } = await supabase
  .from('posts')
  .insert({
    title: 'Tech Post',
    tags: ['javascript', 'typescript', 'react'],
    ratings: [5, 4, 5, 4, 5]
  })
  .select()

6.2 SQL插入数组 #

sql
INSERT INTO posts (title, tags, ratings)
VALUES (
    'Tech Post',
    ARRAY['javascript', 'typescript', 'react'],
    ARRAY[5, 4, 5, 4, 5]
);

-- 或使用字符串语法
INSERT INTO posts (title, tags)
VALUES ('Tech Post', '{javascript,typescript,react}');

七、错误处理 #

7.1 常见错误 #

typescript
const { data, error } = await supabase
  .from('users')
  .insert({ email: 'john@example.com' })

if (error) {
  switch (error.code) {
    case '23505':
      console.error('数据已存在')
      break
    case '23503':
      console.error('外键约束违反')
      break
    case '23502':
      console.error('必填字段为空')
      break
    case '42501':
      console.error('权限不足')
      break
    default:
      console.error('插入失败:', error.message)
  }
}

7.2 错误处理最佳实践 #

typescript
async function insertUser(userData: { name: string; email: string }) {
  try {
    const { data, error } = await supabase
      .from('users')
      .insert(userData)
      .select()
      .single()

    if (error) throw error
    return { success: true, data }
  } catch (error: any) {
    console.error('Insert error:', error)
    return { 
      success: false, 
      error: error.message || 'Unknown error' 
    }
  }
}

// 使用
const result = await insertUser({ 
  name: 'John', 
  email: 'john@example.com' 
})

if (result.success) {
  console.log('User created:', result.data)
} else {
  console.error('Failed:', result.error)
}

八、性能优化 #

8.1 批量插入优化 #

typescript
// 使用事务批量插入
async function batchInsertWithTransaction(items: any[]) {
  const { data, error } = await supabase.rpc('batch_insert_products', {
    products: items
  })
  
  return { data, error }
}
sql
-- 创建批量插入函数
CREATE OR REPLACE FUNCTION batch_insert_products(products JSONB)
RETURNS SETOF products AS $$
BEGIN
    INSERT INTO products (name, price, category)
    SELECT 
        p->>'name',
        (p->>'price')::numeric,
        p->>'category'
    FROM jsonb_array_elements(products) AS p
    RETURNING *;
END;
$$ LANGUAGE plpgsql;

8.2 插入性能建议 #

text
性能优化建议
├── 使用批量插入代替循环单条插入
├── 关闭不必要的索引
├── 使用COPY命令导入大量数据
├── 分批处理大数据量
└── 考虑使用服务端函数

九、使用RPC插入 #

9.1 创建插入函数 #

sql
-- 创建插入函数
CREATE OR REPLACE FUNCTION create_user_with_profile(
    user_email TEXT,
    user_name TEXT,
    user_bio TEXT DEFAULT NULL
)
RETURNS JSON AS $$
DECLARE
    new_user_id UUID;
    result JSON;
BEGIN
    -- 插入用户
    INSERT INTO auth.users (email, raw_user_meta_data)
    VALUES (user_email, jsonb_build_object('name', user_name))
    RETURNING id INTO new_user_id;
    
    -- 插入资料
    INSERT INTO profiles (id, bio)
    VALUES (new_user_id, user_bio);
    
    -- 返回结果
    SELECT json_build_object(
        'user_id', new_user_id,
        'email', user_email,
        'name', user_name
    ) INTO result;
    
    RETURN result;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

9.2 调用RPC插入 #

typescript
// 调用自定义插入函数
const { data, error } = await supabase.rpc('create_user_with_profile', {
  user_email: 'john@example.com',
  user_name: 'John Doe',
  user_bio: 'Software Developer'
})

console.log('Created user:', data)

十、实战示例 #

10.1 创建博客文章 #

typescript
interface Post {
  title: string
  content: string
  author_id: string
  tags: string[]
  published?: boolean
}

async function createPost(post: Post) {
  const { data, error } = await supabase
    .from('posts')
    .insert({
      title: post.title,
      content: post.content,
      author_id: post.author_id,
      tags: post.tags,
      published: post.published ?? false,
      slug: post.title.toLowerCase().replace(/\s+/g, '-')
    })
    .select()
    .single()

  if (error) throw error
  return data
}

10.2 创建订单 #

typescript
interface OrderItem {
  product_id: number
  quantity: number
  price: number
}

async function createOrder(
  userId: string, 
  items: OrderItem[]
) {
  // 计算总价
  const total = items.reduce(
    (sum, item) => sum + item.price * item.quantity, 
    0
  )

  // 创建订单
  const { data: order, error: orderError } = await supabase
    .from('orders')
    .insert({
      user_id: userId,
      total,
      status: 'pending'
    })
    .select()
    .single()

  if (orderError) throw orderError

  // 创建订单项
  const orderItems = items.map(item => ({
    order_id: order.id,
    ...item
  }))

  const { error: itemsError } = await supabase
    .from('order_items')
    .insert(orderItems)

  if (itemsError) throw itemsError

  return order
}

十一、总结 #

插入操作要点:

操作 方法
单条插入 insert({…})
批量插入 insert([…])
插入或更新 upsert({…})
返回数据 .select()
错误处理 检查error.code

下一步,让我们学习数据查询!

最后更新:2026-03-28