Sequelize GROUP BY 仅在主表上聚合,而不是连接中的完整连接表

Posted

技术标签:

【中文标题】Sequelize GROUP BY 仅在主表上聚合,而不是连接中的完整连接表【英文标题】:Sequelize GROUP BY aggregating only on main table instead of the complete joined table in a join 【发布时间】:2016-09-17 07:21:59 【问题描述】:

我有五个表USER_ATTRIBQUESTION_MAINREPLY_MAINCATEGORY_MAINQSTN_CATG,相互关联如下:

m.QUESTION_MAIN.belongsTo(m.USER_ATTRIB,  foreignKey: 'POSTER_I', targetKey: 'USER_I');
m.QUESTION_MAIN.hasMany(m.REPLY_MAIN,  foreignKey: 'QSTN_I' );
m.QUESTION_MAIN.belongsToMany(m.CATEGORY_MAIN,  through: m.QSTN_CATG, foreignKey: 'QSTN_I' );
m.QUESTION_MAIN.hasMany(m.QSTN_CATG,  foreignKey: 'QSTN_I' );

我想在QUESTION_MAIN 上运行查询以获取有关问题的详细信息。需要的细节之一是对问题的回复数量,可以通过

查询

SELECT COUNT(REPLY_MAINs.QSTN_I) GROUP BY QSTN_I;

我要运行的组合查询是:

SELECT `QUESTION_MAIN`.*
       ,`USER_ATTRIB`.`USERATTRIB_ID` AS `USER_ATTRIB.USERATTRIB_ID`
       ,`USER_ATTRIB`.`USER_NAME` AS `USER_ATTRIB.USER_NAME`
       ,`QSTN_CATGs`.`QSTN_CATG_ID` AS `QSTN_CATGs.QSTN_CATG_ID`,
       ,`QSTN_CATGs`.`CATG_I` AS `QSTN_CATGs.QSTN_CATG_I`
       ,`REPLY_MAINs`.`REPLY_ID` AS `REPLY_MAINs.REPLY_ID`
       , COUNT(`REPLY_MAINs`.`QSTN_I`) AS `REPLY_MAINs.REPLY_COUNT`
FROM (
        SELECT `QUESTION_MAIN`.`QUESTION_ID`
               , ( 6371 * acos( cos( radians(13.0508629) ) * cos( radians( QSTN_LOC_LAT ) ) * cos( radians( QSTN_LOC_LONG ) - radians(77.6092108) ) + sin( radians(13.0508629) ) * sin( radians( QSTN_LOC_LAT ) ) ) ) AS `DISTANCE`
        FROM `QUESTION_MAIN` AS `QUESTION_MAIN` 
        WHERE (
                SELECT `QSTN_I` 
                FROM `QSTN_CATG` AS `QSTN_CATG` 
                WHERE (`QSTN_CATG`.`QSTN_I` = `QUESTION_MAIN`.`QUESTION_ID`) LIMIT 1 
                ) IS NOT NULL  
        HAVING `DISTANCE` < 5 
        ORDER BY `QUESTION_MAIN`.`CREATED` DESC LIMIT 3
        ) AS `QUESTION_MAIN` 
LEFT OUTER JOIN `USER_ATTRIB` AS `USER_ATTRIB` ON `QUESTION_MAIN`.`POSTER_I` = `USER_ATTRIB`.`USER_I` 
INNER JOIN `QSTN_CATG` AS `QSTN_CATGs` ON `QUESTION_MAIN`.`QUESTION_ID` = `QSTN_CATGs`.`QSTN_I` 
LEFT OUTER JOIN `REPLY_MAIN` AS `REPLY_MAINs` ON `QUESTION_MAIN`.`QUESTION_ID` = `REPLY_MAINs`.`QSTN_I` 
              AND `REPLY_MAINs`.`REPLY_STATUS` = 200
GROUP BY `QUESTION_ID` 
ORDER BY `QUESTION_MAIN`.`CREATED` DESC;

这是进行该查询的 Sequelize:

