使用 Sequelize 计算关联条目

Posted

技术标签:

【中文标题】使用 Sequelize 计算关联条目【英文标题】:Counting associated entries with Sequelize 【发布时间】:2016-10-15 12:40:29 【问题描述】:

我有两张桌子,locationssensorssensors 中的每个条目都有一个指向 locations 的外键。使用 Sequelize,我如何获取来自 locations 的所有条目以及与 locations 中的每个条目相关联的 sensors 中的条目总数?

原始 SQL:

SELECT 
    `locations`.*,
    COUNT(`sensors`.`id`) AS `sensorCount` 
FROM `locations` 
JOIN `sensors` ON `sensors`.`location`=`locations`.`id`;
GROUP BY `locations`.`id`;

型号:

module.exports = function(sequelize, DataTypes) 
    var Location = sequelize.define("Location", 
        id: 
            type: DataTypes.INTEGER.UNSIGNED,
            primaryKey: true
        ,
        name: DataTypes.STRING(255)
    , 
        classMethods: 
            associate: function(models) 
                Location.hasMany(models.Sensor, 
                    foreignKey: "location"
                );
            
        
    );

    return Location;
;


module.exports = function(sequelize, DataTypes) 
    var Sensor = sequelize.define("Sensor", 
        id: 
            type: DataTypes.INTEGER.UNSIGNED,
            primaryKey: true
        ,
        name: DataTypes.STRING(255),
        type: 
            type: DataTypes.INTEGER.UNSIGNED,
            references: 
                model: "sensor_types",
                key: "id"
            
        ,
        location: 
            type: DataTypes.INTEGER.UNSIGNED,
            references: 
                model: "locations",
                key: "id"
            
        
    , 
        classMethods: 
            associate: function(models) 
                Sensor.belongsTo(models.Location, 
                    foreignKey: "location"
                );

                Sensor.belongsTo(models.SensorType,  
                    foreignKey: "type"
                );
            
        
    );

    return Sensor;
;

【问题讨论】:

这真的是你想要的SQL吗?我不认为这会像你想象的那样做。事实上,我不确定查询是否会运行而不会引发错误。 @dvlsg 我运行它,它正确返回了locations 表中的所有行和字段,并且为每一行返回了sensors 中正确数量的关联条目。 其实@dvlsg,这是不对的。我做了更多测试(locations 表中有更多条目),结果我忘记了GROUP BY 声明。我已经编辑了问题。 啊,好吧。这更有意义。我想也许 mysql 正在拉一些我不知道的恶作剧(而且我知道他们使用隐含的 GROUP 语句来做到这一点,所以这并非完全不合理)。 ***.com/questions/52496842/… 【参考方案1】:

HAVINGORDER BYINNEROUTER JOIN 的示例 + 一些错误/不直观的行为

我在Sequelize query with count in inner join 进行了更详细的介绍,但这里是要点的快速摘要列表:

你必须使用row.get('count')row.count不起作用 你必须在 PostgreSQL 上parseInt 此代码在 PostgreSQL 上失败,column X must appear in the GROUP BY clause or be used in an aggregate function 由于一个 sequelize 错误

OUTER JOIN 使用 required: false 包含 0 个计数的示例:

sqlite.js

