【问题标题】:Filtering chain issue with ActiveRecord and Arel (SQL)ActiveRecord 和 Arel (SQL) 的过滤链问题
【发布时间】:2020-10-05 05:43:23
【问题描述】:

设置如下:

class Title
  has_many :managed_rights
end
class ManagedRight
  belongs_to :title
  has_many :managed_right_countries
  enumerize :territory_rights, in: [:all, :include, :exclude], default: :all
end
class ManagedRightCountry
  belongs_to :managed_right
  belongs_to :country
end
class Country; end

我想按国家/地区过滤标题,其中country_ids = [3, 5, 8, 9]。

  • 所有案例 如果title.managed_rights[0].territory_rights == :all选择这个标题
  • 如果包含大小写 仅当 managed_right_countries 中的任何一个在 country_ids 中时才选择,否则跳过。
title.managed_rights[0].territory_rights == :include 
AND title.managed_rights[0].managed_right_countries IN country_ids
  • 如果排除案例 选择所有标题,除非 managed_right_countries 中的任何一个在 country_ids 中。
title.managed_rights[0].territory_rights == :exclude
AND title.managed_rights[0].managed_right_countries ALL NOT IN country_ids

这就是我对 Arel 的尝试:

managed_rights = ManagedRight.arel_table
managed_right_countries = ManagedRightCountry.arel_table

Title.left_outer_joins(managed_rights: :managed_right_countries)
  .where(
    # some other filters not related to "managed_rights"
    managed_rights[:territory_rights].eq(:all)
      .or(
        managed_rights[:territory_rights].eq(:include)
        .and(managed_right_countries[:country_id].in(country_ids))
        .or(
          managed_rights[:territory_rights].eq(:exclude)
          .and(managed_right_countries[:country_id].not_in(country_ids))
        )
      )
  ).distinct

它适用于大多数情况,除非managed_right_countries.country_id 有一些不在country_ids 数组中的ID。上面的查询 :exclude 的含义仅在所有国家/地区都在数组中时才有效。 但是如果数组中有任何国家,我需要从结果中排除标题。

我怎样才能让它正常工作?

最好使用 AREL,但如果无法仅使用 Arel,原始 SQL(PostgreSQL) 也可以工作。

示例:

title_01 has `managed_rights[0].territory_rights = :all`
title_02 has `managed_rights[0].territory_rights = :include`
         and `managed_rights[0].managed_right_countries = [1, 2]`
title_03 has `managed_rights[0].territory_rights = :exclude`
         and `managed_rights[0].managed_right_countries = [2, 3]`
  • 当按国家 [1] 过滤时,应选择所有 3 个标题(title_02 包含 1 个,但 title_03 不排除)
  • 当按国家 [2] 时,仅应选择 title_01 和 title_02(因为排除了三分之三)
  • 当按国家 [3] 时,仅应选择 title_01,因为 title_02 不包括 3,而 title_03 不包括 3
  • 如果按空国家/地区过滤 [],则应显示所有三个

【问题讨论】:

    标签: sql ruby-on-rails ruby postgresql activerecord


    【解决方案1】:

    通过创建嵌套子查询来解决。

    SQL解决方案:

    SELECT titles.* FROM titles
    LEFT OUTER JOIN managed_rights ON managed_rights.title_id = titles.id
    LEFT OUTER JOIN managed_right_countries ON managed_right_countries.managed_right_id = managed_rights.id
    WHERE (titles.rights = 'all' OR managed_rights.title_id is NULL)
    OR managed_rights.territory_rights = 'all'
    OR (
        managed_rights.territory_rights = 'include' AND managed_right_countries.country_id in (country_ids)
    )
    OR (
        managed_rights.territory_rights = 'exclude' 
            AND NOT titles.id in (
                SELECT titles.id FROM titles
                LEFT OUTER JOIN managed_rights ON managed_rights.title_id = titles.id
                LEFT OUTER JOIN managed_right_countries ON managed_right_countries.managed_right_id = managed_rights.id
                WHERE managed_rights.territory_rights = 'exclude' 
                    AND managed_right_countries.country_id in (country_ids)
            )
    )
    

    AREL 解决方案:

    titles = Title.arel_table
    managed_rights = ManagedRight.arel_table
    managed_right_countries = ManagedRightCountry.arel_table
    
    excluded_title_ids = titles.project('titles.id')
      .join(managed_rights, Arel::Nodes::OuterJoin)
      .on(managed_rights[:title_id].eq(titles[:id]))
      .join(managed_right_countries, Arel::Nodes::OuterJoin)
      .on(managed_right_countries[:managed_right_id].eq(managed_rights[:id]))
      .where(
        managed_rights[:territory_rights].eq(:exclude)
          .and(managed_right_countries[:country_id].in(available_countries))
      )
    
    Title.left_outer_joins(managed_rights: :managed_right_countries)
      .where(
        managed_rights[:territory_rights].eq(:all)
        .or(
          managed_rights[:territory_rights].eq(:include)
            .and(managed_right_countries[:country_id].in(available_countries))
            .or(
              managed_rights[:territory_rights].eq(:exclude)
              .and(titles[:id].not_in(excluded_title_ids))
            )
        )
      )
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-20
      • 2016-08-27
      • 1970-01-01
      • 2013-05-27
      相关资源
      最近更新 更多