ã¯ããã«
ãGoogleã«ã¬ã³ãã¼ã®ãããªæéæ ãæ±ãã·ã¹ãã ãè¨è¨ããéãéå§ã»çµäºæå»ã管çãããã¸ãã¯ã¯å®¹æã§ã¯ãªãã ããããPostgreSQLã«ã¯ ç¯å²å ãããããã®æ©è½ãæ´»ç¨ãããã¨ã§ãéå§æå»ï¼begin_atï¼ã¨çµäºæå»ï¼end_atï¼ã1ã¤ã®ã«ã©ã ã§æ±ããããã«ãªãã ããã§æ¬ç¨¿ã§ã¯ãç¯å²åãç¨ããè¨è¨ã¨ããã®å©ç¹ãç´¹ä»ããã
æéæ ãæ±ãé£ãã
ãã¾ãåæã¨ãã¦æéæ ã®æ±ãããªãé£ããããç´¹ä»ããã
ãã½ããã¦ã§ã¢ãã¶ã¤ã³ã§ãã£ã¦ããé£è¼ã宿¦ãã¼ã¿ãã¼ã¹ãªãã¡ã¯ã¿ãªã³ã°ã® ã12ãåä»ãªæéæ ã«åãåã ã§ãç´¹ä»ããããæéã®ç¯å²ãæ¯è¼ããã¨ããé£ããã ç¯å²ã®éãªãã«ã¯ä»¥ä¸ã®ç¨®é¡ãããã
- å å«ï¼ç¯å²Aãç¯å²Bãå®å ¨ã«å«ã
- éè¤ï¼ç¯å²Aã¨ç¯å²Bã«å ±éç¹ããã
- 飿¥ï¼ç¯å²Aã¨ç¯å²Bãé£ãåã

