Skip to content

Sequelize 基础查询操作 - findAll、findOne、count等方法详解

发布时间:2024-02-12
作者:一介布衣
标签:Sequelize, 查询操作, findAll, 数据查询

前言

前面两篇文章我们学习了模型定义和实例操作,今天咱们来学习 Sequelize 的查询功能。说实话,查询是我们日常开发中用得最多的功能,掌握好了查询,基本上就掌握了 Sequelize 的核心。

我记得刚开始用 Sequelize 的时候,总是搞不清楚什么时候用 findAll,什么时候用 findOne,还有各种查询条件的写法,经常要翻文档。后来慢慢总结,发现其实是有规律的。

今天我就把这些查询方法和技巧整理出来,让大家能够快速上手,写出高效的查询代码。

基础查询方法

1. findAll() - 查询多条记录

这是最常用的查询方法:

javascript
// 查询所有用户
const users = await User.findAll();

// 带条件查询
const activeUsers = await User.findAll({
  where: {
    status: 'active'
  }
});

// 查询指定字段
const users = await User.findAll({
  attributes: ['id', 'name', 'email']
});

// 排除某些字段
const users = await User.findAll({
  attributes: { exclude: ['password', 'deletedAt'] }
});

实际应用场景

javascript
// 用户列表页面
async function getUserList(page = 1, limit = 10) {
  const offset = (page - 1) * limit;
  
  return await User.findAll({
    attributes: ['id', 'name', 'email', 'createdAt'],
    where: { status: 'active' },
    order: [['createdAt', 'DESC']],
    limit,
    offset
  });
}

2. findOne() - 查询单条记录

查询第一条符合条件的记录:

javascript
// 根据条件查询第一个用户
const user = await User.findOne({
  where: { email: 'test@example.com' }
});

if (user) {
  console.log('找到用户:', user.name);
} else {
  console.log('用户不存在');
}

// 查询最新的用户
const latestUser = await User.findOne({
  order: [['createdAt', 'DESC']]
});

3. findByPk() - 根据主键查询

这是查询单条记录最高效的方法:

javascript
// 根据主键查询
const user = await User.findByPk(1);

// 指定查询字段
const user = await User.findByPk(1, {
  attributes: ['id', 'name', 'email']
});

// 包含关联数据
const user = await User.findByPk(1, {
  include: ['posts', 'profile']
});

4. findAndCountAll() - 查询并统计

分页查询的最佳选择:

javascript
const result = await User.findAndCountAll({
  where: { status: 'active' },
  limit: 10,
  offset: 0,
  order: [['createdAt', 'DESC']]
});

console.log('总数:', result.count);
console.log('当前页数据:', result.rows);

// 计算分页信息
const totalPages = Math.ceil(result.count / 10);

实际分页实现

javascript
async function getPaginatedUsers(page = 1, pageSize = 10, search = '') {
  const offset = (page - 1) * pageSize;
  
  const whereClause = {};
  if (search) {
    whereClause[Op.or] = [
      { name: { [Op.like]: `%\${search}%` } },
      { email: { [Op.like]: `%\${search}%` } }
    ];
  }
  
  const result = await User.findAndCountAll({
    where: whereClause,
    limit: pageSize,
    offset,
    order: [['createdAt', 'DESC']],
    attributes: { exclude: ['password'] }
  });
  
  return {
    data: result.rows,
    pagination: {
      total: result.count,
      page,
      pageSize,
      totalPages: Math.ceil(result.count / pageSize)
    }
  };
}

5. count() - 统计记录数

javascript
// 统计所有用户
const totalUsers = await User.count();

// 带条件统计
const activeUsers = await User.count({
  where: { status: 'active' }
});

// 按字段分组统计
const statusCount = await User.count({
  group: 'status'
});
// 结果: [{ status: 'active', count: 10 }, { status: 'inactive', count: 5 }]

6. max() / min() / sum() - 聚合函数

