0

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

假设我有一张桌子

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

假设我希望每个 id 都有“2013-11-20”所属的时期。

即天真

 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 年 1 月 14 日 < 2011 年 1 月 1 日。

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

4

4 回答 4

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. 无需使用表格,只需使用日期函数(或任何其他编程语言)。

使用 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

还:

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)
     )
于 2014-01-15T00:34:01.443 回答
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
;

在 SQL Fiddle 上查看现场演示

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

我还想建议,如果可能的话,您停止使用包含结束日期,您的ToDate列中包含期间的最后一天,例如'2013-12-31',并开始使用排他结束日期,其中ToDate列具有下一个开始时期。造成这种情况的原因通常只有在长时间的数据库经验之后才会显现出来,但想象一下,如果您突然不得不添加少于 1 天的时间段(例如轮班甚至几小时)会发生什么——一切都会中断!但是,如果您一直使用排他的结束日期,那么一切都会照常进行。此外,必须将句点合并在一起的查询变得更加复杂,因为您在整个地方添加 1 而不是进行简单的等值连接,例如WHERE P1.ToDate = P2.FromDate. 我向你保证,与使用独占结束日期相比,你后悔使用包容性结束日期的机会要大得多。

于 2014-01-15T03:10:47.820 回答
-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
      )
    )
于 2014-01-15T05:35:12.047 回答
-1

你为什么不创建“边界期”?选择任意的开始时间和结束时间日期,例如 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            |
+--+-------------+
于 2014-08-13T16:00:58.457 回答