ãæéæ ã®æ±ãã¯SQLã«éãããããã°ã©ãã³ã°ã®é¡æã¨ãã¦é£æåº¦ãé«ãã ç¹ã«éè¤ã¨å«æãè¤æ°ã®ãã¿ã¼ã³ã®å ´åããã¸ãã¯ãè¤éã«ãªãããã°ã®æ¸©åºã«ãªããããã ããã¯ããªã¼ããã«ã³ã¼ããã§ããã8.5 ä¾ï¼è¤éãªãã¸ãã¯ã¨æ ¼éãããã§ãã®åé¡ãåãä¸ãããã¦ããã
PostgreSQLã®ç¯å²åãç¨ããè¨è¨
ããã®åé¡ã解決ããããã«PostgreSQLã®ç¯å²åã®æ´»ç¨æ¹æ³ãç´¹ä»ããã ç¯å²åãå©ç¨ãããã¨ã§ãæéæ ã®éãªããå å«ã飿¥ãç´æçãªæ¼ç®åã§è¡¨ç¾ã§ããã³ã¼ãã®è¤éæ§ãå¤§å¹ ã«åæ¸ã§ããã
é«éã§æè»ãªæ¤ç´¢
ããã¼ã¿ã®ä¿åã ãã§ã¯ãªãããã¼ã¿ã®æ¤ç´¢ã§ãå¼·åã ã PostgreSQLã®å ¬å¼ããã¥ã¡ã³ãã«ããã¨ããã以ä¸ã®ãããªãã¨ãã§ããã
- ç¯å²ã®å å«
- ç¯å²ã®éè¤
- ç¯å²ã®é£æ¥
- 対象ç¯å²ã®éå§åãçµäºå¾ã
対象ç¯å²ã¨éãªãã®åå¾
ä¾ãã°äºç´æ¤ç´¢ã§ç¹å®ã®æé帯ï¼2024/12/10 12:00 - 13:00ï¼ã«å©ç¨å¯è½ãªè»ä¸¡ãæ¤ç´¢ããå ´åã¯æ¬¡ã®ãããSQLã«ãªã*1ã
-- carsãã¼ãã«: è»ä¸¡ã®åºæ¬æ å ±ã管ç CREATE TABLE cars ( car_id SERIAL PRIMARY KEY, car_name TEXT NOT NULL ); -- reservationsãã¼ãã«: è»ä¸¡IDã¨äºç´ãããæé帯ãç¯å²åã§ç®¡ç CREATE TABLE reservations ( reservation_id SERIAL PRIMARY KEY, car_id INT NOT NULL REFERENCES cars(car_id), reservation_time tstzrange NOT NULL ); -- è»ä¸¡ãã¼ã¿ã®ãµã³ãã«æå ¥ INSERT INTO cars (car_name) VALUES ('Car A'), ('Car B'), ('Car C'); -- äºç´ãã¼ã¿ã®ãµã³ãã«æå ¥ -- Car Aã¯2024/12/10 11:00ï½12:30ãCar Bã¯2024/12/10 12:30ï½13:30ã§äºç´ãããã¨ãã INSERT INTO reservations (car_id, reservation_time) VALUES (1, tstzrange('2024-12-10 11:00:00+09','2024-12-10 12:30:00+09','[)')), -- Car A (2, tstzrange('2024-12-10 12:30:00+09','2024-12-10 13:30:00+09','[)')); -- Car B
| car_id | car_name |
|---|---|
| 1 | Car A |
| 2 | Car B |
| 3 | Car C |
| reservation_id | car_id | reservation_time |
|---|---|---|
| 1 | 1 | ["2024-12-10 11:00:00+09","2024-12-10 12:30:00+09") |
| 2 | 2 | ["2024-12-10 12:30:00+09","2024-12-10 13:30:00+09") |
ããã®ãã¼ã¿ã§ããã°ã2024/12/10 12:00 - 2024/12/10 13:00 ã§æ¤ç´¢ããã¨æ¬¡ã®ããã«ãªãã
- Car A ã¯11:00ï½12:30ã¾ã§äºç´æ¸ã¿ã®ããã12:00ï½13:00ã®ç¯å²ã¨éãªã
- Car B ã¯12:30ï½13:30ã¾ã§äºç´æ¸ã¿ãªã®ã§ã12:00ï½13:00ã¨ã¯12:30ï½13:00ãéãªã
- Car C ã¯äºç´ãªãã®ããéãªããå©ç¨å¯è½
ããããå®éã«ãã¼ãã«ã«å¯¾ãã¦æ¤ç´¢ããã¨ä»¥ä¸ã®ããã«ãªãã
SELECT c.*
FROM cars c
WHERE NOT EXISTS (
SELECT 1
FROM reservations r
WHERE r.car_id = c.car_id
AND r.reservation_time && tstzrange('2024-12-10 12:00:00+09', '2024-12-10 13:00:00+09', '[)'));
| car_id | car_name |
|---|---|
| 3 | Car C |

ãä»ã«ãæå®ãããæéã«äºç´ããã¦ããè»ä¸¡ãæ¢ãå ´åã¯ä»¥ä¸ã®ãããªSQLã«ãªãã
SELECT c.* FROM cars c JOIN reservations r ON c.car_id = r.car_id WHERE r.reservation_time @> '2024-12-10 12:50:00+09'::timestamptz;
| car_id | car_name |
|---|---|
| 2 | Car B |

