Sequelize - 如何提取具有 1 个关联记录的记录并按关联记录属性排序

Posted

技术标签:

【中文标题】Sequelize - 如何提取具有 1 个关联记录的记录并按关联记录属性排序【英文标题】:Sequelize - How to pull records with 1 associated record and order by associated record attribute 【发布时间】:2019-11-26 15:08:45 【问题描述】:

我有一个 Convos 表,里面有很多消息。

我想要什么:拉出所有 convos 和最后一条消息。通过last_message.created_at订购车队

models.Convos.findAll(
    include: [
        
            model: models.Messages,
            as: "last_message",
            order: [ [ 'created_at', 'DESC' ]],
            limit: 1,
         
    ],
    where:
        [Op.or]: [
            
                sender_id: req.decoded.id
            ,
            
                recipient_id: req.decoded.id
            
        ],
    ,
)

我最接近订购的是:

order: [
  [model: models.Messages, as: 'last_message', 'created_at', 'DESC'],
],

但这给出了错误:

`Unhandled rejection SequelizeDatabaseError: missing FROM-clause entry for table "last_message"`

从这里,我猜这个错误可能暗示有没有任何消息的 convos,使 last_message.created_at 未定义(虽然我可能完全误解了这个错误)。

因此,从那里开始,我一直在尝试在 where 语句中添加一个子句,该子句只提取至少有 1 条消息的 convos。这是我尝试过的一堆东西,它们都抛出了错误:

添加到哪里:

Sequelize.literal("`last_message`.`id` IS NOT NULL")


'$models.Messages.id$':  [Op.ne]: null ,

Sequelize.fn("COUNT", Sequelize.col("last_message")):  [Op.gt]: 0 
'$last_message.id$':  [Op.ne]: null 

'$last_message.id$': 
  [Op.ne]: null

我也尝试过having 而不是where 声明:

having: Sequelize.where(Sequelize.fn('COUNT', Sequelize.col('$last_message.id$')), '>=', 0)

如何按关联记录 last_message.created_at 对 convos 进行正确排序?

更新 - CONVOS 模型的相关部分

"use strict";
module.exports = (sequelize, DataTypes) => 
  let Convos = sequelize.define(
    "Convos",
    
      sender_id: 
        type: DataTypes.INTEGER,
        references: 
          model: "Users",
          key: "id"
        
      ,
      recipient_id: 
        type: DataTypes.INTEGER,
        references: 
          model: "Users",
          key: "id"
        
      ,
      created_at: DataTypes.DATE,
      updated_at: DataTypes.DATE
    ,
    
      timestamps: false,
      freezeTableName: true,
      schema: "public",
      tableName: "convos"
    
  );

  Convos.associate = models => 
    Convos.hasMany(models.Messages, 
      as: "last_message",
      foreignKey: "convo_id",
      sourceKey: "id"
    );
  ;

  return Convos;
;

更新 当关联模型具有limit 时,我发现问题在于使用 Sequelize.literal。例如,这有效:

