跳到主要内容

Aggregate API 聚合统计查询

v1.2.0+

aggregate API 是 Lovrabet SDK 提供的数据聚合统计接口,支持 SUM、COUNT、AVG 等聚合函数,配合 GROUP BY、HAVING 实现复杂的数据统计分析,是构建报表和数据大屏的核心工具。

前置条件

开始之前,请确保已完成 SDK 配置。推荐使用 CLI 自动生成配置

以下示例使用别名方式 client.models.orders 以便阅读,也可以使用标准方式 client.models.dataset_xxx(功能完全一致)。

操作限制

aggregate() 操作仅 WebAPI 模式(Cookie 认证)支持。OpenAPI 模式暂不支持此功能。

🎯 主要功能

  • 聚合函数 - 支持 SUM(求和)、COUNT(计数)、AVG(平均值)
  • 分组统计 - GROUP BY 分组聚合
  • 条件筛选 - WHERE 条件过滤 + HAVING 分组后过滤
  • 结果排序 - 支持按聚合结果排序
  • 分页支持 - 大数据量分组结果的分页
  • 精度控制 - 四舍五入、小数位数设置

📝 API 签名

async aggregate<T = any>(params: AggregateParams): Promise<T | T[]>

参数说明

interface AggregateParams {
select?: string[]; // 选择返回的字段(通常用于 GROUP BY)
aggregate: AggregateField[]; // 聚合配置(必填)
where?: WhereCondition; // 查询条件(聚合前过滤)
groupBy?: string[]; // 分组字段
having?: HavingCondition[]; // 分组后过滤条件
orderBy?: SortList; // 排序规则
currentPage?: number; // 当前页码
pageSize?: number; // 每页数量
}

聚合字段配置

interface AggregateField {
type: 'SUM' | 'COUNT' | 'AVG'; // 聚合类型
field: string; // 字段名(COUNT 用 '*' 表示所有)
alias?: string; // 返回结果的字段别名
distinct?: boolean; // 是否去重(COUNT DISTINCT)
round?: boolean; // 是否四舍五入
precision?: number; // 小数位数(配合 round 使用)
}

返回值

  • 无 GROUP BY:返回单个对象 { alias1: value1, alias2: value2 }
  • 有 GROUP BY:返回数组 [{ groupField: value, alias1: value1 }, ...]

📊 聚合函数详解

SUM(求和)

计算指定字段的总和,适用于数值类型字段。

// 计算订单总金额
const result = await client.models.orders.aggregate({
aggregate: [
{ type: 'SUM', field: 'total_amount', alias: 'total_sales' },
],
});

console.log(result);
// { total_sales: 125678.90 }

COUNT(计数)

统计记录数量。

// 统计订单总数
const result = await client.models.orders.aggregate({
aggregate: [
{ type: 'COUNT', field: '*', alias: 'total_count' },
],
});

console.log(result);
// { total_count: 1250 }

// 统计不重复的客户数
const result = await client.models.orders.aggregate({
aggregate: [
{ type: 'COUNT', field: 'customer_id', alias: 'unique_customers', distinct: true },
],
});

console.log(result);
// { unique_customers: 320 }

AVG(平均值)

计算指定字段的平均值。

// 计算平均订单金额
const result = await client.models.orders.aggregate({
aggregate: [
{ type: 'AVG', field: 'total_amount', alias: 'avg_amount', round: true, precision: 2 },
],
});

console.log(result);
// { avg_amount: 125.45 }

多个聚合函数组合

// 同时获取总数、总和、平均值
const stats = await client.models.orders.aggregate({
aggregate: [
{ type: 'COUNT', field: '*', alias: 'order_count' },
{ type: 'SUM', field: 'total_amount', alias: 'total_sales', round: true, precision: 2 },
{ type: 'AVG', field: 'total_amount', alias: 'avg_amount', round: true, precision: 2 },
],
});

console.log(stats);
// { order_count: 1250, total_sales: 156789.50, avg_amount: 125.43 }

🔀 GROUP BY 分组聚合

基础分组