ããã®ããã«æéæ ã«å¯¾ãã¦ãæè»ãªæ¤ç´¢ãã§ãããã¨ãç¯å²åã§æéãæ±ãã¡ãªããã§ããã
INDEXã®æ´»ç¨
ã@> ã && ãªã©ã®æ¼ç®åæ¤ç´¢ã¯GiST INDEXãè¨å®ãããã¨ã§é«éã«æ¤ç´¢ã§ããã
è¨å®æ¹æ³ã¯ä»¥ä¸ã®éãã
-- æ¤ç´¢ç¨ã®INDEX CREATE INDEX idx_reservations_reservation_time_gist ON reservations USING gist (reservation_time);
ä»åã¯ã¬ã³ã¼ãã2è¡ãããªãã®ã§å®è¡è¨ç»ãè¦ã㨠idx_reservations_reservation_time_gist ã®INDEXã¯ä½¿ããªããã¡ããã¨1000件以ä¸ã®ãã¼ã¿ãªã©ã§ããã°INDEXã使ã£ã¦ãããã
æä»å¶ç´ã«ããããã«ãããã³ã°é²æ¢
ãæéã®æ±ãã®é£ããã¨ããã«æ¤ç´¢ã¨åãããããããã«ãããã³ã°ã®å¯¾å¿ãããã ä»åã®å ´åãåãè»ä¸¡ã«å¯¾ããäºç´ã®éè¤ãé²ãæ¹æ³ã¨ãã¦æä»å¶ç´ãããã ããã«ã¤ãã¦ã¯éå»ç´¹ä»ããã®ã§ãã¡ããè¦ã¦ã»ããã
ãä»åã®ä¾ã§æä»å¶ç´ãæå¹åããå ´åã¯ä»¥ä¸ã®ã¨ããã
-- æä»å¶ç´ã®æå¹å -- btree_gistæ¡å¼µã®æå¹å(ã¾ã ã§ãªããã°) CREATE EXTENSION IF NOT EXISTS btree_gist; -- æä»å¶ç´ã®è¿½å ALTER TABLE reservations ADD CONSTRAINT reservations_no_overlap EXCLUDE USING gist ( car_id WITH =, reservation_time WITH && );
ããã®ããã«è¤æ°ã®è»ä¸¡ããã£ãã¨ãã¦ããåãè»ä¸¡ã¸ã®äºç´ã®ããã«ãããã³ã°ãã¿ãããªãã¨ãRDBMSã®å¶ç´ã§é²ããã¨ãã§ããã ããã¯ä¾ãã°ããã«ã®é¨å±ç®¡çãè»ã®æéå²ã®æ å½ã®ã¢ãµã¤ã³ãªã©æ§ã ãªã¹ã±ã¸ã¥ã¼ã«ç®¡çã®ã·ã¼ã³ã§ã¨ã¦ãæå¹ãªé¸æè¢ã§ããã
PostgreSQL 14以éã®ãè¤æ°ç¯å²åã対å¿
ãããã¾ã§ãç¯å²åã®è©±ãªã®ã ããããã«ããã«PostgreSQL 14ããè¤æ°ç¯å²åããµãã¼ãããããã¨ã§ããé²åããæéæ ã®æ±ãã¨ãã¦æ±ºå®çã¨ãããè¨è¨ãå¯è½ã«ãªã£ãã®ã§ç´¹ä»ããã
ç¯å²åãç¨ããè¨ç®
ãç¯å²åã使ãã¨ãç¯å²åå£«ã®æ¼ç®ï¼å¼ãç®ãªã©ï¼ãå¯è½ã«ãªãã ãã ãã1ã¤ã®ç¯å²ãè¤æ°ã«åå²ãããã±ã¼ã¹ã«ããã¦ã¯ã徿¥ã®ç¯å²åã§ã¯ERRORã«ãªããæ±ããå°é£ã§ãã£ãã ããããè¤æ°ç¯å²åã使ãã°ãåå²å¾ã®ç¯å²ããã¼ã¿åã¨ãã¦ä¿æã§ããã

