【问题标题】:Rails active record query to retrive all records that contains ALL IDS in the array of IDSRails活动记录查询检索IDS数组中包含ALL 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 %>

【问题讨论】:

    标签: sql ruby-on-rails activerecord


    【解决方案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 }
      

      【讨论】:

        猜你喜欢
        • 2012-05-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-02-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多