ActiveRecord 和 Arel 的过滤链问题 (SQL)
Filtering chain issue with ActiveRecord and Arel (SQL)
设置如下:
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解法:
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))
)
)
)
设置如下:
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解法:
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))
)
)
)