javascript
// 最大值
const maxAge = await User.max('age');

// 最小值
const minAge = await User.min('age');

// 求和
const totalScore = await User.sum('score');

// 带条件的聚合
const avgAge = await User.aggregate('age', 'avg', {
  where: { status: 'active' }
});

查询条件详解

1. 基本条件

javascript
// 等于
const users = await User.findAll({
  where: { status: 'active' }
});

// 多个条件(AND)
const users = await User.findAll({
  where: {
    status: 'active',
    age: 25
  }
});

// 数组条件(IN)
const users = await User.findAll({
  where: {
    id: [1, 2, 3, 4, 5]
  }
});

2. 操作符条件

javascript
const { Op } = require('sequelize');

// 比较操作符
const users = await User.findAll({
  where: {
    age: {
      [Op.gt]: 18,        // 大于
      [Op.gte]: 18,       // 大于等于
      [Op.lt]: 65,        // 小于
      [Op.lte]: 65,       // 小于等于
      [Op.ne]: null       // 不等于
    }
  }
});

// 范围查询
const users = await User.findAll({
  where: {
    age: {
      [Op.between]: [18, 65]    // 在范围内
    },
    status: {
      [Op.in]: ['active', 'pending']  // 在列表中
    },
    email: {
      [Op.notIn]: ['banned@example.com']  // 不在列表中
    }
  }
});

// 模糊查询
const users = await User.findAll({
  where: {
    name: {
      [Op.like]: '%张%',        // 包含
      [Op.notLike]: '%admin%',  // 不包含
      [Op.iLike]: '%ZHANG%',    // 忽略大小写包含(PostgreSQL)
      [Op.startsWith]: '张',    // 以...开始
      [Op.endsWith]: '三',      // 以...结束
      [Op.substring]: '张三'    // 包含子字符串
    }
  }
});

// 空值查询
const users = await User.findAll({
  where: {
    deletedAt: {
      [Op.is]: null           // 是 null
    },
    avatar: {
      [Op.not]: null          // 不是 null
    }
  }
});

3. 逻辑操作符

javascript
// OR 条件
const users = await User.findAll({
  where: {
    [Op.or]: [
      { status: 'active' },
      { isVip: true }
    ]
  }
});

// 复杂逻辑组合
const users = await User.findAll({
  where: {
    [Op.and]: [
      {
        [Op.or]: [
          { status: 'active' },
          { status: 'pending' }
        ]
      },
      {
        age: { [Op.gte]: 18 }
      }
    ]
  }
});

// NOT 条件
const users = await User.findAll({
  where: {
    [Op.not]: {
      status: 'banned'
    }
  }
});

4. 日期查询

javascript
// 日期比较
const users = await User.findAll({
  where: {
    createdAt: {
      [Op.gte]: new Date('2024-01-01'),
      [Op.lt]: new Date('2024-12-31')
    }
  }
});

// 相对日期
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
const recentUsers = await User.findAll({
  where: {
    lastLoginAt: {
      [Op.gte]: thirtyDaysAgo
    }
  }
});

// 日期函数(MySQL)
const todayUsers = await User.findAll({
  where: sequelize.where(
    sequelize.fn('DATE', sequelize.col('createdAt')),
    new Date().toISOString().split('T')[0]
  )
});

排序和限制

1. 排序(ORDER BY)

javascript
// 单字段排序
const users = await User.findAll({
  order: [['createdAt', 'DESC']]
});

// 多字段排序
const users = await User.findAll({
  order: [
    ['status', 'ASC'],
    ['createdAt', 'DESC']
  ]
});

// 关联表排序
const users = await User.findAll({
  include: ['posts'],
  order: [
    ['name', 'ASC'],
    ['posts', 'createdAt', 'DESC']
  ]
});

// 函数排序
const users = await User.findAll({
  order: [
    sequelize.fn('RAND')  // 随机排序(MySQL)
  ]
});