QUESTION_MAIN.findAll(
attributes:['QUESTION_ID', 'POSTER_I',
  ['( 6371 * acos( ' 
                  + 'cos( radians('+qstnFeedRequest.qstnLocLat+') ) ' 
                  + '* cos( radians( QSTN_LOC_LAT ) ) '
                  + '* cos( radians( QSTN_LOC_LONG ) - radians('+ qstnFeedRequest.qstnLocLong+') ) '
                  + '+ sin( radians('+qstnFeedRequest.qstnLocLat+') ) '
                  + '* sin( radians( QSTN_LOC_LAT ) ) ) '
  + ')', 'DISTANCE'
  ]
],
include: [
   model: USER_ATTRIB, 
    attributes:['USER_NAME']
  ,
   model: QSTN_CATG, 
    attributes: [['CATG_I', 'QSTN_CATG_I']],
    where: qstnCatgWhereClause
  ,
   model: REPLY_MAIN, 
    attributes: [[sequelize.fn('COUNT', sequelize.col('REPLY_MAINs.QSTN_I')), 'REPLY_COUNT']], 
    where: REPLY_STATUS: 200,
    required: false
  
],
having: 'DISTANCE' : $lt: 5 ,
where: whereClause,
group: ['QUESTION_ID'],
limit: qstnFeedRequest.limit
)

问题是GROUP BY 子句被应用在内部内部查询中,而不是整个连接:

SELECT `QUESTION_MAIN`.*,
   ...
FROM (
    SELECT `QUESTION_MAIN`.`QUESTION_ID`,
    ...  
    HAVING `DISTANCE` < 5 
    GROUP BY `QUESTION_ID` -- This should go outside
    ORDER BY `QUESTION_MAIN`.`CREATED` DESC LIMIT 3
    ) AS `QUESTION_MAIN` 
LEFT OUTER JOIN `USER_ATTRIB` ...
ORDER BY `QUESTION_MAIN`.`CREATED` DESC;

这会导致计数错误聚合。无论我尝试什么,我都无法从内部查询中取出 GROUP BY 子句。

如何对整个连接进行分组而不是单独对主表进行分组?

【问题讨论】:

更新:由于限制,生成的 SQL 有一个子查询,这在 1:M (as said here) 的上下文中是有意义的。删除限制删除了子查询并在整个连接上分组,这解决了我的问题。现在,我想保持限制并将 GROUP BY 推到子查询之外。我该怎么做? 【参考方案1】:

翻了半天,终于找到了解决办法。

正如link is in the comment 的线程中所说,使用连接之外的限制进行 1:M 查询是低效的。因此,Sequelize 对 1:1 和 1:M 关系进行单独查询考虑到separate: true 属性在 1:M 表的包含语句中设置。

即使在此之后,还有几个问题:

如果表的连接列未包含在属性中,则代码会中断。

Sequelize 还将外部 having 子句应用于内部表。为了防止这种情况,我在包含中添加了一个真实的having 声明。

这是我修改后的最终续集:

QUESTION_MAIN.findAll(
attributes:['QUESTION_ID', 'POSTER_I',
  ['( 6371 * acos( ' 
                  + 'cos( radians('+qstnFeedRequest.qstnLocLat+') ) ' 
                  + '* cos( radians( QSTN_LOC_LAT ) ) '
                  + '* cos( radians( QSTN_LOC_LONG ) - radians('+ qstnFeedRequest.qstnLocLong+') ) '
                  + '+ sin( radians('+qstnFeedRequest.qstnLocLat+') ) '
                  + '* sin( radians( QSTN_LOC_LAT ) ) ) '
  + ')', 'DISTANCE'
  ]
],
include: [
   model: USER_ATTRIB, 
    attributes:['USER_NAME']
  ,
   model: QSTN_CATG, 
    attributes: [['CATG_I', 'QSTN_CATG_I']],
    where: qstnCatgWhereClause
  ,
   model: REPLY_MAIN, //this is the 1:M table
    attributes: ['QSTN_I', [sequelize.fn('COUNT', sequelize.col('REPLY_MAIN.QSTN_I')), 'REPLY_COUNT']], 
    //QSTN_I is the column joining QUESTION_MAIN and REPLY_MAIN. Not including this in the attributes throws an error 
    where: REPLY_STATUS: 200,
    group: ['QSTN_I'], //grouping it in this query instead of the main query
    separate: true,//the culprit
    having: 'REPLY_COUNT': $ne: null, //this is a dummy having clause which always returns true. This is added to stop the outer having clause being applied to the inner query
    required: false
  
],
having: 'DISTANCE' : $lt: 5 ,
where: whereClause,
limit: qstnFeedRequest.limit
)

希望这可以节省某人 2 天的时间

【讨论】:

以上是关于Sequelize GROUP BY 仅在主表上聚合,而不是连接中的完整连接表的主要内容,如果未能解决你的问题,请参考以下文章

为啥在主表上查询很慢?

在主表上的数据库触发器中将数据插入临时表

如何在主表上加入按天聚合的数据

在 sequelize 中使用 group by 和 joins

Sequelize join with group by

将数据添加到主表上的多个记录的链接表中