ruby 删除具有重叠项范围的现有记录,将nil视为无穷大

eblbsuwk  于 12个月前  发布在  Ruby
关注(0)|答案(1)|浏览(103)

我在这里,因为我一直在寻找一些查询,可以帮助找到现有的记录与重叠的项目范围,但我找不到任何东西。假设我有一个名为Cart的模型。购物车具有以下属性:item_minitem_max,其中item_max可为空,当为空时将被视为无限。在我的模型中,我想添加一个验证,这样就不会保存具有重叠项目范围的记录。
我已经创建了一个查询,但它并不适用于我的所有测试用例:

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

然而,这种验证并不适用于我的所有测试用例。你能帮我改进我的查询,这样我的测试用例就可以通过了吗?
顺便说一句,我正在使用PostgreSQL

vc6uscn9

vc6uscn91#

使用Range#overlaps?ActiveRecord::Calculations#pluckArray#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

相关问题