在设定的期间之外选择适当的期间或最大/最小期间

时间:2014-01-15 00:07:53

标签: sql ingres

我正在试图查看是否可以有效地选择给定日期所属的期间。

假设我有一张桌子

id<long>|period_start<date>|period_end<date>|period_number<int>

并且假设我想要“2013-11-20”属于的每个id。

即。天真地

 select id, period_number 
 from period_table 
 where '2013-11-20' >= period_start and '2013-11-20' < period_end

但是,如果我的日期超出任何period_end或在任何period_start之前,它将找不到此ID。在这些情况下,我想要最小值(如果在第一个period_start之前)或最大值(如果在最后一个period_end之后)。

有任何想法,如果这可以有效地完成?我显然可以做多个查询(例如,如上所述选择表格,然后再做一个查询来计算最小和最大周期)。

所以例如

+--+------------+----------+-------------+
|id|period_start|period_end|period_number|
+--+------------+----------+-------------+
|1 |2011-01-01  |2011-12-31|1            |
|1 |2012-01-01  |2012-12-31|2            |
|1 |2013-01-01  |2013-12-31|3            |
+--+------------+----------+-------------+

如果我想要2012-05-03所属的时期,我的天真sql工作并返回句号#2(1 | 2作为行,id,period_number)。但是,如果我想要什么时期2014-01-14(或2010-01-14)它不能放置它,因为它在表外。

因此,“2014-01-14”是> 2013-12-31,如果我选择2010-01-14,我希望它返回“1 | 3”行,我希望它返回1 | 1,如2010-01-14&lt; 2011-01-01

关键在于,我们有一个索引表,可以跟踪不同类型的期间以及它们相对于许多不同事物的相对价值(想想季度,半年,年),它们都不符合正常年份。有时我们想说我们想要相对于日期Y的周期X(某个整数)。如果我们可以在表格中放置Y并找出Y的period_number,我们可以轻松地进行数学计算以确定要添加/减去的内容达到那个价值。如果Y超出表的边界,我们将Y分别定义为表的最大值/最小值。

4 个答案:

答案 0 :(得分:0)

您似乎想要打印指定日期年份的第一天最后一天

所以:

  • 2012-05-03,打印 1 | 2012-01-01 | 2012-12-31 | 2
  • 2014-01-14,打印 1 | 2014-01-01 | 2014-12-31 | 4

解决方案:

  1. 创建一个插入脚本,插入截至2100年的所有行(您可以轻松添加更多行)。

  2. 不使用表格,只需使用日期函数(或任何其他编程语言)。

  3. 使用SQL日期函数

    获取给定的日期,并使用日期函数打印出所有需要的四列。

    示例(MySQL) - 应该使用INGRES进行一些修改

    注意:当我检查函数时,它应该在INGRES中工作,它们是相同的:

    SQLFiddle:http://sqlfiddle.com/#!2/d41d8/29204

    更新

    UNION应该可以为你工作。根据INGRES的需要进行以下修改。

    SET @input =  '2015-01-14';
    
    (SELECT id,
              period_number
       FROM period_table
       WHERE @input >= period_start
         AND @input < period_end)
    UNION
      (SELECT id,
              period_number
       FROM period_table
       WHERE @input > (select MAX(period_end) FROM period_table) 
       order by period_end desc limit 1
      )
    UNION
      (SELECT id,
              period_number
       FROM period_table
       WHERE @input < (select MIN(period_start) FROM period_table) 
       order by period_start asc limit 1
      )
    

    SQLFiddle:http://sqlfiddle.com/#!2/643da/3

    ALSO:

    SET @input =  '2014-01-14';
    
    SELECT id,
              period_number
    
       FROM period_table
       WHERE 
         (@input >= period_start
         AND @input < period_end)
         OR 
         (
           @input > (select MAX(period_end) FROM period_table) AND
           period_number= (select MAX(period_number) FROM period_table)
         )
          OR 
         (
           @input < (select MIN(period_start) FROM period_table) AND
           period_number= (select MIN(period_number) FROM period_table)
         )
    

答案 1 :(得分:0)

注意:我错过了您使用的数据库引擎,所以我从SQL Server的角度回答。但是,查询非常简单,您应该能够根据自己的需要进行调整。