const assert = require('assert');
const  DataTypes, Op, Sequelize  = require('sequelize');
const sequelize = new Sequelize('tmp', undefined, undefined, Object.assign(
  dialect: 'sqlite',
  storage: 'tmp.sqlite'
));
;(async () => 
const User = sequelize.define('User', 
  name:  type: DataTypes.STRING ,
, );
const Post = sequelize.define('Post', 
  body:  type: DataTypes.STRING ,
, );
User.belongsToMany(Post, through: 'UserLikesPost');
Post.belongsToMany(User, through: 'UserLikesPost');
await sequelize.sync(force: true);
const user0 = await User.create(name: 'user0')
const user1 = await User.create(name: 'user1')
const user2 = await User.create(name: 'user2')
const post0 = await Post.create(body: 'post0')
const post1 = await Post.create(body: 'post1')
const post2 = await Post.create(body: 'post2')
// Set likes for each user.
await user0.addPosts([post0, post1])
await user1.addPosts([post0, post2])

let rows = await User.findAll(
  attributes: [
    'name',
    [sequelize.fn('COUNT', sequelize.col('Posts.id')), 'count'],
  ],
  include: [
    
      model: Post,
      attributes: [],
      required: false,
      through: attributes: [],
      where:  id:  [Op.ne]: post2.id ,
    ,
  ],
  group: ['User.name'],
  order: [[sequelize.col('count'), 'DESC']],
  having: sequelize.where(sequelize.fn('COUNT', sequelize.col('Posts.id')), Op.lte, 1)
)
assert.strictEqual(rows[0].name, 'user1')
assert.strictEqual(parseInt(rows[0].get('count'), 10), 1)
assert.strictEqual(rows[1].name, 'user2')
assert.strictEqual(parseInt(rows[1].get('count'), 10), 0)
assert.strictEqual(rows.length, 2)
)().finally(() =>  return sequelize.close() );

与:

package.json


  "name": "tmp",
  "private": true,
  "version": "1.0.0",
  "dependencies": 
    "pg": "8.5.1",
    "pg-hstore": "2.3.3",
    "sequelize": "6.5.1",
    "sqlite3": "5.0.2"
  

和节点 v14.17.0。

INNER JOIN 版本不包括 0 计数:

let rows = await User.findAll(
  attributes: [
    'name',
    [sequelize.fn('COUNT', '*'), 'count'],
  ],
  include: [
    
      model: Post,
      attributes: [],
      through: attributes: [],
      where:  id:  [Op.ne]: post2.id ,
    ,
  ],
  group: ['User.name'],
  order: [[sequelize.col('count'), 'DESC']],
  having: sequelize.where(sequelize.fn('COUNT', '*'), Op.lte, 1)
)
assert.strictEqual(rows[0].name, 'user1')
assert.strictEqual(parseInt(rows[0].get('count'), 10), 1)
assert.strictEqual(rows.length, 1)

【讨论】:

【参考方案2】:

如何为它定义一个数据库视图,然后为该视图定义一个模型?只要您需要传感器的数量,就可以获取与查询中包含的视图的关系。这样代码可能看起来更干净,但我不知道是否会有性能成本。其他人可能会回答...

CREATE OR REPLACE VIEW view_location_sensors_count AS
select "locations".id as "locationId", count("sensors".id) as "locationSensorsCount"
from locations
left outer join sensors on sensors."locationId" = location.id
group by location.id

在为视图定义模型时,您删除了 id 属性并将 locationId 设置为主键。 您的模型可能如下所示:

const  Model, DataTypes  = require('sequelize')

const attributes = 
    locationID: 
        type: DataTypes.UUIDV4, // Or whatever data type is your location ID
        primaryKey: true,
        unique: true
    ,
    locationSensorsCount: DataTypes.INTEGER


const options = 
    paranoid: false,
    modelName: 'ViewLocationSensorsCount',
    tableName: 'view_location_sensors_count',
    timestamps: false



/**
 * This is only a database view. It is not an actual table, so 
 * DO NOT ATTEMPT insert, update or delete statements on this model
 */
class ViewLocationSensorsCount extends Model 
    static associate(models) 
        ViewLocationSensorsCount.removeAttribute('id')
        ViewLocationSensorsCount.belongsTo(models.Location,  as:'location', foreignKey: 'locationID' )
    


    static init(sequelize) 
        this.sequelize = sequelize
        return super.init(attributes, ...options, sequelize)
    


module.exports = ViewLocationSensorsCount

最后,在您的 Location 模型中,您设置了与 Sensor 模型的 hasOne 关系。

【讨论】:

【参考方案3】:

使用 Sequelize 计算关联条目