// 按状态分组统计订单
const result = await client.models.orders.aggregate({
select: ['status'],
aggregate: [
{ type: 'COUNT', field: '*', alias: 'count' },
{ type: 'SUM', field: 'total_amount', alias: 'total' },
],
groupBy: ['status'],
});

console.log(result);
// [
// { status: 'pending', count: 45, total: 5678.00 },
// { status: 'completed', count: 1200, total: 145678.50 },
// { status: 'cancelled', count: 5, total: 433.00 },
// ]

多字段分组

// 按年份和月份分组统计
const result = await client.models.orders.aggregate({
select: ['year', 'month'],
aggregate: [
{ type: 'COUNT', field: '*', alias: 'order_count' },
{ type: 'SUM', field: 'total_amount', alias: 'total_sales' },
],
groupBy: ['year', 'month'],
orderBy: [{ year: 'desc' }, { month: 'desc' }],
});

console.log(result);
// [
// { year: 2025, month: 2, order_count: 150, total_sales: 18765.00 },
// { year: 2025, month: 1, order_count: 200, total_sales: 25432.00 },
// { year: 2024, month: 12, order_count: 180, total_sales: 22100.00 },
// ]

按分类统计

// 按产品分类统计销量
const result = await client.models.order_items.aggregate({
select: ['category_name'],
aggregate: [
{ type: 'COUNT', field: '*', alias: 'item_count' },
{ type: 'SUM', field: 'quantity', alias: 'total_quantity' },
{ type: 'SUM', field: 'subtotal', alias: 'total_revenue' },
],
groupBy: ['category_name'],
orderBy: [{ total_revenue: 'desc' }],
});

console.log(result);
// [
// { category_name: '电子产品', item_count: 500, total_quantity: 800, total_revenue: 456789.00 },
// { category_name: '服装', item_count: 300, total_quantity: 1200, total_revenue: 123456.00 },
// ]

🔍 WHERE 条件过滤

在聚合前先过滤数据,只对符合条件的数据进行统计。

// 统计已完成订单
const result = await client.models.orders.aggregate({
aggregate: [
{ type: 'COUNT', field: '*', alias: 'completed_count' },
{ type: 'SUM', field: 'total_amount', alias: 'completed_total' },
],
where: {
status: { $eq: 'completed' },
},
});

console.log(result);
// { completed_count: 1200, completed_total: 145678.50 }

// 统计指定日期范围内的订单
const result = await client.models.orders.aggregate({
select: ['status'],
aggregate: [
{ type: 'COUNT', field: '*', alias: 'count' },
{ type: 'SUM', field: 'total_amount', alias: 'total' },
],
where: {
create_time: { $gte: '2025-01-01', $lte: '2025-01-31' },
status: { $in: ['completed', 'shipped'] },
},
groupBy: ['status'],
});

🎯 HAVING 分组后过滤

HAVING 用于对聚合结果进行过滤,类似于 WHERE 但作用于分组后的结果。

// 找出销售额超过 10000 的分类
const result = await client.models.orders.aggregate({
select: ['category_id'],
aggregate: [
{ type: 'SUM', field: 'total_amount', alias: 'total_sales' },
],
groupBy: ['category_id'],
having: [
{ columnName: 'total_sales', condition: { $gte: 10000 } },
],
orderBy: [{ total_sales: 'desc' }],
});

console.log(result);
// [
// { category_id: 1, total_sales: 45678.00 },
// { category_id: 3, total_sales: 23456.00 },
// ]

// 多条件 HAVING
const result = await client.models.orders.aggregate({
select: ['customer_id'],
aggregate: [
{ type: 'COUNT', field: '*', alias: 'order_count' },
{ type: 'SUM', field: 'total_amount', alias: 'total_amount' },
],
groupBy: ['customer_id'],
having: [
{ columnName: 'order_count', condition: { $gte: 5 } },
{ columnName: 'total_amount', condition: { $gte: 1000 } },
],
});

// 找出订单数 >= 5 且总金额 >= 1000 的 VIP 客户

📋 分页

对于大量分组结果,可以使用分页。