ããããæ´»ç¨ãããã¨ã§ããã¨ãã°åºèã®å¶æ¥æéï¼ç¯å²ï¼ããæ¢åã®äºç´æ ï¼ç¯å²ï¼ãå¼ãç®ããç¾å¨ã®ç©ºãæéï¼è¤æ°ç¯å²ï¼ãè¨ç®ããã¨ãã£ããã¨ãå¯è½ã«ãªã£ãã
WITH store_hours AS ( -- å¶æ¥æéãmultirangeã¸ãã£ã¹ã SELECT tstzrange('2024-12-10 09:00:00+09', '2024-12-10 18:00:00+09', '[)')::tstzmultirange AS open_time), reserved AS ( -- å¶æ¥æé帯ã«éãªãäºç´ã1ã¤ã®multirangeã«éç´ SELECT range_agg(reservation_time)::tstzmultirange AS reserved_time FROM reservations WHERE reservation_time && tstzrange('2024-12-10 09:00:00+09', '2024-12-10 18:00:00+09', '[)')) SELECT unnest(open_time - COALESCE(reserved_time, '{}'::tstzmultirange)) AS free_slots FROM store_hours, reserved;
| free_slots |
|---|
| ["2024-12-10 09:00:00+09","2024-12-10 11:00:00+09") |
| ["2024-12-10 13:30:00+09","2024-12-10 18:00:00+09") |

ãè¤æ°ç¯å²ç¯å²åã使ããã¨ã§è¤æ°åã®äºç´æ ãäºç´å¯è½ãããªã©ã®æ¤ç´¢ãã§ããã ããã«ãã£ã¦ãè¤æ°æ¥ç¨ã®æ è¡ã®äºç´æ æ¤ç´¢ãªã©ãå¯è½ã«ãªãã
æ¤ç´¢ãæä»å¶ç´ã¸ã®å¿ç¨
ãè¨ç®ã ãã§ãªãæ¤ç´¢ãæä»å¶ç´ãåæ§ã«è¨å®ã§ããã ä¾ãã°ç¤¾å¡ã®ä»æã®ã·ãããä½ãéã«åºå¤æ¥åä½ã§ã¬ã³ã¼ããå¢ãã¦ãããããªå ´åã¯æåä½ã§ã¬ã³ã¼ããã¾ã¨ãããã¨ã§ã¬ã³ã¼ãæ°ãæ¸ãããã
ç¯å²åã使ã£ã¦æå¹ãªæéã®ä¸è¦§ãåºã
ãæå¾ã«PostgreSQLã® generate_series() ã¨ç¯å²åãçµã¿åãããã¨åºèã§ã®åä»å¯è½ãªæéæ ä¸è¦§ãçæã§ããã
ããã§ä»åã¯60ååä½ã®äºç´æ ã30åå»ã¿ã§åä»å¯è½ãªéå§æå»ã¨ãã¦ä¸è¦§åããSQLãç´¹ä»ããã
WITH slots AS (
SELECT gs AS start_time,
-- å¶æ¥æéã®ä¸è¦§ããäºç´ã«å¿
è¦ãªæéã®æ ãçæãã
tstzrange(gs, gs + INTERVAL '60 minutes', '[)') AS slot_range
FROM generate_series(
-- 30åå»ã¿ã®å¯¾è±¡ã®å¶æ¥æéã®ä¸è¦§ãçæãã
'2024-12-10 09:00:00+09'::timestamptz,
'2024-12-10 18:00:00+09'::timestamptz,
'30 minutes'
) AS gs
)
SELECT start_time, slot_range
FROM slots
WHERE NOT EXISTS (
SELECT 1
FROM reservations r
-- slots.slot_rangeãäºç´ã«å«ã¾ãã¦ããªããã¨ã確èª
WHERE r.reservation_time && slot_range
)
ORDER BY start_time;
| start_time | slot_range |
|---|---|
| 2024-12-10 09:00:00.000000 +09:00 | ["2024-12-10 09:00:00+09","2024-12-10 10:00:00+09") |
| 2024-12-10 09:30:00.000000 +09:00 | ["2024-12-10 09:30:00+09","2024-12-10 10:30:00+09") |
| 2024-12-10 10:00:00.000000 +09:00 | ["2024-12-10 10:00:00+09","2024-12-10 11:00:00+09") |
| 2024-12-10 13:30:00.000000 +09:00 | ["2024-12-10 13:30:00+09","2024-12-10 14:30:00+09") |
| 2024-12-10 14:00:00.000000 +09:00 | ["2024-12-10 14:00:00+09","2024-12-10 15:00:00+09") |
| 2024-12-10 14:30:00.000000 +09:00 | ["2024-12-10 14:30:00+09","2024-12-10 15:30:00+09") |
| 2024-12-10 15:00:00.000000 +09:00 | ["2024-12-10 15:00:00+09","2024-12-10 16:00:00+09") |
| 2024-12-10 15:30:00.000000 +09:00 | ["2024-12-10 15:30:00+09","2024-12-10 16:30:00+09") |
| 2024-12-10 16:00:00.000000 +09:00 | ["2024-12-10 16:00:00+09","2024-12-10 17:00:00+09") |
| 2024-12-10 16:30:00.000000 +09:00 | ["2024-12-10 16:30:00+09","2024-12-10 17:30:00+09") |
| 2024-12-10 17:00:00.000000 +09:00 | ["2024-12-10 17:00:00+09","2024-12-10 18:00:00+09") |
| 2024-12-10 17:30:00.000000 +09:00 | ["2024-12-10 17:30:00+09","2024-12-10 18:30:00+09") |
| 2024-12-10 18:00:00.000000 +09:00 | ["2024-12-10 18:00:00+09","2024-12-10 19:00:00+09") |
ãããã«ãã£ã¦ãäºç´æ¸ã¿ã®æé帯ãé¤å¤ããåè£æ ãä¸è¦§è¡¨ç¤ºã§ããã ãã¨ãã°11:00ããã¯Car Aã®äºç´ãå ¥ã£ã¦ããããã10:30以éã¯åä»ãã§ããªãã

