You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将Sequelize查询结果按cart_id分组为指定格式?

按cart_id分组整理Sequelize查询结果的方法

问题背景

我是Node.js Express结合PostgreSQL的新手,编写了如下路由:

get: async (req, res, next) => {
       
        var cat = await sequelize.query(
            `
            SELECT 
o."id" as "cart_id", 
o."user_id", 
o."createdBy",
o."createdAt", 
o."updatedAt",
i."quantity",
pi."name",
pi."price"
FROM "Orders" AS o
Left join public."CartItem" i on  i."cart_id"=o."id"
Left join public."Item" pi on  pi."id"=i."product_id"
WHERE o."user_id"=${req.body.user_id}

            `
            ,
         { type: QueryTypes.SELECT,group:`i."cart_id"` })
            .catch(e => res.send(e))
        if (cat) {
            // console.log(cat.Orders)
            cat.map(e=> {
                console.log(e)
            })
            res.send(cat)
        }
    },

通过Postman调用该路由后,得到如下响应:

[
    {
        "cart_id": 9,
        "user_id": 1,
        "createdBy": null,
        "createdAt": "2022-11-11T09:51:47.968Z",
        "updatedAt": "2022-11-11T09:51:47.968Z",
        "quantity": 4,
        "name": "test1",
        "price": 12000
    },
    {
        "cart_id": 9,
        "user_id": 1,
        "createdBy": null,
        "createdAt": "2022-11-11T09:51:47.968Z",
        "updatedAt": "2022-11-11T09:51:47.968Z",
        "quantity": 4,
        "name": "test2",
        "price": 12000
    }
]

希望将响应整理为以下格式(按cart_id分组,注:原期望格式外层数组存在语法错误,修正为对象格式):

{
    "cart_9": [
        {
            "user_id": 1,
            "createdBy": null,
            "createdAt": "2022-11-11T09:51:47.968Z",
            "updatedAt": "2022-11-11T09:51:47.968Z",
            "quantity": 4,
            "name": "test1",
            "price": 12000
        },
        {
            "user_id": 1,
            "createdBy": null,
            "createdAt": "2022-11-11T09:51:47.968Z",
            "updatedAt": "2022-11-11T09:51:47.968Z",
            "quantity": 4,
            "name": "test2",
            "price": 12000
        }
    ]
}

解决方案

1. 使用JavaScript reduce 处理查询结果

在获取查询结果后,通过reduce方法按cart_id分组,同时移除每个项中的cart_id字段:

get: async (req, res, next) => {
    try {
        const cat = await sequelize.query(
            `
            SELECT 
                o."id" as "cart_id", 
                o."user_id", 
                o."createdBy",
                o."createdAt", 
                o."updatedAt",
                i."quantity",
                pi."name",
                pi."price"
            FROM "Orders" AS o
            Left join public."CartItem" i on  i."cart_id"=o."id"
            Left join public."Item" pi on  pi."id"=i."product_id"
            WHERE o."user_id" = :userId
            `,
            { 
                type: QueryTypes.SELECT,
                replacements: { userId: req.body.user_id } // 避免SQL注入风险
            }
        );

        if (cat) {
            // 分组并转换格式
            const groupedResult = cat.reduce((acc, item) => {
                const key = `cart_${item.cart_id}`;
                // 解构移除cart_id字段
                const { cart_id, ...rest } = item;
                // 初始化分组数组
                if (!acc[key]) acc[key] = [];
                acc[key].push(rest);
                return acc;
            }, {});

            res.send(groupedResult);
        }
    } catch (e) {
        res.status(500).send(e); // 用状态码标识错误更规范
    }
},

2. 修复SQL注入风险

你原代码直接将req.body.user_id拼接进SQL语句,这会导致严重的SQL注入漏洞,必须使用Sequelize的replacements参数传递变量,如上述代码所示。

3. 可选:使用Sequelize关联模型(ORM风格)

如果已定义模型关联,可避免手写SQL,直接通过模型查询并转换格式:
假设模型关联已定义:

// Order模型
const Order = sequelize.define('Order', {
    id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
    user_id: DataTypes.INTEGER,
    createdBy: DataTypes.INTEGER,
    createdAt: DataTypes.DATE,
    updatedAt: DataTypes.DATE
});

// CartItem模型
const CartItem = sequelize.define('CartItem', {
    cart_id: DataTypes.INTEGER,
    product_id: DataTypes.INTEGER,
    quantity: DataTypes.INTEGER
});

// Item模型
const Item = sequelize.define('Item', {
    id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
    name: DataTypes.STRING,
    price: DataTypes.INTEGER
});

// 建立关联
Order.hasMany(CartItem, { foreignKey: 'cart_id', sourceKey: 'id' });
CartItem.belongsTo(Item, { foreignKey: 'product_id', targetKey: 'id' });

查询代码可改写为:

get: async (req, res, next) => {
    try {
        const orders = await Order.findAll({
            where: { user_id: req.body.user_id },
            include: [
                {
                    model: CartItem,
                    include: [Item]
                }
            ]
        });

        // 转换为目标格式
        const groupedResult = orders.reduce((acc, order) => {
            const key = `cart_${order.id}`;
            const items = order.CartItems.map(cartItem => ({
                user_id: order.user_id,
                createdBy: order.createdBy,
                createdAt: order.createdAt,
                updatedAt: order.updatedAt,
                quantity: cartItem.quantity,
                name: cartItem.Item.name,
                price: cartItem.Item.price
            }));
            acc[key] = items;
            return acc;
        }, {});

        res.send(groupedResult);
    } catch (e) {
        res.status(500).send(e);
    }
},

内容的提问来源于stack exchange,提问作者نعمان منذر محمود الجميلي

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 09:15:35