数据库辅助

wescode 的数据库辅助功能帮助你完成 SQL 编写、Schema 设计、数据迁移、查询优化和 ORM 代码生成。

功能概览

能力说明
SQL 生成用自然语言描述需求,自动生成 SQL
Schema 设计辅助设计合理的数据库表结构
数据迁移生成安全的迁移脚本
查询优化分析慢查询,建议优化方案
ORM 辅助生成 ORM 模型和查询代码

SQL 生成

自然语言转 SQL

用中文或英文描述需求,AI 自动生成对应的 SQL:

用户:查询最近 30 天内下单金额超过 1000 的用户,按总金额排序

AI:
  SELECT
      u.id,
      u.username,
      u.email,
      SUM(o.total_amount) AS total_spent,
      COUNT(o.id) AS order_count
  FROM users u
  JOIN orders o ON u.id = o.user_id
  WHERE o.created_at >= NOW() - INTERVAL '30 days'
      AND o.status = 'completed'
  GROUP BY u.id, u.username, u.email
  HAVING SUM(o.total_amount) > 1000
  ORDER BY total_spent DESC;

  说明:
  - 只计算已完成的订单(status = 'completed')
  - 使用 SUM 聚合计算总金额
  - HAVING 在 GROUP BY 之后过滤

支持的数据库方言

数据库方言特性
PostgreSQL窗口函数、CTE、JSONB、数组类型
MySQLINDEX HINT、特有函数、编码设置
SQLite简化语法、类型亲和性
SQL ServerTOP、OFFSET-FETCH、T-SQL

AI 会根据项目的数据库类型自动选择正确的方言:

用户:分页查询用户列表

AI(PostgreSQL):
  SELECT * FROM users
  ORDER BY created_at DESC
  LIMIT $1 OFFSET $2;

AI(MySQL):
  SELECT * FROM users
  ORDER BY created_at DESC
  LIMIT ?, ?;

复杂查询生成

用户:统计每个部门的平均工资、最高工资、人数,
      排除实习生,只显示人数超过 5 的部门

AI:
  WITH dept_stats AS (
      SELECT
          d.name AS department,
          AVG(e.salary) AS avg_salary,
          MAX(e.salary) AS max_salary,
          COUNT(*) AS headcount
      FROM employees e
      JOIN departments d ON e.department_id = d.id
      WHERE e.employment_type != 'intern'
      GROUP BY d.id, d.name
      HAVING COUNT(*) > 5
  )
  SELECT
      department,
      ROUND(avg_salary, 2) AS avg_salary,
      max_salary,
      headcount
  FROM dept_stats
  ORDER BY avg_salary DESC;

Schema 设计

从需求设计表结构

用户:设计一个电商系统的订单模块数据库

AI:订单模块 Schema 设计:

  -- 订单主表
  CREATE TABLE orders (
      id          BIGSERIAL PRIMARY KEY,
      order_no    VARCHAR(32) NOT NULL UNIQUE,
      user_id     BIGINT NOT NULL REFERENCES users(id),
      status      VARCHAR(20) NOT NULL DEFAULT 'pending',
      total_amount DECIMAL(12, 2) NOT NULL,
      currency    VARCHAR(3) NOT NULL DEFAULT 'CNY',
      remark      TEXT,
      created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
      updated_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
      paid_at     TIMESTAMPTZ,
      shipped_at  TIMESTAMPTZ,
      completed_at TIMESTAMPTZ
  );

  -- 订单商品表
  CREATE TABLE order_items (
      id          BIGSERIAL PRIMARY KEY,
      order_id    BIGINT NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
      product_id  BIGINT NOT NULL REFERENCES products(id),
      sku_id      BIGINT REFERENCES product_skus(id),
      quantity    INT NOT NULL CHECK (quantity > 0),
      unit_price  DECIMAL(12, 2) NOT NULL,
      subtotal    DECIMAL(12, 2) NOT NULL,
      created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
  );

  -- 订单状态流转记录
  CREATE TABLE order_status_logs (
      id          BIGSERIAL PRIMARY KEY,
      order_id    BIGINT NOT NULL REFERENCES orders(id),
      from_status VARCHAR(20),
      to_status   VARCHAR(20) NOT NULL,
      operator    VARCHAR(64),
      reason      TEXT,
      created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
  );

  -- 索引
  CREATE INDEX idx_orders_user_id ON orders(user_id);
  CREATE INDEX idx_orders_status ON orders(status);
  CREATE INDEX idx_orders_created_at ON orders(created_at);
  CREATE INDEX idx_order_items_order_id ON order_items(order_id);
  CREATE INDEX idx_order_items_product_id ON order_items(product_id);

  设计要点:
  1. 金额使用 DECIMAL(12,2),避免浮点精度问题
  2. 时间字段使用 TIMESTAMPTZ,支持时区
  3. 订单号独立于自增 ID,业务可读
  4. 状态流转有日志表,便于审计
  5. 外键约束保证数据一致性

Schema Review

用户:帮我 review 这个表结构设计

AI:Schema Review 结果:

  ✓ 优点:
  - 使用 BIGSERIAL 而非 INT,扩展性好
  - 金额使用 DECIMAL 类型正确
  - 有适当的索引

  ⚠ 建议改进:
  1. status 字段建议使用 ENUM 或 CHECK 约束
     → ALTER TABLE orders ADD CHECK (status IN ('pending', 'paid', 'shipped', 'completed', 'cancelled'));

  2. 缺少 deleted_at 软删除字段
     → 订单数据不应物理删除

  3. user_id 索引可加 status 组合
     → CREATE INDEX idx_orders_user_status ON orders(user_id, status);

  4. order_no 生成策略需明确
     → 建议:日期前缀 + 随机数(如 20260914-ABC123)