models.Convos.findAll(
  include: [
  
        model: models.Messages,
        as: "last_message",
        order: [ [ 'created_at', 'DESC' ]],
        //limit: 1,
        required: true,
        duplicating: false, 
    ,
],
where: 
  [Op.or]: [
      
          sender_id: req.decoded.id
      ,
      
          recipient_id: req.decoded.id
      
  ],
,
order: [[Sequelize.literal(`last_message.created_at`), 'DESC']],
offset: offset,
limit: 10,
).then(convos =>  ....

但是当我取消注释包含部分中的 limit: 1 时,我收到错误消息: Unhandled rejection SequelizeDatabaseError: missing FROM-clause entry for table "last_message"

这里是没有limit 1的查询日志:

Executing (default): SELECT "Convos"."id", "Convos"."sender_id", "Convos"."recipient_id", "Convos"."created_at", "Convos"."updated_at", "last_message"."id" AS "last_message.id", "last_message"."body" AS "last_message.body", "last_message"."read" AS "last_message.read", "last_message"."group_meeting_id" AS "last_message.group_meeting_id", "last_message"."user_id" AS "last_message.user_id", "last_message"."created_at" AS "last_message.created_at", "last_message"."updated_at" AS "last_message.updated_at", "last_message"."convo_id" AS "last_message.convo_id", "last_message->user"."id" AS "last_message.user.id", "last_message->user"."first_name" AS "last_message.user.first_name", "last_message->user"."avatar_file_name" AS "last_message.user.avatar_file_name", "senderUser"."id" AS "senderUser.id", "senderUser"."first_name" AS "senderUser.first_name", "senderUser"."avatar_file_name" AS "senderUser.avatar_file_name", "recipientUser"."id" AS "recipientUser.id", "recipientUser"."first_name" AS "recipientUser.first_name", "recipientUser"."avatar_file_name" AS "recipientUser.avatar_file_name" FROM "public"."convos" AS "Convos" INNER JOIN "public"."msgs" AS "last_message" ON "Convos"."id" = "last_message"."convo_id" LEFT OUTER JOIN "public"."users" AS "last_message->user" ON "last_message"."user_id" = "last_message->user"."id" LEFT OUTER JOIN "public"."users" AS "senderUser" ON "Convos"."sender_id" = "senderUser"."id" LEFT OUTER JOIN "public"."users" AS "recipientUser" ON "Convos"."recipient_id" = "recipientUser"."id" WHERE ("Convos"."sender_id" = 32 OR "Convos"."recipient_id" = 32) ORDER BY last_message.created_at DESC LIMIT 10 OFFSET 70;

使用limit: 1查询:

Executing (default): SELECT "Convos"."id", "Convos"."sender_id", "Convos"."recipient_id", "Convos"."created_at", "Convos"."updated_at", "senderUser"."id" AS "senderUser.id", "senderUser"."first_name" AS "senderUser.first_name", "senderUser"."avatar_file_name" AS "senderUser.avatar_file_name", "recipientUser"."id" AS "recipientUser.id", "recipientUser"."first_name" AS "recipientUser.first_name", "recipientUser"."avatar_file_name" AS "recipientUser.avatar_file_name" FROM "public"."convos" AS "Convos" LEFT OUTER JOIN "public"."users" AS "senderUser" ON "Convos"."sender_id" = "senderUser"."id" LEFT OUTER JOIN "public"."users" AS "recipientUser" ON "Convos"."recipient_id" = "recipientUser"."id" WHERE ("Convos"."sender_id" = 32 OR "Convos"."recipient_id" = 32) ORDER BY last_message.created_at DESC LIMIT 10 OFFSET 0;

这里有一些链接有助于理解 limit 导致问题,但我仍然没有找到解决这个问题的解决方案。

Link 1

Link 2

Link 3

Link 4

谢谢!

【问题讨论】:

你知道如何在不使用 sequelize.js 的情况下获得想要的结果吗?仅仅通过使用 SQL? @madflow 喜欢纯 SQL 吗?我试图用Sequelize.literal("last_message.id` IS NOT NULL")` 写一些东西,简短的回答是否定的——否则我不会发布问题哈哈 :) 我理解这个概念,但我正在努力获取正确的语法。 那么 - 请发布您的表结构和数据库引擎。锦上添花:创建一个小提琴:db-fiddle.com。从那里开始 - 将其转换为 sequalize.js 生态系统应该很容易。 制作小提琴没有意义 - 我不知道如何写这个,这就是问题所在。 db 关系已经说明 - convo 有很多消息。这个问题不需要整个表结构,更多的是关于orderwhere 语句的sequelize 语法的问题。 在你说having而不是where的最后一点,不应该是>0(不是>=) - (Sequelize.fn('COUNT', Sequelize. col('$last_message.id$')), '>', 0) 【参考方案1】:

免责声明:我不知道续集。并且 - 有三个记录版本,您没有说明您使用的是哪一个。

我想要什么:拉出所有 convos 和最后一条消息。按以下顺序订购车队 last_message.created_at

我可以提供 SQL (Postgres) 解决方案。

我假设有一个 convos 表(类似):

CREATE TABLE convos (
  id INT PRIMARY KEY,
  sender_id INT,
  recipient_id INT
);

还有一个消息表

CREATE TABLE messages (
    id INT PRIMARY KEY,
    convo_id INT REFERENCES convos (id),
    created_at timestamp  without time zone
);

还有一些测试数据:

INSERT INTO 
convos (id, sender_id, recipient_id) 
VALUES (1,1,1), (2,2,2), (3,3,4);

INSERT INTO 
messages (id, convo_id, created_at) 
VALUES 
(1,1, '1990-07-24'), 
(2,1, '2019-07-24'), 
(3,2, '1990-07-24'), 
(4,2, '2019-07-24'), 
(5,3, null);

当您要查询convos 表并从messages 表中获取最新消息时,您必须在子查询(或CTE,或横向连接,...)中使用messages 的结果.

例如:

SELECT 
    convos.sender_id,
    convos.recipient_id,
    messages.last_message_date
FROM convos
LEFT JOIN 
(
    SELECT convo_id, max(created_at) as last_message_date
    FROM messages 
    GROUP BY convo_id
) messages ON convos.id=messages.convo_id
ORDER BY  messages.last_message_date DESC

所以对于 sequelize.js - 您必须了解它如何使用关联模型进行子查询并使用结果。

【讨论】:

嗨 - 感谢您的尝试。您是否阅读了我的帖子中的更新部分?它显示了一个有效的查询,但如果我尝试限制为仅 1 条消息,则会引发错误。我正在使用 sequelize.js。同样 - 我将 convos 模型和关联放在问题中。 是的 - 我已阅读更新。我对此的看法是:您只能限制子查询中的消息。如果 sequelize 有一个 api - 或者你可以使用literal,宾果游戏。也许您可以在他们的聊天中获得帮助:sequelize-slack.herokuapp.com. 并链接这个问题。

以上是关于Sequelize - 如何提取具有 1 个关联记录的记录并按关联记录属性排序的主要内容,如果未能解决你的问题,请参考以下文章

如何获取至少有 1 个关联行与 sequelize 的行

Sequelize 基于关联的查找

如何从sequelize中的实例中排除关联属于多?

跨 Sequelize 关联播种数据

使用 sequelize N:M 关联实现“平坦”结果的“正确”方法是啥

如何嵌套 Sequelize 关联?