MongoDB对HABTM关系(Mongoid,RoR)的条件聚合查询?

Posted

技术标签:

【中文标题】MongoDB对HABTM关系(Mongoid,RoR)的条件聚合查询?【英文标题】:MongoDB conditional aggregate query on a HABTM relationship (Mongoid, RoR)? 【发布时间】:2016-05-29 18:12:42 【问题描述】:

Rails 4.2.5, Mongoid 5.1.0

我有三个模型 - MailboxCommunicationMessage

mailbox.rb

class Mailbox
    include Mongoid::Document
    belongs_to :user
    has_many :communications
end

communication.rb

class Communication
    include Mongoid::Document
    include Mongoid::Timestamps
    include AASM

    belongs_to :mailbox
    has_and_belongs_to_many :messages, autosave: true

    field :read_at,     type: DateTime
    field :box,         type: String
    field :touched_at,  type: DateTime
    field :import_thread_id, type: Integer
    scope :inbox, ->  where(:box => 'inbox') 
end

message.rb

class Message
    include Mongoid::Document
    include Mongoid::Timestamps

    attr_accessor :communication_id

    has_and_belongs_to_many :communications, autosave: true
    belongs_to :from_user, class_name: 'User'
    belongs_to :to_user, class_name: 'User'

    field :subject, type: String
    field :body,    type: String
    field :sent_at, type: DateTime
end

我正在使用身份验证 gem devise,它可以访问指向当前登录用户的 current_user 助手。

我为满足以下条件的控制器构建了一个查询: 获取current_usermailbox,其communicationbox 字段过滤,其中box == 'inbox'。 它是这样构造的(并且正在工作):

current_user.mailbox.communications.where(:box => 'inbox')

当我尝试构建此查询时,我的问题出现了。我希望链接查询,以便我只获得messageslast 消息不是来自current_user。我知道 .last 方法,它返回最近的记录。我提出了以下查询,但无法理解需要调整哪些内容才能使其正常工作:

current_user.mailbox.communications.where(:box => 'inbox').where(:messages.last.from_user => '$ne' => current_user)

此查询产生以下结果: undefined method 'from_user' for #<Origin::Key:0x007fd2295ff6d8>

我目前可以通过执行以下操作来完成此操作,我知道这非常低效,想立即更改:

mb = current_user.mailbox.communications.inbox

comms = mb.reject |c| c.messages.last.from_user == current_user

我希望将此逻辑从 ruby​​ 转移到实际的数据库查询中。提前感谢任何在这方面为我提供帮助的人,如果这里有任何有用的信息,请告诉我。

【问题讨论】:

我不认为 ActiveRecord 可以为您做到这一点 - 基于聚合(最后)的条件可能太复杂了。您可能不得不求助于原始 SQL。 有错误吗?你写。where(:messages.last.from_user => '$ne' => current_user)条件正在评论)但在current_user.mailbox.communications.reject |c| c.last.from_user == current_user 条件正在交流 @PJSCopeland,mongo 不是 SQL 数据库 @ljlozano,也许您正在寻找***.com/questions/5550253/… 和docs.mongodb.org/v3.0/reference/operator/aggregation/last(它也是聚合)。所以你的问题是如何在 mongo db 中使用聚合条件 @NickRoz 我很抱歉,是的,这是一个错字。我已经更新了我的问题。我现在也要看看这些链接。 【参考方案1】:

好的,所以这里发生的事情有点混乱,并且与 Mongoid 在进行关联时的实际智能程度有关。

特别是在两个关联之间“交叉”时如何构造查询。

对于您的第一个查询:

current_user.mailbox.communications.where(:box => 'inbox')

这对 mongoid 来说很酷,因为它实际上只是将 2 个 db 调用脱糖:

    获取用户当前邮箱 Mongoid 直接针对通信集合构建一个标准,其中一条 where 语句表示:使用项目 1 中的邮箱 id,并过滤到 box = inbox。

现在,当我们处理您的下一个问题时,

current_user.mailbox.communications.where(:box => 'inbox').where(:messages.last.from_user => '$ne' => current_user)

是 Mongoid 开始困惑的时候。

这是主要问题:当您使用“where”时,您正在查询您所在的集合。您不会跨越关联

where(:messages.last.from_user => '$ne' => current_user) 实际上在做的是检查消息关联。 Mongoid 实际上正在做的是在 communication 文档中搜索一个属性,该属性的 JSON 路径类似于:communication['messages']['last']['from_user']。

既然您知道了原因,您就可以得到您想要的,但与同等的 ActiveRecord 工作相比,这需要更多的汗水。

您可以通过以下方式获得想要的东西:

user_id = current_user.id
communication_ids = current_user.mailbox.communications.where(:box => 'inbox').pluck(:_id)
# We're going to need to work around the fact there is no 'group by' in
# Mongoid, so there's really no way to get the 'last' entry in a set
messages_for_communications = Messages.where(:communications_ids => "$in" => communications_ids).pluck(
  [:_id, :communications_ids, :from_user_id, :sent_at]
)
# Now that we've got a hash, we need to expand it per-communication,
# And we will throw out communications that don't involve the user
messages_with_communication_ids = messages_for_communications.flat_map do |mesg|
  message_set = []
  mesg["communications_ids"].each do |c_id|
    if communication_ids.include?(c_id)
      message_set << (:id => mesg["_id"],
       :communication_id => c_id,
       :from_user => mesg["from_user_id"],
       :sent_at => mesg["sent_at"])
    end
  message_set
end
# Group by communication_id
grouped_messages = messages_with_communication_ids.group_by  |msg| mesg[:communication_id] 
communications_and_message_ids = 
grouped_messages.each_pair do |k,v|
  sorted_messages = v.sort_by  |msg| msg[:sent_at] 
  if sorted_messages.last[:from_user] != user_id
    communications_and_message_ids[k] = sorted_messages.last[:id]
  end
end
# This is now a hash of :communication_id => :last_message_id
communications_and_message_ids

我不确定我的代码是否 100%(您可能需要检查文档中的字段名称以确保我搜索的是正确的),但我认为您了解了一般模式。

【讨论】:

以上是关于MongoDB对HABTM关系(Mongoid,RoR)的条件聚合查询?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Mongoid 查看原始 mongoDB 查询

使用 mongoid 查看 MongoDB 中的现有索引

Ruby on Rails 是 mongodb - mongoid

Mongoid / Mongodb 和查询嵌入文档

如何将内存中的 MongoDB 与 Rails、Mongoid 和 Rspec 一起使用?

在非 Rails 环境中通过 Mongoid 创建 MongoDB 索引