我跟踪了这篇博客:https://info.crunchydata.com/blog/range-types-recursion-how-to-search-availability-with-postgresql
CREATE TABLE travels (
id serial PRIMARY KEY,
travel_dates daterange NOT NULL,
EXCLUDE USING spgist (travel_dates WITH &&)
);
当我连续插入具有持续时间的行时,我发现这个函数有缺陷
CREATE OR REPLACE FUNCTION travels_get_available_dates(daterange)
RETURNS TABLE(available_dates daterange)
AS $$
WITH RECURSIVE calendar AS (
SELECT
$1 AS left,
$1 AS center,
$1 AS right
UNION
SELECT
CASE travels.travel_dates && calendar.left
WHEN TRUE THEN daterange(lower(calendar.left), lower(travels.travel_dates * calendar.left))
ELSE daterange(lower(calendar.right), lower(travels.travel_dates * calendar.right))
END AS left,
CASE travels.travel_dates && calendar.left
WHEN TRUE THEN travels.travel_dates * calendar.left
ELSE travels.travel_dates * calendar.right
END AS center,
CASE travels.travel_dates && calendar.right
WHEN TRUE THEN daterange(upper(travels.travel_dates * calendar.right), upper(calendar.right))
ELSE daterange(upper(travels.travel_dates * calendar.left), upper(calendar.left))
END AS right
FROM calendar
JOIN travels ON
travels.travel_dates && $1 AND
travels.travel_dates <> calendar.center AND (
travels.travel_dates && calendar.left OR
travels.travel_dates && calendar.right
)
)
SELECT *
FROM (
SELECT
a.left AS available_dates
FROM calendar a
LEFT OUTER JOIN calendar b ON
a.left <> b.left AND
a.left @> b.left
GROUP BY a.left
HAVING NOT bool_or(COALESCE(a.left @> b.left, FALSE))
UNION
SELECT
a.right AS available_dates
FROM calendar a
LEFT OUTER JOIN calendar b ON
a.right <> b.right AND
a.right @> b.right
GROUP BY a.right
HAVING NOT bool_or(COALESCE(a.right @> b.right, FALSE))
) a
$$ LANGUAGE SQL STABLE;
INSERT INTO travels (travel_dates)
VALUES
(daterange('2018-03-02', '2018-03-02', '[]')),
(daterange('2018-03-06', '2018-03-09', '[]')),
(daterange('2018-03-11', '2018-03-12', '[]')),
(daterange('2018-03-16', '2018-03-17', '[]')),
(daterange('2018-03-25', '2018-03-27', '[]'));
这在此时按预期工作。
SELECT *
FROM travels_get_available_dates(daterange('2018-03-01', '2018-04-01'))
ORDER BY available_dates;
available_dates
-------------------------
[2018-03-01,2018-03-02)
[2018-03-03,2018-03-06)
[2018-03-10,2018-03-11)
[2018-03-13,2018-03-16)
[2018-03-18,2018-03-25)
[2018-03-28,2018-04-01)
但是当添加此行时:
INSERT INTO travels (travel_dates)
VALUES
(daterange('2018-03-03', '2018-03-05', '[]'));
然后重新运行
SELECT *
FROM travels_get_available_dates(daterange('2018-03-01', '2018-04-01'))
ORDER BY available_dates;
我明白
available_dates
-------------------------
empty
4条答案
按热度按时间roqulrg31#
我在原博客文章中添加了一条评论,说明我认为错误是从哪里产生的,也就是说,在处理空范围的方式中。
当日期范围是连续的,或者更确切地说是相邻的时,它在“左”列和“右”列中的任一列或甚至两者中导致“空”范围。(并且假设空范围在‘left’列中),在‘LEFT OUTER JOIN…ON…’子句中,自由且有效的travel_date与从B.left range since A.left〈〉‘empty’&& A.left @〉‘empty’的‘empty’范围配对,因为所有范围都平凡地包含空范围。理想地,它应该与NULL配对,因为这是一个左外连接,它将被包含在最终结果集中,但'empty'有点 * 碍事 *。'empty'然后再次弹出'GROUP BY... HAVING...'子句,其中.left @〉'empty'计算为true,并且它's被否定,因此所有有效的旅行日期都被丢弃,导致一个空表。我的解决方案如下,将'emptys'设置为NULL,并丢弃'center'中的任何日期范围:
yyyllmsg2#
我认为你应该采取另一种方法:
我们必须递归地将给定的范围拆分为左段和右段,然后只得到那些未被占用的部分。
lskq00tm3#
我原来忘记了“中心”区域的条款。下面是:
sr4lhrrt4#
我不能让递归函数工作--我只会得到一个无限循环。然而,你不需要递归来解决这个问题!你可以使用PostgreSQL WINDOWS来代替。
https://www.postgresql.org/docs/current/tutorial-window.html
鉴于原件:
并插入以下值:
下面的SQL将找到所有可用的日期(我已经抛出了连续可用日期的数量,因为这是我需要的东西):
注意,available_count为null表示在该点(旅行表中的最后日期)无限可用。此外,如果你想限制这两个给定日期之间的可用性,你可以添加一个WHERE子句(例如限制在2018-03-01和2018-03-15之间):
在这种情况下,您希望忽略空值;我还没有想出最干净的方法来做到这一点,但你可以使它成为一个子查询…
我相信有一个更巧妙的方法来做到这一点......但我不知道是什么:)注意,如果你想在你的范围内包括现在的一天,你可以在你的WHERE子句中使用'today',并在你的'travels'表中获得从现在到第一天的可用性。
我希望这对其他人有帮助;这大概是两天的工作来解决这个问题!