验证具有重叠项目范围的现有记录,将 nil 视为无限
Validate existing records with overlap item ranges taking nil as infinite
我来这里是因为我一直在四处寻找一些可以帮助查找具有重叠项目范围的现有记录的查询,但我找不到任何东西。假设我有一个名为 Cart
的模型。 Cart 具有以下属性:item_min
和 item_max
其中 item_max
可以为 null,当为 nil 时将被视为无限。在我的模型中,我想添加一个验证,这样就不会保存具有重叠项目范围的记录。
我创建了一个查询,但它不适用于我的所有测试用例:
saved cart: item_min: 2, item_max: nil
try to save cart: `item_min: 1, item_max: 1` VALID
try to save cart: `item_min: 1, item_max: 2` VALID
try to save cart: `item_min: 1, item_max: 6` INVALID
try to save cart: `item_min: 4, item_max: 7` INVALID
try to save cart: `item_cart: 4, item_max: nil` INVALID
saved cart: `item_min: 2, item_max: 7`
try to save `cart: item_min: 1, item_max: 1` VALID
try to save cart: `item_min: 8, item_max: 10` VALID
try to save cart: `item_min: 8, item_max: nil` VALID
try to save cart: `item_min: 1, item_max: 2` INVALID
try to save cart: `item_min: 1, item_max: 8` INVALID
try to save cart: `item_min: 1, item_max: 5` INVALID
try to save cart: `item_min: 5, item_max: 10` INVALID
try to save cart: `item_min: 3, item_max: 5` INVALID
try to save cart: `item_min: 1, item_max: nil` INVALID
我创建了以下查询:
def validate_item_count_range
if item_count_max.nil?
overlap_carts = Cart.where(item_count_max: nil)
else
overlap_carts = Cart.where(
"item_count_min >= ? AND item_count_max <= ?", item_count_min, item_count_max,
).or(
Cart.where(
"item_count_min <= ? AND item_count_max IS NULL", item_count_min,
),
)
end
errors.add(:item_count_min, "overlaps with existing carts") if overlap_carts.present?
end
但是,此验证不适用于我的所有测试用例。你能帮我改进我的查询,让 mi 测试用例可以通过吗?
顺便说一句,我正在使用 postgresql
使用Range#overlaps?
, ActiveRecord::Calculations#pluck
and Array#any?
无特殊SQL查询
if Cart.pluck(:item_min, :item_max).any? { |min, max| (min..max).overlaps?(item_min..item_max) }
errors.add(:base, :overlaps_with_existing_carts)
end
无限范围有一个明确的开始值,但有一个 nil
结束值。你可以省略这个 nil
(8..nil) == (8..)
# => true
这样的范围包括从开始值开始的所有值
(8..nil).overlaps?(4..6)
# => false
(8..nil).overlaps?(4..9)
# => true
当然,这种方法适用于通常的范围
(4..6).overlaps?(6..8)
# => true
(4..6).overlaps?(1..3)
# => false
正如 Jad 在评论中所写,如果有数百万条记录,使用数组进行此类验证的性能将会很低。使用 built-in ranges in PostgreSQL 进行 SQL 查询的想法:
if Cart.exists?(["numrange(item_count_min, item_count_max, '[]') && numrange(?, ?, '[]')", item_count_min, item_count_max])
errors.add(:base, :overlaps_with_existing_carts)
end
RDBMS 将优化此类查询。它会比使用巨型数组
更有效
[]
在此查询中表示包含下限和上限(默认情况下上限是独占的)
使用NULL
表示范围无界
&&
运算符检查重叠
SELECT numrange(10, NULL, '[]') && numrange(20, 40, '[]');
-- ?column?
-- ----------
-- t
SELECT numrange(10, 20, '[]') && numrange(20, 40, '[]');
-- ?column?
-- ----------
-- t
我来这里是因为我一直在四处寻找一些可以帮助查找具有重叠项目范围的现有记录的查询,但我找不到任何东西。假设我有一个名为 Cart
的模型。 Cart 具有以下属性:item_min
和 item_max
其中 item_max
可以为 null,当为 nil 时将被视为无限。在我的模型中,我想添加一个验证,这样就不会保存具有重叠项目范围的记录。
我创建了一个查询,但它不适用于我的所有测试用例:
saved cart: item_min: 2, item_max: nil
try to save cart: `item_min: 1, item_max: 1` VALID
try to save cart: `item_min: 1, item_max: 2` VALID
try to save cart: `item_min: 1, item_max: 6` INVALID
try to save cart: `item_min: 4, item_max: 7` INVALID
try to save cart: `item_cart: 4, item_max: nil` INVALID
saved cart: `item_min: 2, item_max: 7`
try to save `cart: item_min: 1, item_max: 1` VALID
try to save cart: `item_min: 8, item_max: 10` VALID
try to save cart: `item_min: 8, item_max: nil` VALID
try to save cart: `item_min: 1, item_max: 2` INVALID
try to save cart: `item_min: 1, item_max: 8` INVALID
try to save cart: `item_min: 1, item_max: 5` INVALID
try to save cart: `item_min: 5, item_max: 10` INVALID
try to save cart: `item_min: 3, item_max: 5` INVALID
try to save cart: `item_min: 1, item_max: nil` INVALID
我创建了以下查询:
def validate_item_count_range
if item_count_max.nil?
overlap_carts = Cart.where(item_count_max: nil)
else
overlap_carts = Cart.where(
"item_count_min >= ? AND item_count_max <= ?", item_count_min, item_count_max,
).or(
Cart.where(
"item_count_min <= ? AND item_count_max IS NULL", item_count_min,
),
)
end
errors.add(:item_count_min, "overlaps with existing carts") if overlap_carts.present?
end
但是,此验证不适用于我的所有测试用例。你能帮我改进我的查询,让 mi 测试用例可以通过吗?
顺便说一句,我正在使用 postgresql
使用Range#overlaps?
, ActiveRecord::Calculations#pluck
and Array#any?
无特殊SQL查询
if Cart.pluck(:item_min, :item_max).any? { |min, max| (min..max).overlaps?(item_min..item_max) }
errors.add(:base, :overlaps_with_existing_carts)
end
无限范围有一个明确的开始值,但有一个 nil
结束值。你可以省略这个 nil
(8..nil) == (8..)
# => true
这样的范围包括从开始值开始的所有值
(8..nil).overlaps?(4..6)
# => false
(8..nil).overlaps?(4..9)
# => true
当然,这种方法适用于通常的范围
(4..6).overlaps?(6..8)
# => true
(4..6).overlaps?(1..3)
# => false
正如 Jad 在评论中所写,如果有数百万条记录,使用数组进行此类验证的性能将会很低。使用 built-in ranges in PostgreSQL 进行 SQL 查询的想法:
if Cart.exists?(["numrange(item_count_min, item_count_max, '[]') && numrange(?, ?, '[]')", item_count_min, item_count_max])
errors.add(:base, :overlaps_with_existing_carts)
end
RDBMS 将优化此类查询。它会比使用巨型数组
更有效[]
在此查询中表示包含下限和上限(默认情况下上限是独占的)
使用NULL
表示范围无界
&&
运算符检查重叠
SELECT numrange(10, NULL, '[]') && numrange(20, 40, '[]');
-- ?column?
-- ----------
-- t
SELECT numrange(10, 20, '[]') && numrange(20, 40, '[]');
-- ?column?
-- ----------
-- t