Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Range on Rails ―「多重範囲型」という新たな選択肢が、複雑ロジックを劇的にシンプ...

Range on Rails ―「多重範囲型」という新たな選択肢が、複雑ロジックを劇的にシンプルにしたワケ

■ イベント
2025年9月26日-27日開催
Kaigi on Rails 2025
https://kaigionrails.org/2025/

■ セッション情報
2025年9月27日登壇
https://kaigionrails.org/2025/talks/umeda-rizap#day2

■ 発表者
バックエンドエンジニア
梅田 智大

Avatar for RIZAPテクノロジーズ

RIZAPテクノロジーズ

October 06, 2025
Tweet

More Decks by RIZAPテクノロジーズ

Other Decks in Programming

Transcript

  1. 16 PostgreSQLのRange型では 分断された範囲 を扱えない ◼ 差集合の結果は、分断された複数の範囲となる tsrange('2025-09-26 12:00', '2025-09-26 13:00')

    - tsrange('2025-09-26 12:10', '2025-09-26 12:40'); -- 結果は「12:00〜12:10」と「12:40〜13:00」に分断される -- 複数の範囲はrange型では扱えずエラーとなる ERROR: result of range difference would not be contiguous ◼ Range型ではひとつの連続した範囲しか扱えないのでエラーになる -- 例: 「12:00〜13:00」から「12:10〜12:40」を引く
  2. 18 PostgreSQLの「多重範囲型」とは ◼ 複数の範囲をArrayのようにまとめて扱えるデータ型 ◼ 範囲の種類によって複数の型がある Int4multirange 整数の多重範囲 tsmultirange タイムスタンプの多重範囲

    datemultirange 日付の多重範囲 1..10, 13..15, 30..33, 37..40, 50..100, 110..112 2025-09-26 9:00..2025- 09-26 12:00, 2025-09-26 14:00.. 2025-09-27 18:00 2025-09-01..2025-09-05, 2025-02-10..2025-02-12, 2025-09-26..2025-09-27
  3. 19 多重範囲型は範囲型と同様の演算を利用できる ◼ 範囲型と同じように 重なり・積集合・差集合 が扱える! -- 重なり(overlaps) SELECT int4multirange(1,

    10) && int4multirange(5, 15); -- 結果: t -- 積集合(intersection) SELECT int4multirange(1, 10) * int4multirange(5, 15); -- 結果: {[5, 10)} -- 差集合(difference) SELECT int4multirange(1, 10) - int4multirange(5, 7); -- 結果: {[1, 5), [7, 10)}
  4. 21 予約不可な複数の期間を多重範囲に集約 ◼ 既存の予約期間、店舗の休業期間、ルームのメンテナンス期間… などの、予約不可な複数の期間をひとつの多重範囲に集約していきます -- コード例(イメージ) tsmultirange(‘{ [2025-09-21 10:30,

    2025-09-21 11:00), [2025-09-22 00:00, 2025-09-24 22:00), [2025-09-25 14:00,2025-09-25 15:30), ... }’) -- 結果: {["2025-09-21 10:30:00","2025-09-21 11:00:00"),["2025-09-22 00:00:00","2025-09-24 22:00:00"),["2025-09-25 14:00:00","2025-09-25 15:30:00"), ...} 1
  5. 22 -- コード例(イメージ) tsmultirange(‘{ [2025-09-21 00:00, 2025-09-28 00:00) }’) --

    結果: {["2025-09-21 00:00:00","2025-09-28 00:00:00")} 空き状況を確認する対象期間も多重範囲に変換 2 ◼ 差集合の演算は多重範囲同士でのみ可能なため、 空き状況を確認する対象期間も多重範囲に変換します
  6. 23 -- コード例(イメージ) tsmultirange(1週間の多重範囲) – tsmultirange(予約不可期間の多重範囲) -- 結果: { ["2025-09-21

    00:00:00","2025-09-21 10:30:00"), ["2025-09-21 11:00:00","2025-09-22 00:00:00"), ["2025-09-24 22:00:00","2025-09-25 14:00:00"), ... } 予約可能な期間を差集合で算出! ◼ 空き状況を確認する対象期間の多重範囲と、 予約不可な期間の多重範囲の差集合を算出! ◼ 差集合の結果が、予約可能な期間となる 3
  7. 24 「多重範囲型」の活用シーンは様々! 複数の雑多な期間を集計する場面で、 多重範囲型 (multirange) は 威力を発揮する かもしれない ◼ サブスクリプションの利用期間

    (休止・再開を含む複数期間) ◼ 勤怠シフト管理 (勤務・休憩・残業の時間帯を集合で管理) ◼ コンテンツ配信/視聴時間の集計 (視聴セッションの多区間集約) ◼ 在庫や貸出管理 (貸出・返却で空き期間を差集合で算出) ◼ システム障害の発生 〜 復旧時間の管理 (障害発生率の算出など) ヒント
  8. 30 VIEWを利用して集計した例 -- 予約できない期間群 → multirangeに集約 → 差集合で空き期間を集計 → multirangeをrangeに展開

    CREATE VIEW reservation_availabilities AS WITH -- 1) 予約できない期間をUNIONでひとつの表にまとめる reservation_unavailabilities AS ( -- 既存の予約 SELECT room_id, period FROM reservations UNION ALL -- ルームのメンテナンス SELECT room_id, period FROM room_reservation_unavailabilities UNION ALL -- 店舗の休業 SELECT r.id AS room_id, sr.period FROM studio_reservation_unavailabilities sr JOIN rooms r ON r.studio_id = sr.studio_id ), -- 2) range_aggで予約不可を「多重範囲(multirange)」に集約 multi_reservation_unavailabilities AS ( SELECT room_id, range_agg(period) AS multi_period FROM reservation_unavailabilities GROUP BY room_id ), -- 3) 1週間の範囲 - 予約不可範囲 = 予約可能範囲 reservation_availabilities AS ( SELECT room_id, tsmultirange( tsrange(current_date, current_date + interval '7 days’) ) - multi_period AS multi_period FROM multi_reservation_availabilities ) -- 4) multirange を unnest で展開して、個々の空き期間(tsrange)にする SELECT room_id, unnest(multi_period) AS period FROM reservation_availabilities;
  9. 31 VIEWに対応するModelを作ればコードはシンプルに! # app/models/reservation_availability.rb class ReservationAvailability < ApplicationRecord belongs_to :room

    end # app/models/room.rb class Room < ApplicationRecord has_many :reservation_availabilities end # 使い方 room = Room.find(params[:id]) room.reservation_availabilities # => 予約可能な期間の一覧