2. 限制和偏移(LIMIT & OFFSET)

javascript
// 限制数量
const users = await User.findAll({
  limit: 10
});

// 分页
const users = await User.findAll({
  limit: 10,
  offset: 20  // 跳过前20条
});

// 实用的分页函数
function getPagination(page, size) {
  const limit = size ? +size : 10;
  const offset = page ? page * limit : 0;
  return { limit, offset };
}

const { limit, offset } = getPagination(2, 10);
const users = await User.findAll({ limit, offset });

字段选择和别名

1. 选择特定字段

javascript
// 只查询指定字段
const users = await User.findAll({
  attributes: ['id', 'name', 'email']
});

// 排除某些字段
const users = await User.findAll({
  attributes: { exclude: ['password', 'deletedAt'] }
});

// 字段别名
const users = await User.findAll({
  attributes: [
    'id',
    ['name', 'userName'],           // name 字段别名为 userName
    ['email', 'userEmail']
  ]
});

2. 计算字段

javascript
// 聚合计算
const users = await User.findAll({
  attributes: [
    'id',
    'name',
    [sequelize.fn('COUNT', sequelize.col('posts.id')), 'postCount']
  ],
  include: ['posts'],
  group: ['User.id']
});

// 字符串拼接
const users = await User.findAll({
  attributes: [
    'id',
    [sequelize.fn('CONCAT', sequelize.col('firstName'), ' ', sequelize.col('lastName')), 'fullName']
  ]
});

// 条件字段
const users = await User.findAll({
  attributes: [
    'id',
    'name',
    [
      sequelize.literal(`CASE WHEN age >= 18 THEN 'adult' ELSE 'minor' END`),
      'ageGroup'
    ]
  ]
});

分组查询

1. 基本分组

javascript
// 按状态分组统计
const statusStats = await User.findAll({
  attributes: [
    'status',
    [sequelize.fn('COUNT', sequelize.col('id')), 'count']
  ],
  group: ['status']
});

// 按日期分组
const dailyStats = await User.findAll({
  attributes: [
    [sequelize.fn('DATE', sequelize.col('createdAt')), 'date'],
    [sequelize.fn('COUNT', sequelize.col('id')), 'count']
  ],
  group: [sequelize.fn('DATE', sequelize.col('createdAt'))]
});

2. HAVING 条件

javascript
// 查询文章数量大于5的用户
const activeAuthors = await User.findAll({
  attributes: [
    'id',
    'name',
    [sequelize.fn('COUNT', sequelize.col('posts.id')), 'postCount']
  ],
  include: ['posts'],
  group: ['User.id'],
  having: sequelize.where(
    sequelize.fn('COUNT', sequelize.col('posts.id')),
    Op.gt,
    5
  )
});

原始查询

1. 使用 raw 选项

javascript
// 返回原始数据,不创建模型实例
const users = await User.findAll({
  raw: true,
  attributes: ['id', 'name', 'email']
});

// 性能对比
console.time('with instances');
const usersWithInstances = await User.findAll();
console.timeEnd('with instances');

console.time('raw data');
const rawUsers = await User.findAll({ raw: true });
console.timeEnd('raw data');

2. 直接 SQL 查询

javascript
// 简单查询
const users = await sequelize.query(
  'SELECT id, name, email FROM users WHERE status = ?',
  {
    replacements: ['active'],
    type: QueryTypes.SELECT
  }
);

// 复杂统计查询
const stats = await sequelize.query(`
  SELECT 
    DATE(created_at) as date,
    COUNT(*) as user_count,
    COUNT(CASE WHEN status = 'active' THEN 1 END) as active_count
  FROM users 
  WHERE created_at >= :startDate
  GROUP BY DATE(created_at)
  ORDER BY date DESC
`, {
  replacements: { startDate: '2024-01-01' },
  type: QueryTypes.SELECT
});

性能优化技巧

1. 索引优化

