我正在试图查看是否可以有效地选择给定日期所属的期间。
假设我有一张桌子
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分别定义为表的最大值/最小值。
答案 0 :(得分:0)
您似乎想要打印指定日期年份的第一天和最后一天 。
所以:
创建一个插入脚本,插入截至2100年的所有行(您可以轻松添加更多行)。
不使用表格,只需使用日期函数(或任何其他编程语言)。
获取给定的日期,并使用日期函数打印出所有需要的四列。
示例(MySQL) - 应该使用INGRES进行一些修改
注意:当我检查函数时,它应该在INGRES中工作,它们是相同的:
http://community.actian.com/wiki/Ingres_DBMS_function_list
SELECT 1 AS id,
CONCAT(DATE_FORMAT('2014-01-14', '%Y-'), '01-01') AS period_start,
CONCAT(DATE_FORMAT('2014-01-14', '%Y-'), '12-31') AS period_end,
DATE_FORMAT('2014-01-14', '%Y')-2010 AS period_number ;
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)
)
答案 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
;
如果您要发布有关表结构和索引的更多信息,我可以更好地为您提供建议。
我还想建议,如果可能的话,您停止使用包含结束日期,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 |
+--+-------------+