Location.findAll(
    attributes:  
        include: [[Sequelize.fn('COUNT', Sequelize.col('sensors.location')), 'sensorCounts']] 
    , // Sequelize.col() should contain a attribute which is referenced with parent table and whose rows needs to be counted
    include: [
        model: Sensor, attributes: []
    ],
    group: ['sensors.location'] // groupBy is necessary else it will generate only 1 record with all rows count
)

注意:

不知何故,这个查询会产生一个错误,例如 sensors.location is not exist in field list。 这是因为 subQuery 是由上述 sequelize 查询形成的。

所以解决方案是提供 subQuery: false like example

Location.findAll(
        subQuery: false,
        attributes:  
            include: [[Sequelize.fn('COUNT', Sequelize.col('sensors.location')), 'sensorCounts']] 
        ,
        include: [
            model: Sensor, attributes: []
        ],
        group: ['sensors.location']
    )

注意: **有时这也可能会在 mysql 配置中生成错误 bcz,默认情况下在 sqlMode 中包含 only-full-group-by,需要将其删除才能正常工作。

错误将如下所示..**

错误:SELECT 列表的表达式 #1 不在 GROUP BY 子句中,并且包含在功能上不依赖于 GROUP BY 子句中的列的非聚合列“db.table.id”;这与 sql_mode=only_full_group_by 不兼容

所以要解决这个错误,请遵循这个答案

SELECT list is not in GROUP BY clause and contains nonaggregated column .... incompatible with sql_mode=only_full_group_by

现在这将成功生成所有关联的计数

希望这会对您或其他人有所帮助!

【讨论】:

很酷,但是如果您想向传感器表添加条件怎么办。例如,表中有一个 user_id 字段,我们如何过滤呢?这可能吗? 是的,你可以,传递其中的 where 属性包括第 0 个索引对象以及模型和属性【参考方案4】:
Location.findAll(
        attributes:  
            include: [[Sequelize.fn("COUNT", Sequelize.col("sensors.id")), "sensorCount"]] 
        ,
        include: [
            model: Sensor, attributes: []
        ]
    );

它有效。但是当我添加“限制”时,我得到了错误:传感器未定义

【讨论】:

你可以试试 subQuery: false in query 选项。 @BhavyaSanchaniya 感谢添加 subQuery: false 我可以使用限制和偏移【参考方案5】:

findAll()include()sequelize.fn() 一起用于COUNT

Location.findAll(
    attributes:  
        include: [[Sequelize.fn("COUNT", Sequelize.col("sensors.id")), "sensorCount"]] 
    ,
    include: [
        model: Sensor, attributes: []
    ]
);

或者,您可能还需要添加group

Location.findAll(
    attributes:  
        include: [[Sequelize.fn("COUNT", Sequelize.col("sensors.id")), "sensorCount"]] 
    ,
    include: [
        model: Sensor, attributes: []
    ],
    group: ['Location.id']
)

【讨论】:

感谢您的回答。这正确计算了 sensourCount 字段,但它还包括来自结果中的 sensors 表的字段。此外,虽然它执行的 SQL 查询显示它包含 locations 表中的所有字段,但它们不包含在结果中(它在 then 子句中返回的对象)。 @MikkoP 好的,让我快速构建一个示例并调试,谢谢。 @MikkoP 您能否编辑问题并发布您的模型定义?谢谢。 我添加了模型定义。这是您的查询输出的内容。 pastebin.com/PwnctW1Y @MikkoP 谢谢,请查看更新。我认为您现在应该能够达到预期的结果。谢谢。

以上是关于使用 Sequelize 计算关联条目的主要内容,如果未能解决你的问题,请参考以下文章

使用 Sequelize 包含子句查询在另一个表中没有条目的记录

如何使用 sequelize 建立模型属于多关联

sequelize模型

使用 Sequelize.js 时在哪里定义关联/关系?

Sequelize 基于关联的查找

如何嵌套 Sequelize 关联?