// 分页获取分类销售排行
const result = await client.models.orders.aggregate({
select: ['category_id'],
aggregate: [
{ type: 'COUNT', field: '*', alias: 'order_count' },
{ type: 'SUM', field: 'total_amount', alias: 'total_sales' },
],
groupBy: ['category_id'],
orderBy: [{ total_sales: 'desc' }],
currentPage: 1,
pageSize: 10,
});

console.log(result);
// 返回前 10 个分类的统计结果

🎨 完整示例

示例 1:销售数据统计面板

import { client } from "./api/client";

async function getSalesDashboard() {
// 1. 总体销售数据
const overallStats = await client.models.orders.aggregate({
aggregate: [
{ type: 'COUNT', field: '*', alias: 'total_orders' },
{ type: 'SUM', field: 'total_amount', alias: 'total_revenue', round: true, precision: 2 },
{ type: 'AVG', field: 'total_amount', alias: 'avg_order_value', round: true, precision: 2 },
],
where: {
status: { $eq: 'completed' },
},
});

// 2. 按分类统计
const categoryStats = await client.models.orders.aggregate({
select: ['category_id', 'category_name'],
aggregate: [
{ type: 'COUNT', field: '*', alias: 'order_count' },
{ type: 'SUM', field: 'total_amount', alias: 'category_revenue' },
],
where: {
status: { $eq: 'completed' },
},
groupBy: ['category_id', 'category_name'],
orderBy: [{ category_revenue: 'desc' }],
pageSize: 5,
});

// 3. 本月销售趋势
const now = new Date();
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1).toISOString();

const monthlyStats = await client.models.orders.aggregate({
select: ['DATE(create_time) as date'],
aggregate: [
{ type: 'COUNT', field: '*', alias: 'daily_orders' },
{ type: 'SUM', field: 'total_amount', alias: 'daily_revenue' },
],
where: {
create_time: { $gte: monthStart },
status: { $eq: 'completed' },
},
groupBy: ['DATE(create_time)'],
orderBy: [{ date: 'asc' }],
});

return {
overall: overallStats,
byCategory: categoryStats,
monthlyTrend: monthlyStats,
};
}

// 使用
const dashboard = await getSalesDashboard();
console.log('总体数据:', dashboard.overall);
console.log('分类排行:', dashboard.byCategory);
console.log('本月趋势:', dashboard.monthlyTrend);

示例 2:客户分析报表

// 客户消费分析
async function analyzeCustomers() {
// 客户消费排名 TOP 10
const topCustomers = await client.models.orders.aggregate({
select: ['customer_id', 'customer_name'],
aggregate: [
{ type: 'COUNT', field: '*', alias: 'order_count' },
{ type: 'SUM', field: 'total_amount', alias: 'total_spent' },
{ type: 'AVG', field: 'total_amount', alias: 'avg_order', round: true, precision: 2 },
],
groupBy: ['customer_id', 'customer_name'],
orderBy: [{ total_spent: 'desc' }],
pageSize: 10,
});

// 客户分层统计
const customerSegments = await client.models.customers.aggregate({
select: ['level'],
aggregate: [
{ type: 'COUNT', field: '*', alias: 'customer_count' },
{ type: 'SUM', field: 'total_purchases', alias: 'total_purchases' },
],
groupBy: ['level'],
orderBy: [{ customer_count: 'desc' }],
});

// 高价值客户(消费超过 5000)
const vipCustomers = await client.models.orders.aggregate({
select: ['customer_id'],
aggregate: [
{ type: 'SUM', field: 'total_amount', alias: 'lifetime_value' },
],
groupBy: ['customer_id'],
having: [
{ columnName: 'lifetime_value', condition: { $gte: 5000 } },
],
orderBy: [{ lifetime_value: 'desc' }],
});

return {
topCustomers,
segments: customerSegments,
vipCount: vipCustomers.length,
};
}

示例 3:React 组件集成

import { useState, useEffect } from "react";
import { client } from "./api/client";
import { Table, Card, Statistic, Row, Col } from "antd";