我能想到的最好的办法是,如果你的表在FromDate上聚集(或至少编入索引),则查询工作在2中:

DECLARE @SearchDate datetime = '4062-05-04';

SELECT TOP 1 *
FROM
   (
      SELECT TOP 2
         Priority = 0,
         *
      FROM dbo.Period
      WHERE @SearchDate >= FromDate
      ORDER BY FromDate DESC
      UNION ALL
      SELECT TOP 1
         2,
         *
      FROM dbo.Period
      WHERE @SearchDate < FromDate
      ORDER BY FromDate
   ) X
ORDER BY Priority, FromDate DESC
;

See a Live Demo at SQL Fiddle

如果您要发布有关表结构和索引的更多信息,我可以更好地为您提供建议。

我还想建议,如果可能的话,您停止使用包含结束日期,ToDate列包含句号的最后一天,例如'2013-12-31',并开始使用独占结束日期,ToDate列具有下一期间的开头。这种情况通常只有在长时间的数据库经验之后才会显现,但想象一下,如果你突然不得不添加短于1天的时段(例如班次甚至小时)会发生什么 - 一切都会破裂!但是如果你一直使用独家结束日期,那么一切都会按照的原样运行。此外,必须将句点合并在一起的查询变得更加复杂,因为您在整个地方添加1而不是执行简单的等值连接,例如WHERE P1.ToDate = P2.FromDate。我向你们保证,你们将使用包容性的结束日期后悔的机会远远超过独家结束日期。

答案 2 :(得分:-1)

我将假设您正在使用SQL服务器以用于下面的脚本,但SQL是通用的,只要列名称分隔符正确,它就可以与任何(?)数据库一起使用

declare @Period_Table table 
(
    [id] int not null,
    period_start datetime not null,
    period_end datetime not null,
    period_number int not null,
    primary key ([id], period_number)
)

insert into @Period_Table values (1, '2011-01-01', '2011-12-31', 1)
insert into @Period_Table values (1, '2012-01-01', '2012-12-31', 2)
insert into @Period_Table values (1, '2013-01-01', '2013-12-31', 3)

declare @TestDate datetime
declare @TestId int 
set @TestId = 1 -- yearly
set @TestDate = '2012-05-03'
set @TestDate = '2014-01-14'
set @TestDate = '2010-01-14'

select *
  from @Period_Table pt
  where pt.[id] = @TestId and
    (
      pt.period_start <= @TestDate or 
      @TestDate < pt.period_start and
      not exists
      (
        select 1
          from @Period_Table pt2
          where pt.[id] = pt2.[id] and pt2.period_start < pt.period_start
      )
    ) and
    (
      pt.period_end >= @TestDate or 
      @TestDate > pt.period_end and
      not exists
      (
        select 1
          from @Period_Table pt2
          where pt.[id] = pt2.[id] and pt2.period_end > pt.period_end
      )
    )

答案 3 :(得分:-1)

为什么你没有创造&#34;边界期&#34;? 选择任意的begin_of_time和end_of_time日期,例如01/01/0001和31/12/9999并插入一个假期。您的示例period_table将变为:

+--+------------+----------+-------------+
|id|period_start|period_end|period_number|
+--+------------+----------+-------------+
|1 |0001-01-01  |2010-12-31|1            |
|1 |2011-01-01  |2011-12-31|1            |
|1 |2012-01-01  |2012-12-31|2            |
|1 |2013-01-01  |2013-12-31|3            |
|1 |2014-01-01  |9999-12-31|3            |
+--+------------+----------+-------------+

在这种情况下,任何查询都将检索一行且只检索一行,例如:

select id, period_number from period_table 
where '2013-11-20' between period_start and period_end

+--+-------------+
|id|period_number|
+--+-------------+
|1 |2            |
+--+-------------+

select id, period_number from period_table 
where '2010-11-20' between period_start and period_end

+--+-------------+
|id|period_number|
+--+-------------+
|1 |1            |
+--+-------------+

select id, period_number from period_table 
where '2014-11-20' between period_start and period_end

+--+-------------+
|id|period_number|
+--+-------------+
|1 |3            |
+--+-------------+