Rails活动记录查询检索IDS数组中包含ALL IDS的所有记录

Posted

技术标签:

【中文标题】Rails活动记录查询检索IDS数组中包含ALL IDS的所有记录【英文标题】:Rails active record query to retrive all records that contains ALL IDS in the array of IDS 【发布时间】:2021-12-01 07:50:20 【问题描述】:

一个视频可以有多个类别。

video.rb 
has_many :video_categories
has_many :categories, through: :video_categories

category.rb
has_many :video_categories
has_many :videos, through: :video_categories

我有一个简单的表单,允许用户选择他想要组合的类别以查找特定的视频。例如,有“python”类别和“高级”类别。如果他选择了这两个类别,它应该显示同时具有这两个类别的视频。

视频 A - 类别 [1,4,7](这是类别 ID)

视频 B - 类别 [1,2,7,9]

视频 C - 类别 [7,9]

如果用户选择类别 [1,7],则输出应该是视频 A 和 B。我返回的当前范围是具有类别 1 的所有视频、具有类别 7 的所有视频和具有两者的视频其中。

我只想要同时包含这两个视频的视频。我该怎么做?

当前范围:

video.rb
scope :for_categories, -> (category_ids) 
    joins(:video_categories).where(
      video_categories:  category_id: category_ids 
    )
  

pages_controller.rb
def search
 @results = Video.for_categories(params[:category_ids])
end

我的表格

        <%= form_with url: "/search", method: :get do |form| %>
            <%= form.collection_check_boxes(:category_ids, Category.all,
                :id, :title,  prompt: 'None',  multiple: true ) do |cat| %>
              <label class="text-capitalize checkbox-inline mr-2">
                <%= cat.check_box %>
                <%= cat.label %>
              </label>
            <% end %>
          <%= form.submit "Search" %>
        <% end %>

【问题讨论】:

【参考方案1】:

我能够通过以下代码找到解决方案

video.rb
scope :by_categories, -> (category_ids) 
    joins(:categories).where(categories: [category_ids] )
  

  scope :by_all_categories, -> (category_ids) 
    by_categories(category_ids).
    group("videos.id").
    having('count(videos.id) >= ?', category_ids.size)
  
pages_controller.rb
def search
    @results = Video.by_all_categories(params[:category_ids].reject(&:empty?))
end

【讨论】:

【参考方案2】:

您想应用一个 GROUP 并使用 HAVING 对组设置条件以确保加入的类别数与 id 数匹配:

class Video
  def self.for_categories(*category_ids)
     ids = category_ids.flatten # handles a single array as input
     group(:id)
       .joins(:categories)
       .where(categories:  id:  ids )
       .having(
          Category.arel_table[:id]
                  .count
                  .gteq(ids.length)
       )
  end
end

就 SQL 而言,这将为您提供如下内容:

SELECT videos.*
FROM videos
INNER JOIN video_categories ON video_categories.video_id = videos.id
INNER JOIN categories ON video_categories.category_id = categories.id
WHERE categories.id IN (1, 2, 3)
GROUP BY videos.id
HAVING COUNT(categories.id) >= 3

【讨论】:

在 Postgres 上,您可以使用 Category.arel_table[Arel.star] 来获取 COUNT(categories.*),这比 COUNT(categories.id) 稍微高效一些。 mysql 或 SQLite 不支持它。 在我看来,我得到了一个 ActiveRecord::StatementInvalid。 &lt;% @results.each do |video| %&gt; 在我的控制器中:def search @results = Video.for_categories(params[:category_ids])。我错过了什么? 糟糕,应该是where(categories: id: ids )【参考方案3】:
categories_to_search = [1, 7]

conditions = categories_to_search.map do |c|
   viedo_categories:  category_id: c  
do

Video.where conditions
# this is the same as: Video.where viedo_categories:  category_id: 1 , viedo_categories:  category_id: 7 

【讨论】:

以上是关于Rails活动记录查询检索IDS数组中包含ALL IDS的所有记录的主要内容,如果未能解决你的问题,请参考以下文章

Ruby On Rails 5,activerecord query其中模型关联id包括数组中的所有id

如何从 Rails 中的查询中排除 id 数组(使用 ActiveRecord)?

ValueError:在 LinearSVC 期间,数组在 _assert_all_finite 中包含 NaN 或无穷大

Rails活动记录单个错误修补程序

如何使用代码点火器活动记录查询返回一维数组中的表字段

如何在rails的where子句中包含if条件