function SalesReport() {
const [loading, setLoading] = useState(false);
const [overallStats, setOverallStats] = useState<any>({});
const [categoryData, setCategoryData] = useState<any[]>([]);

useEffect(() => {
loadReport();
}, []);

const loadReport = async () => {
setLoading(true);
try {
// 总体统计
const overall = await client.models.orders.aggregate({
aggregate: [
{ type: 'COUNT', field: '*', alias: 'totalOrders' },
{ type: 'SUM', field: 'total_amount', alias: 'totalRevenue' },
{ type: 'AVG', field: 'total_amount', alias: 'avgOrderValue', round: true, precision: 2 },
],
where: {
status: { $eq: 'completed' },
},
});
setOverallStats(overall);

// 分类统计
const byCategory = await client.models.orders.aggregate({
select: ['category_name'],
aggregate: [
{ type: 'COUNT', field: '*', alias: 'orderCount' },
{ type: 'SUM', field: 'total_amount', alias: 'revenue' },
],
where: {
status: { $eq: 'completed' },
},
groupBy: ['category_name'],
orderBy: [{ revenue: 'desc' }],
});
setCategoryData(byCategory);
} catch (error) {
console.error("加载报表失败:", error);
} finally {
setLoading(false);
}
};

const columns = [
{ title: "分类", dataIndex: "category_name", key: "category_name" },
{ title: "订单数", dataIndex: "orderCount", key: "orderCount" },
{
title: "销售额",
dataIndex: "revenue",
key: "revenue",
render: (v: number) => `¥${v?.toLocaleString()}`,
},
];

return (
<div>
<Row gutter={16} style={{ marginBottom: 24 }}>
<Col span={8}>
<Card>
<Statistic
title="总订单数"
value={overallStats.totalOrders}
loading={loading}
/>
</Card>
</Col>
<Col span={8}>
<Card>
<Statistic
title="总销售额"
value={overallStats.totalRevenue}
precision={2}
prefix="¥"
loading={loading}
/>
</Card>
</Col>
<Col span={8}>
<Card>
<Statistic
title="平均订单金额"
value={overallStats.avgOrderValue}
precision={2}
prefix="¥"
loading={loading}
/>
</Card>
</Col>
</Row>

<Card title="分类销售排行">
<Table
dataSource={categoryData}
columns={columns}
rowKey="category_name"
loading={loading}
pagination={false}
/>
</Card>
</div>
);
}

🆚 aggregate vs filter vs SQL

特性aggregatefilter自定义 SQL
用途聚合统计列表查询复杂查询
聚合函数✅ SUM/COUNT/AVG✅ 全部
GROUP BY✅ 原生支持
HAVING✅ 原生支持
多表 JOIN
类型安全✅ TypeScript✅ TypeScript⚠️ 需手写类型
适用场景报表、统计、大屏列表、搜索复杂业务查询

⚠️ 注意事项

1. 字段类型

确保聚合字段是数值类型:

// ✅ 正确 - 数值字段
{ type: 'SUM', field: 'total_amount', alias: 'total' }

// ❌ 错误 - 字符串字段无法求和
{ type: 'SUM', field: 'order_no', alias: 'total' }

2. COUNT 的 field 参数

// 统计所有记录数
{ type: 'COUNT', field: '*', alias: 'total' }

// 统计某字段非空记录数
{ type: 'COUNT', field: 'customer_id', alias: 'customers_with_orders' }

// 统计不重复值
{ type: 'COUNT', field: 'customer_id', alias: 'unique_customers', distinct: true }

3. GROUP BY 字段顺序

多个 GROUP BY 字段时,结果按字段顺序分组:

// 先按年份,再按月份
groupBy: ['year', 'month']

// 结果结构:2025 → 1月, 2025 → 2月, 2024 → 12月...

4. HAVING vs WHERE

  • WHERE:聚合前过滤,减少参与聚合的数据量
  • HAVING:聚合后过滤,筛选聚合结果
// WHERE:先筛选已完成订单,再统计
where: { status: { $eq: 'completed' } }

// HAVING:统计所有订单,只返回销售额 > 10000 的分组
having: [{ columnName: 'total_sales', condition: { $gte: 10000 } }]

5. 性能优化

  • 使用 WHERE 提前过滤数据,减少聚合计算量
  • 合理设置 pageSize,避免返回过多分组结果
  • 对于大数据量表,建议添加索引

📚 相关文档


有问题?查看 故障排查文档 或联系技术支持。