根据多对多关系返回记录列表的最快方法

Posted

技术标签:

【中文标题】根据多对多关系返回记录列表的最快方法【英文标题】:Fastest way to return a list of records depending on many many-to-many relationships 【发布时间】:2018-10-06 22:44:05 【问题描述】:

我正在 Rails 中开发一个 API,其中存在 usersmessages 表。 users 也有性别 (gender_id),属于一个国家 (country_id) 并有公民身份 (civil_status_id),messages 是由 admins 创建的。

到目前为止,我有这个模型。

现在我要开发以下需求

管理员应该能够根据其属性(国家、性别或公民身份)创建针对users 的消息。此外,管理员应该能够将消息声明为全局消息,在这种情况下,“所有”users 应该接收它,但也应该允许例外。例如,如果管理员想要向所有国家/地区的用户发送消息,俄罗斯和中国除外。

问题是我不是 Rails/SQL 专家,但我想有效地做到这一点,以便如果明天应用程序有一万或十万用户,服务器会快速响应。

所以我在想以下

首先创建 3 个多对多关系(countries_messagesgenders_messagescivil_statuses_messages)。这些表的记录代表messagescountriescivil_statusesgenders之间的关系。

然后创建一个表单,管理员可以在其中创建消息,通过几个选择框,他应该能够选择他想要访问的用户的属性。创建消息的表单还应该有一个复选框来确定消息是否是全球性的,如果它被标记,那么我会认为所选国家、性别和公民身份将是管理员想要排除的类别,即如果管理员想要向系统中的所有人发送消息,除了来自加拿大的人,他应该标记全局选项并在选择框中选择国家加拿大(显然这将在视图中说明)。

到这里为止,我有这个模型。

我确实怀疑哪种方式更有效地返回与用户对应的消息。

方法一

当管理员指定消息是全球性的,除了来自 ID 为 3 的国家/地区的消息,我可以添加到 countries_messages 记录,例如 (country_id: 1, message_id: 2), (country_id: 2, message_id: 2) , (country_id: 4, message_id: 2), ... 等,即与除 id 为 2 的国家以外的所有国家/地区建立关系。

然后检索当前用户应该阅读的消息,如下所示:

global_messages = Message.where(global: true).ids

country_messages = current_user.country.messages.ids
gender_messages = current_user.gender.messages.ids
civil_status_messages = current_user.current_status.messages.ids

@messages = Message.find(global_messages + country_messages + gender_messages + civil_status_messages)

方法二

其他方式可能是将该消息与排除的国家/地区建立关系,即,如果我专门为来自 id 为 2 的国家/地区的人发送消息,那么我应该在 @987654344 中添加记录 (country_id: 2, message_id: 2) @,但在相反的情况下,如果我向除 id 为 2 的国家/地区以外的每个国家/地区发送消息,那么我还应该将记录 (country_id: 2, message_id: 2) 添加到 countries_messages

在这种情况下,我可以知道是否针对男性和阿根廷人排除了一条消息,例如,如果该消息是全球性的,并且它与代表阿根廷和男性的国家和性别记录相关联。

那么当前用户应该阅读的消息的检索将是这样的:

global_messages = Message.where(global: true).ids

country_messages = current_user.country.messages
gender_messages = current_user.gender.messages
civil_status_messages = current_user.current_status.messages

excluded_country_messages_ids = country_messages.where(global: true).ids
excluded_gender_messages_ids = gender_messages.where(global: true).ids
excluded_civil_status_messages_ids = civil_status_messages.where(global: true).ids

@messages = Message.find(global_messages + country_messages + gender_messages + civil_status_messages - excluded_country_messages_ids - excluded_gender_messages_ids - excluded_civil_status_messages_ids)

可能有更多方法可以做到这一点,所以我希望收到建议,或者如果您发现我可以改进以做同样的事情,请告诉我。有不明白的可以追问。

【问题讨论】:

【参考方案1】:

根据您的数据库选择,您可能需要考虑将消息“属性”(国家、性别、...)存储在消息表的 jsonb 列中。它还可以包含一个user_ids 属性来简化事情。查询可能看起来有点滑稽,但您可以将很多内容移入范围以清除代码中的内容,甚至添加索引以加快处理速度。

Here is a great article on using jsonb with indexes in Rails with postgresql.

您可以做的另一种选择是创建一个带有多态引用的UserMessage 连接表,该引用可以将不同的属性与消息和用户相关联:

class UserMessage < ApplicationRecord
  belongs_to :user
  belongs_to :message
  belongs_to :messageable_type # e.g. "Country"
  belongs_to :messageable_id # e.g. some Country id
end

【讨论】:

以上是关于根据多对多关系返回记录列表的最快方法的主要内容,如果未能解决你的问题,请参考以下文章

Prisma:在显式多对多关系中创建或连接记录

mysql 多对多映射关系的筛选SQL怎么写

在 NHibernate 中定义多对多关系以允许删除但避免重复记录的正确方法是啥

如何为多对多关系创建 UI?

使用 Laravel 限制多态多对多关系中的相关记录

过滤具有多对多关系的对象,检查它是不是包含列表中的至少一个元素