SQL日期范围查询 - 表比较

时间:2014-09-04 09:02:49

标签: sql sql-server tsql date gaps-and-islands

我有两个包含以下信息的SQL Server表:

t_venues

venue_id是唯一的

venue_id  |  start_date  |  end_date
       1  |  01/01/2014  |  02/01/2014
       2  |  05/01/2014  |  05/01/2014
       3  |  09/01/2014  |  15/01/2014
       4  |  20/01/2014  |  30/01/2014

t_venueuser

venue_id不是唯一的

venue_id  |  start_date  |  end_date
       1  |  02/01/2014  |  02/01/2014
       2  |  05/01/2014  |  05/01/2014
       3  |  09/01/2014  |  10/01/2014
       4  |  23/01/2014  |  25/01/2014

从这两个表格中,我需要找到每个范围都没有选择的日期,因此输出结果如下:

venue_id  |  start_date  |  end_date
       1  |  01/01/2014  |  01/01/2014
       3  |  11/01/2014  |  15/01/2014
       4  |  20/01/2014  |  22/01/2014
       4  |  26/01/2014  |  30/01/2014

我可以比较这两个表,并使用'从t_venues获取日期范围以显示在我的查询中,除了'但是我无法通过查询来生成未选择的日期。任何帮助将不胜感激。

2 个答案:

答案 0 :(得分:3)

日历表!

日历表的另一个完美候选人。如果您无法搜索here's one I made earlier

设置数据

DECLARE @t_venues table (
   venue_id   int
 , start_date date
 , end_date   date
);

INSERT INTO @t_venues (venue_id, start_date, end_date)
  VALUES (1, '2014-01-01', '2014-01-02')
       , (2, '2014-01-05', '2014-01-05')
       , (3, '2014-01-09', '2014-01-15')
       , (4, '2014-01-20', '2014-01-30')
;

DECLARE @t_venueuser table (
   venue_id   int
 , start_date date
 , end_date   date
);

INSERT INTO @t_venueuser (venue_id, start_date, end_date)
  VALUES (1, '2014-01-02', '2014-01-02')
       , (2, '2014-01-05', '2014-01-05')
       , (3, '2014-01-09', '2014-01-10')
       , (4, '2014-01-23', '2014-01-25')
;

查询

SELECT t_venues.venue_id
     , calendar.the_date
     , CASE WHEN t_venueuser.venue_id IS NULL THEN 1 ELSE 0 END As is_available
FROM   dbo.calendar /* see: http://gvee.co.uk/files/sql/dbo.numbers%20&%20dbo.calendar.sql for an example */
 INNER
  JOIN @t_venues As t_venues
    ON t_venues.start_date <= calendar.the_date
   AND t_venues.end_date   >= calendar.the_date
 LEFT
  JOIN @t_venueuser As t_venueuser
    ON t_venueuser.venue_id = t_venues.venue_id
   AND t_venueuser.start_date <= calendar.the_date
   AND t_venueuser.end_date   >= calendar.the_date
ORDER
    BY t_venues.venue_id
     , calendar.the_date
;

结果

venue_id    the_date                is_available
----------- ----------------------- ------------
1           2014-01-01 00:00:00.000 1
1           2014-01-02 00:00:00.000 0
2           2014-01-05 00:00:00.000 0
3           2014-01-09 00:00:00.000 0
3           2014-01-10 00:00:00.000 0
3           2014-01-11 00:00:00.000 1
3           2014-01-12 00:00:00.000 1
3           2014-01-13 00:00:00.000 1
3           2014-01-14 00:00:00.000 1
3           2014-01-15 00:00:00.000 1
4           2014-01-20 00:00:00.000 1
4           2014-01-21 00:00:00.000 1
4           2014-01-22 00:00:00.000 1
4           2014-01-23 00:00:00.000 0
4           2014-01-24 00:00:00.000 0
4           2014-01-25 00:00:00.000 0
4           2014-01-26 00:00:00.000 1
4           2014-01-27 00:00:00.000 1
4           2014-01-28 00:00:00.000 1
4           2014-01-29 00:00:00.000 1
4           2014-01-30 00:00:00.000 1

(21 row(s) affected)

解释

我们的日历表包含每个日期的条目。

我们加入我们的t_venues(如果您有选择,请丢失t_前缀!),我们会在start_dateend_date之间每天返回。此加入的venue_id=4示例输出:

