使用 Sequelize 计算关联条目
Posted
技术标签:
【中文标题】使用 Sequelize 计算关联条目【英文标题】:Counting associated entries with Sequelize 【发布时间】:2016-10-15 12:40:29 【问题描述】:我有两张桌子,locations
和 sensors
。 sensors
中的每个条目都有一个指向 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】:
HAVING
、ORDER BY
、INNER
与 OUTER
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 计算关联条目的主要内容,如果未能解决你的问题,请参考以下文章