Проверять существующие записи с перекрывающимися диапазонами элементов, принимая ноль за бесконечность

Я здесь, потому что искал некоторые запросы, которые могли бы помочь найти существующие записи с перекрывающимися диапазонами элементов, но ничего не нашел. Допустим, у меня есть модель по имени Cart. Корзина имеет следующие атрибуты: item_min и item_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

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
30
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Использование Range#overlaps?, ActiveRecord::Calculations#pluck и 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

Как написал Джад в комментарии, производительность такой проверки с массивами будет низкой, если есть миллион записей. Идея SQL-запроса с использованием встроенные диапазоны в PostgreSQL:

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

СУРБД оптимизирует такой запрос. Это будет намного эффективнее, чем работать с гигантским массивом

[] в этом запросе означает включающие нижнюю и верхнюю границы (по умолчанию верхняя граница исключается)

Использование NULL означает, что диапазон не ограничен

&& оператор проверяет перекрытия

SELECT numrange(10, NULL, '[]') && numrange(20, 40, '[]');
-- ?column? 
-- ----------
--  t

SELECT numrange(10, 20, '[]') && numrange(20, 40, '[]');
-- ?column? 
-- ----------
--  t

в зависимости от количества Cart, с которыми вы играете, вы можете отфильтровать их (до pluck), что-то вроде «где item_min <= new_item_max или item_max >= new_item_min», но приведенный выше код должен быть хорош, пока у вас не будет 1000 тележек или так :)

Jad 07.04.2022 10:24

Другие вопросы по теме