使用Sequelize多次连接同一张表

时间:2018-07-23 13:41:22

标签: node.js sequelize.js

我有以下型号:

const User = Sequelize.define('user', {
    login: Sequelize.DataTypes.STRING,
    password: Sequelize.DataTypes.STRING,
    is_manager: Sequelize.DataTypes.BOOLEAN,
    notes: Sequelize.DataTypes.STRING
});

const Bike = Sequelize.define('bike', {
    model: Sequelize.DataTypes.STRING,
    photo: Sequelize.DataTypes.BLOB,
    color: Sequelize.DataTypes.STRING,
    weight: Sequelize.DataTypes.FLOAT,
    location: Sequelize.DataTypes.STRING,
    is_available: Sequelize.DataTypes.BOOLEAN
});

const Rate = Sequelize.define('rate', {
    rate: Sequelize.DataTypes.INTEGER
});
Rate.belongsTo(User);
User.hasMany(Rate);
Rate.belongsTo(Bike);
Bike.hasMany(Rate);

我想选择平均费用加上每辆自行车的当前用户费用的自行车:

    Bike.findAll({
        attributes: {include: [[Sequelize.fn('AVG', Sequelize.col('rates.rate')), 'rate_avg']],
        },
        include: [{
            model: Rate,
            attributes: []
        }, {
            model: Rate,
            attributes: ['rate'],
            include: [{
                model: User,
                attributes: [],
                where: {
                    login: req.user.login
                }
            }]
        }],
        group: Object.keys(Bike.rawAttributes).map(key => 'bike.' + key) // group by all fields of Bike model
    })

它构造以下查询:SELECT [bike].[id], [bike].[model], [bike].[photo], [bike].[color], [bike].[weight], [bike].[location], [bike].[is_available], AVG([rates].[rate]) AS [rate_avg], [rates].[id] AS [rates.id], [rates].[rate] AS [rates.rate] FROM [bikes] AS [bike] LEFT OUTER JOIN [rates] AS [rates] ON [bike].[id] = [rates].[bikeId] LEFT OUTER JOIN ( [rates] AS [rates] INNER JOIN [users] AS [rates->user] ON [rates].[userId] = [rates->user].[id] AND [rates->user].[login] = N'user' ) ON [bike].[id] = [rates].[bikeId] GROUP BY [bike].[id], [bike].[model], [bike].[photo], [bike].[color], [bike].[weight], [bike].[location], [bike].[is_available];

并且失败:SequelizeDatabaseError: The correlation name 'rates' is specified multiple times in a FROM clause.

如何正确编写查询?我需要Sequelize为第二次连接中使用的rates表分配另一个别名(并将其列添加到GROUP BY子句中,但这是下一步)。

3 个答案:

答案 0 :(得分:0)

解决方案:

Bike.findAll({
        attributes: {include: [[Sequelize.fn('AVG', Sequelize.col('rates.rate')), 'rate_avg']],
    },
    include: [{
        model: Rate,
        attributes: []
    }, {
        model: Rate,
        required : false , // 1. just to make sure not making inner join
        separate : true , // 2. will run query separately , so your issue will be solved of multiple times 
        attributes: ['rate'],
        include: [{
            model: User,
            attributes: [],
            where: {
                login: req.user.login
            }
        }]
        group : [] // 3. <------- This needs to be managed , so please check errors and add fields as per error
    }],
    group: Object.keys(Bike.rawAttributes).map(key => 'bike.' + key) // group by all fields of Bike model
})
  

注意:阅读评论

答案 1 :(得分:0)

Sequelize不支持两次通过同一关联进行访问(请参阅hereherehere)。在模型级别,您可以在Bike和Rate之间定义2种不同的关联,但是必须更改模型,添加新的外键等,这是一个非常棘手的解决方案。

顺便说一句,它不能解决您的其他问题,即您仅按Bike分组,然后要选择用户的费率。要解决此问题,您还必须更改分组以包括用户费率。 (请注意,如果用户每辆自行车的费率超过1个,则由于每个用户的费率都要反复对自行车的费率进行平均,因此这也会造成效率低下。)

一种适当的解决方案是使用窗口函数,首先对每辆自行车的费率求平均,然后过滤掉不属于登录用户的所有费率。可能看起来像这样:

SELECT *
FROM (
    SELECT bike.*,
        users.login AS user_login,
        AVG (rates.rate) OVER (PARTITION BY bike.id) AS rate_avg
    FROM bike
    INNER JOIN rates ON rates.bikeId = bike.id
    INNER JOIN users ON rates.userId = users.id
)
WHERE user_login = :req_user_login

不幸的是,据我所知,sequelize当前不支持FROM子句中的子查询,并且不以这种方式使用窗口函数,因此您必须回到原始查询。

答案 2 :(得分:0)

您可以通过添加与该模型相同的额外关联但使用不同的别名 as: 'alias1'as: 'alias2' 、... - 所有这些都存在于同一模型中,从而对同一个表进行多个内部联接+ 相同类型的关联。
还在 github 问题上发布了此解决方案:https://github.com/sequelize/sequelize/issues/7754#issuecomment-783404779

例如对于有很多接收者的聊天
关联(根据需要复制)

Chat.hasMany(Receiver, {
    // foreignKey: ...
    as: 'chatReceiver',
});
Chat.hasMany(Receiver, {
    // foreignKey: ...
    as: 'chatReceiver2',
});

现在您需要多次包含关联模型,所有模型都具有不同的别名,因此它不会被覆盖。
所以你可以在查询中使用它们,如下所示:

Chat.findAll({
    attributes: ["id"],
    include: [{
        required: true,
        model: Receiver,
        as: 'chatReceiver', // Alias 1
        attributes: [],
        where: { userID: 1 }, // condition 1
    }, {
        required: true,
        model: Receiver,
        as: 'chatReceiver2', // Alias 2
        attributes: [],
        where: { userID: 2 }, // condition 2 as needed
    }]
});