在多行中查找/到日期 - SQL Postgres

时间:2015-04-16 13:37:05

标签: sql postgresql

我希望能够在日期范围内“预订”,但是您无法预订日期差距。因此,只要它们是连续的,就可以预订多种费率。

如果有更好的存储起始/结束范围的方法,我很乐意改变数据结构/索引。

到目前为止,我有一个“费率”表,其中包含每日费率的开始/结束时间段。

e.g。费率表。

ID  Price   From        To
1   75.00   2015-04-12  2016-04-15
2   100.00  2016-04-16  2016-04-17
3   50.00   2016-04-18  2016-04-30

对于上述数据,我想返回:

From        To
2015-04-12  2016-4-30

为简单起见,可以安全地假设日期安全连续。对于连续日期,To始终为from之前的一天。

对于只有1行的情况,我希望它返回该行的From / To。

另外澄清我是否有以下数据:

ID  Price   From        To
1   75.00   2015-04-12  2016-04-15
2   100.00  2016-04-17  2016-04-18
3   50.00   2016-04-19  2016-04-30
4   50.00   2016-05-01  2016-05-21

意味着存在间隙>= 1 day,它将被视为一个单独的范围。 在这种情况下,我希望如下:

From        To
2015-04-12  2016-04-15
2015-04-17  2016-05-21

编辑1

在玩完之后,我想出了以下似乎有效的SQL。虽然我不确定它是否有更好的方法/问题?

WITH grouped_rates AS 
(SELECT
  from_date,
  to_date,
  SUM(grp_start) OVER (ORDER BY from_date, to_date) group
FROM (SELECT
        gite_id,
        from_date,
        to_date,
        CASE WHEN (from_date - INTERVAL '1 DAY') = lag(to_date)
            OVER (ORDER BY from_date, to_date)
        THEN 0
        ELSE 1
        END grp_start
      FROM rates
      GROUP BY from_date, to_date) AS start_groups)
SELECT
  min(from_date) from_date,
  max(to_date)   to_date
FROM grouped_rates
GROUP BY grp;

2 个答案:

答案 0 :(得分:2)

这是在数据中识别连续的重叠组。一种方法是找出每个组开始的位置,然后进行累积求和。以下查询添加一个标志,指示行是否启动组:

select r.*,
       (case when not exists (select 1
                              from rates r2
                              where r2.from < r.from and r2.to >= r.to or
                                    (r2.from = r.from and r2.id < r.id)
                             )
             then 1 else 0 end) as StartFlag
from rate r;

相关条件中的or用于处理定义组的间隔在间隔的开始日期重叠的情况。

然后,您可以对此标志执行累计求和并按该总和进行汇总:

with r as (
      select r.*,
             (case when not exists (select 1
                                    from rates r2
                                    where (r2.from < r.from and r2.to >= r.to) or
                                          (r2.from = r.from and r2.id < r.id)
                                   )
                   then 1 else 0 end) as StartFlag
      from rate r
     )
select min(from), max(to)
from (select r.*,
             sum(r.StartFlag) over (order by r.from) as grp
      from r
     ) r
group by grp;

答案 1 :(得分:0)

CREATE TABLE prices( id INTEGER NOT NULL PRIMARY KEY
        , price MONEY
        , date_from DATE NOT NULL
        , date_upto DATE NOT NULL
        );

    -- some data (upper limit is EXCLUSIVE)
INSERT INTO prices(id, price, date_from, date_upto) VALUES
 ( 1,   75.00, '2015-04-12', '2016-04-16' )    
,( 2,   100.00, '2016-04-17', '2016-04-19' )
,( 3,   50.00, '2016-04-19', '2016-05-01' )
,( 4,   50.00, '2016-05-01', '2016-05-22' )
        ;

-- SELECT * FROM prices;

    -- Recursive query to "connect the dots"
WITH RECURSIVE rrr AS (
        SELECT date_from, date_upto
        , 1 AS nperiod
        FROM prices p0
        WHERE NOT EXISTS (SELECT * FROM prices nx WHERE nx.date_upto = p0.date_from)    -- no preceding segment
        UNION ALL
        SELECT r.date_from, p1.date_upto
                , 1+r.nperiod AS nperiod
        FROM prices p1
        JOIN rrr r ON p1.date_from = r.date_upto
        )
SELECT * FROM rrr r
WHERE NOT EXISTS (SELECT * FROM prices nx WHERE nx.date_from = r.date_upto)     -- no following segment
        ;

结果:


 date_from  | date_upto  | nperiod 
------------+------------+---------
 2015-04-12 | 2016-04-16 |       1
 2016-04-17 | 2016-05-22 |       3
(2 rows)