javascript
// 确保查询字段有索引
const users = await User.findAll({
  where: {
    email: 'test@example.com',  // email 字段应该有索引
    status: 'active'            // status 字段应该有索引
  }
});

// 复合索引查询
const users = await User.findAll({
  where: {
    status: 'active',
    createdAt: {
      [Op.gte]: new Date('2024-01-01')
    }
  }
  // 应该在 (status, created_at) 上建立复合索引
});

2. 查询优化

javascript
// 避免查询不必要的字段
const users = await User.findAll({
  attributes: ['id', 'name'],  // 只查询需要的字段
  where: { status: 'active' }
});

// 使用 EXISTS 代替 IN(大数据量时)
const usersWithPosts = await User.findAll({
  where: {
    id: {
      [Op.in]: sequelize.literal(`
        (SELECT DISTINCT user_id FROM posts WHERE status = 'published')
      `)
    }
  }
});

3. 分页优化

javascript
// 大偏移量优化
async function getOptimizedPagination(lastId = 0, limit = 10) {
  return await User.findAll({
    where: {
      id: { [Op.gt]: lastId }  // 使用游标分页
    },
    order: [['id', 'ASC']],
    limit
  });
}

实战案例

用户搜索功能

javascript
async function searchUsers(params) {
  const { keyword, status, ageRange, page = 1, limit = 10 } = params;
  
  const whereClause = {};
  
  // 关键词搜索
  if (keyword) {
    whereClause[Op.or] = [
      { name: { [Op.like]: `%\${keyword}%` } },
      { email: { [Op.like]: `%\${keyword}%` } }
    ];
  }
  
  // 状态筛选
  if (status) {
    whereClause.status = status;
  }
  
  // 年龄范围
  if (ageRange) {
    whereClause.age = {
      [Op.between]: ageRange
    };
  }
  
  const { count, rows } = await User.findAndCountAll({
    where: whereClause,
    attributes: { exclude: ['password'] },
    order: [['createdAt', 'DESC']],
    limit,
    offset: (page - 1) * limit
  });
  
  return {
    users: rows,
    total: count,
    page,
    totalPages: Math.ceil(count / limit)
  };
}

数据统计报表

javascript
async function getUserStatistics(startDate, endDate) {
  // 用户注册趋势
  const registrationTrend = await User.findAll({
    attributes: [
      [sequelize.fn('DATE', sequelize.col('createdAt')), 'date'],
      [sequelize.fn('COUNT', sequelize.col('id')), 'count']
    ],
    where: {
      createdAt: {
        [Op.between]: [startDate, endDate]
      }
    },
    group: [sequelize.fn('DATE', sequelize.col('createdAt'))],
    order: [[sequelize.fn('DATE', sequelize.col('createdAt')), 'ASC']],
    raw: true
  });
  
  // 用户状态分布
  const statusDistribution = await User.findAll({
    attributes: [
      'status',
      [sequelize.fn('COUNT', sequelize.col('id')), 'count']
    ],
    group: ['status'],
    raw: true
  });
  
  // 活跃用户统计
  const activeUsers = await User.count({
    where: {
      lastLoginAt: {
        [Op.gte]: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)
      }
    }
  });
  
  return {
    registrationTrend,
    statusDistribution,
    activeUsers
  };
}

总结

今天我们深入学习了 Sequelize 的基础查询操作:

  • ✅ 各种查询方法的使用场景和技巧
  • ✅ 丰富的查询条件和操作符
  • ✅ 排序、分页、分组等高级功能
  • ✅ 性能优化的最佳实践
  • ✅ 实际项目中的应用案例

掌握了这些知识,你就能够:

  • 编写高效的数据查询代码
  • 处理复杂的业务查询需求
  • 优化查询性能
  • 构建完整的搜索和统计功能

下一篇文章,我们将学习更高级的查询方法,包括子查询、联合查询等复杂操作。


相关文章推荐:

有问题欢迎留言讨论,我会及时回复大家!