【问题标题】:Selecting an appropriate period or max/minimum period when outside of the period set在设置的时间段之外选择适当的时间段或最大/最小时间段
【发布时间】:2014-01-15 00:07:52
【问题描述】:

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

假设我有一张桌子

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-01-14

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

【问题讨论】:

  • 您能否发布一些示例数据和预期结果?您还使用什么 RDBMS(SQL 风格)?
  • 添加了一个小例子。
  • 请根据您的样本数据发布期望的结果。 2010-01-142014-01-14 的输出究竟应该是什么?
  • 我认为你应该描述你试图解决的整体问题。你到底想做什么?您的查询不适用于 2014 年,因为该记录不存在。如果你插入它,问题就结束了。
  • 我脑子里一直在想的事情是在数据摄取时处理这个问题,并包含一个从 mindate->first record date 和 last record date->maxdate 开始的虚假记录。那么问题是,如何使其与实时更新保持同步并确保其准确。

标签: sql ingres


【解决方案1】:

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

所以:

  • 对于 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)
     )

【讨论】:

  • 对不起,不是我想要的,我展示的表格不是输出,而是源数据。即我正在搜索的内容。 period_start/end 列不必是 beg/end of year,但可以是任何随机数据(只是为了定义相邻的时期。
  • @Spotter 更新为产生最小值或最大值。
  • 是的,这是我一直在考虑的事情,工会。不太确定它在我们现有的环境(相当大的表,数百万个条目)的上下文中的性能。
【解决方案2】:

注意:我错过了您使用的数据库引擎,所以我从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。我向您保证,您后悔使用包容性结束日期的机会比使用独占性结束日期要大得多。

【讨论】:

    【解决方案3】:

    为什么不创建“边界周期”? 选择任意的开始时间和结束时间日期,例如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            |
    +--+-------------+
    

    【讨论】:

    • 这实际上是我最终做的事情(嗯,非常相似)。
    • @spotter 这是一种经典的方法。唯一需要注意的是正确管理周期,例如修改一个周期可能会触发其他更改以保持“链”的完整性
    【解决方案4】:

    我将假设您也在使用 SQL Server 来编写下面的脚本,但 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
          )
        )
    

    【讨论】:

    • 他使用的是 Ingres 而不是 SQL Server
    • 当时没有指定。正如我提到的 Select 语句,使用适当的表名将起作用
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-05-08
    • 1970-01-01
    • 1970-01-01
    • 2018-07-29
    • 1970-01-01
    • 2021-08-08
    • 1970-01-01
    相关资源
    最近更新 更多