ãã®ãã¿ã¼ã³ãäºç´ãµã¤ããªã©ã§é »åºããã¦ã¼ã¹ã±ã¼ã¹ã ãããã®ã¯ã¨ãªã¯ INDEXãå©ç¨ããã¦ãããããé«éã«æ¤ç´¢ ã§ãã¦ãã
注æç¹
ãPostgreSQLã®ç¬èªæ©è½ã使ãå®è£ ãªã®ã§MySQLãªã©ã«ç§»è¡ãããã¨ã¯ã§ããªããªãã ã¾ãORMã対å¿ãã¦ããªããã¨ãå¤ããå®éã«ã¯äºç´æ å¯è½ãã¼ãã«ã¨ãã¦Viewãå®ç¾©ãã¦ãããã«å¯¾ãã¦ORMããæ±ããã¨ãå¤ããªãã Viewã®æ±ããªã©ã¯ãã¤ã°ã¬ã¼ã·ã§ã³ã®æ¹æ³ã«ãå½±é¿ãä¸ããã®ã§ãéç¨ãå«ãã¦æ¤è¨ãã¦ã»ããã
çµããã«
ãè¤æ°ç¯å²åãç¯å²åãå©ç¨ããæéæ ã®æ±ããç´¹ä»ããã èªåã®é¢ãã£ã¦ããããã¸ã§ã¯ãã§ã¯å®éã«æ´»ç¨ãã¦ãã¦ãå®ç¸¾ããããä»ã«ããã¸ã§ã¯ãªã©ã§ã¯è¦ããã¨ããªãã®ã§ç´¹ä»ããã æ°è¦æ¡ä»¶ã®å ´åãªã©ã¯ãã®è¨è¨ã®ããã ãã«PostgreSQLã鏿ããã¡ãªãããããã®ã§ãã²æ¤è¨ãã¦ã»ããã
*1:ChatGPTã§ãµã³ãã«ä½ã£ãããã¡ã便å©ããããã¡ããã¨åã