数据迁移

生成迁移脚本

用户:给 users 表添加 phone 和 avatar_url 字段

AI:生成迁移脚本:

  -- migrations/004_add_user_phone_avatar.up.sql
  ALTER TABLE users
      ADD COLUMN phone VARCHAR(20),
      ADD COLUMN avatar_url TEXT;

  CREATE INDEX idx_users_phone ON users(phone);

  -- migrations/004_add_user_phone_avatar.down.sql
  DROP INDEX IF EXISTS idx_users_phone;

  ALTER TABLE users
      DROP COLUMN IF EXISTS phone,
      DROP COLUMN IF EXISTS avatar_url;

  注意事项:
  - phone 允许为空(现有用户未填写)
  - 添加索引用于后续的手机号查询
  - down 脚本提供完整回滚能力

安全迁移建议

AI 会根据数据量和变更类型给出安全建议:

AI:⚠ 安全提醒:

  此迁移涉及大表变更(users 表约 100 万行)。

  PostgreSQL 中 ADD COLUMN(无默认值)是轻量操作,
  不会锁表。但如果添加 NOT NULL + DEFAULT:

  ✗ 危险写法:
    ALTER TABLE users ADD COLUMN phone VARCHAR(20) NOT NULL DEFAULT '';
    → 会重写整张表,锁定数分钟

  ✓ 安全写法(分步):
    1. ALTER TABLE users ADD COLUMN phone VARCHAR(20);
    2. UPDATE users SET phone = '' WHERE phone IS NULL;  -- 分批执行
    3. ALTER TABLE users ALTER COLUMN phone SET NOT NULL;
    4. ALTER TABLE users ALTER COLUMN phone SET DEFAULT '';

查询优化

分析慢查询

用户:这个查询很慢,帮我优化
SELECT * FROM orders WHERE user_id = 123 ORDER BY created_at DESC;

AI:查询分析:

  当前执行计划:
  Seq Scan on orders  (cost=0.00..15234.00 rows=500000)
  → 全表扫描!缺少 user_id 索引

  优化方案:

  方案 1:添加索引(推荐)
  CREATE INDEX idx_orders_user_created
    ON orders(user_id, created_at DESC);
  → 预期:Seq Scan → Index Scan,ms 级响应

  方案 2:如果只需要最新 N 条
  SELECT * FROM orders
  WHERE user_id = 123
  ORDER BY created_at DESC
  LIMIT 20;
  → 配合索引,效率极高

  方案 3:避免 SELECT *
  SELECT id, order_no, status, total_amount, created_at
  FROM orders
  WHERE user_id = 123
  ORDER BY created_at DESC;
  → 减少数据传输量

  综合建议:方案 1 + 方案 2 + 方案 3 一起使用

N+1 查询检测

用户:这段代码性能不好,帮我看看

AI:检测到 N+1 查询问题:

  当前代码:
  users := db.Find(&users)           // 1 次查询
  for _, u := range users {
      db.Find(&u.Orders, "user_id = ?", u.ID)  // N 次查询
  }
  → 如果有 100 个用户,就会执行 101 次查询!

  优化方案:使用 Preload 一次性加载
  db.Preload("Orders").Find(&users)
  → 只需 2 次查询(1 次 users + 1 次 orders WHERE user_id IN (...))

ORM 辅助

生成 Model

用户:根据 orders 表生成 GORM model

AI:
  // internal/model/order.go

  type Order struct {
      ID          int64          `gorm:"primaryKey;autoIncrement" json:"id"`
      OrderNo     string         `gorm:"uniqueIndex;size:32;not null" json:"order_no"`
      UserID      int64          `gorm:"index;not null" json:"user_id"`
      Status      string         `gorm:"size:20;not null;default:pending" json:"status"`
      TotalAmount decimal.Decimal `gorm:"type:decimal(12,2);not null" json:"total_amount"`
      Currency    string         `gorm:"size:3;not null;default:CNY" json:"currency"`
      Remark      *string        `json:"remark,omitempty"`
      CreatedAt   time.Time      `gorm:"autoCreateTime" json:"created_at"`
      UpdatedAt   time.Time      `gorm:"autoUpdateTime" json:"updated_at"`
      PaidAt      *time.Time     `json:"paid_at,omitempty"`

      // 关联
      User       *User       `gorm:"foreignKey:UserID" json:"user,omitempty"`
      OrderItems []OrderItem `gorm:"foreignKey:OrderID" json:"items,omitempty"`
  }

  func (Order) TableName() string {
      return "orders"
  }

生成 Repository

用户:为 Order model 生成 repository

AI:
  // internal/repository/order.go

  type OrderRepository struct {
      db *gorm.DB
  }

  func NewOrderRepository(db *gorm.DB) *OrderRepository {
      return &OrderRepository{db: db}
  }

  func (r *OrderRepository) Create(ctx context.Context, order *model.Order) error {
      return r.db.WithContext(ctx).Create(order).Error
  }

  func (r *OrderRepository) FindByID(ctx context.Context, id int64) (*model.Order, error) {
      var order model.Order
      err := r.db.WithContext(ctx).
          Preload("OrderItems").
          First(&order, id).Error
      if err != nil {
          return nil, err
      }
      return &order, nil
  }

  func (r *OrderRepository) ListByUserID(ctx context.Context, userID int64, page, pageSize int) ([]model.Order, int64, error) {
      var orders []model.Order
      var total int64

      query := r.db.WithContext(ctx).
          Where("user_id = ?", userID)

      query.Model(&model.Order{}).Count(&total)

      err := query.
          Order("created_at DESC").
          Offset((page - 1) * pageSize).
          Limit(pageSize).
          Find(&orders).Error

      return orders, total, err
  }

  // ... 更多方法

注意事项