venue_id    the_date
----------- -----------------------
4           2014-01-20 00:00:00.000
4           2014-01-21 00:00:00.000
4           2014-01-22 00:00:00.000
4           2014-01-23 00:00:00.000
4           2014-01-24 00:00:00.000
4           2014-01-25 00:00:00.000
4           2014-01-26 00:00:00.000
4           2014-01-27 00:00:00.000
4           2014-01-28 00:00:00.000
4           2014-01-29 00:00:00.000
4           2014-01-30 00:00:00.000

(11 row(s) affected)

现在我们每天有一行,我们[外]加入我们的t_venueuser表。我们以与以前大致相同的方式加入此项,但又增加了一点:我们也需要基于venue_id加入!

venue_id=4运行此结果会产生以下结果:

venue_id    the_date                t_venueuser_venue_id
----------- ----------------------- --------------------
4           2014-01-20 00:00:00.000 NULL
4           2014-01-21 00:00:00.000 NULL
4           2014-01-22 00:00:00.000 NULL
4           2014-01-23 00:00:00.000 4
4           2014-01-24 00:00:00.000 4
4           2014-01-25 00:00:00.000 4
4           2014-01-26 00:00:00.000 NULL
4           2014-01-27 00:00:00.000 NULL
4           2014-01-28 00:00:00.000 NULL
4           2014-01-29 00:00:00.000 NULL
4           2014-01-30 00:00:00.000 NULL

(11 row(s) affected)

了解我们如何为没有NULL记录的行设置t_venueuser值。天才,不是吗? ;-)

所以在我的第一个查询中,我给了你一个显示可用性的快速CASE语句(1 =可用,0 =不可用)。这只是为了说明,但可能对您有用。

然后,您可以将查询打包,然后对此计算列应用额外的过滤器,或者只需在WHERE t_venueuser.venue_id IS NULL中添加where子句,这将执行相同的操作。

答案 1 :(得分:1)

这是一个完整的黑客攻击,但它会提供您需要的结果,我只是根据您提供的数据对其进行测试,因此很可能会遇到更大的设置。

一般来说,你在这里要解决的是间隙和岛屿问题的变化,这是(简要地)一些缺少某些项目的序列。缺失的项目称为差距,现有项目称为岛屿。如果您想了解这个问题,请查看以下几篇文章:

<强>代码:

;with dates as
(
    SELECT  vdates.venue_id,    
            vdates.vdate
    FROM  ( SELECT DATEADD(d,sv.number,v.start_date) vdate
                 , v.venue_id
            FROM t_venues v
            INNER JOIN master..spt_values sv 
                ON sv.type='P'
               AND sv.number BETWEEN 0 AND datediff(d, v.start_date, v.end_date)) vdates
    LEFT JOIN t_venueuser vu
        ON vdates.vdate >= vu.start_date
       AND vdates.vdate <= vu.end_date
       AND vdates.venue_id = vu.venue_id
    WHERE ISNULL(vu.venue_id,-1) = -1
)
SELECT venue_id, ISNULL([1],[2]) StartDate, [2] EndDate
FROM   (SELECT venue_id, rDate, ROW_NUMBER() OVER (PARTITION BY venue_id, DateType ORDER BY rDate) AS rType, DateType as dType
        FROM(   SELECT d1.venue_id
                      ,d1.vdate AS rDate
                      ,'1' AS DateType
                FROM dates AS d1    
                LEFT JOIN dates AS d0
                    ON DATEADD(d,-1,d1.vdate) = d0.vdate
                LEFT JOIN dates AS d2       
                    ON DATEADD(d,1,d1.vdate) = d2.vdate
                WHERE CASE ISNULL(d2.vdate, '01 Jan 1753') WHEN '01 Jan 1753' THEN '2' ELSE '1' END = 1
                AND ISNULL(d0.vdate, '01 Jan 1753') = '01 Jan 1753'
                UNION 
                SELECT d1.venue_id
                      ,ISNULL(d2.vdate,d1.vdate)
                      ,'2'
                FROM dates AS d1    
                LEFT JOIN dates AS d2       
                    ON DATEADD(d,1,d1.vdate) = d2.vdate
                WHERE CASE ISNULL(d2.vdate, '01 Jan 1753') WHEN '01 Jan 1753' THEN '2' ELSE '1' END = 2
            ) res
        ) src
PIVOT   (MIN (rDate)
        FOR dType IN
        ( [1], [2] )
        ) AS pvt

<强>结果:

venue_id    StartDate   EndDate
1           2014-01-01  2014-01-01
3           2014-01-11  2014-01-15
4           2014-01-20  2014-01-22
4           2014-01-26  2014-01-30