之间的SQL Server

之间的SQL Server,sql,sql-server,tsql,sql-server-2005,Sql,Sql Server,Tsql,Sql Server 2005,我有一个表,有年、月和几个数字列 Year Month Total 2011 10 100 2011 11 150 2011 12 100 2012 01 50 2012 02 200 现在,我想选择2011年11月到2012年2月之间的行。请注意,我想查询使用范围。就像我在表中有一个日期列一样 想出一种方法,在表的现有状态下使用BETWEEN将起作用,但在任何情况下性能都会更差: 它最多会消耗更多的CPU来对行

我有一个表,有年、月和几个数字列

Year   Month  Total
2011     10    100
2011     11    150
2011     12    100  
2012     01    50
2012     02    200

现在,我想
选择2011年11月到2012年2月之间的
行。请注意,我想查询使用范围。就像我在表中有一个日期列一样

想出一种方法,在表的现有状态下使用BETWEEN将起作用,但在任何情况下性能都会更差:

  • 它最多会消耗更多的CPU来对行进行某种计算,而不是将它们作为日期处理
  • 在最坏的情况下,它会强制对表中的每一行进行表扫描,但是如果列具有索引,那么使用正确的查询就可以进行查找。这可能是一个巨大的性能差异,因为将约束强制到BETWEEN子句中将禁用使用索引
如果您在日期列上有一个索引,并且非常关心性能,我建议您使用以下方法:

DECLARE
   @FromDate date = '20111101',
   @ToDate date = '20120201';

SELECT *
FROM dbo.YourTable T
WHERE
   (
      T.[Year] > Year(@FromDate)
      OR (    
         T.[Year] = Year(@FromDate)
         AND T.[Month] >= Month(@FromDate)
      )
   ) AND (
      T.[Year] < Year(@ToDate)
      OR (
         T.[Year] = Year(@ToDate)
         AND T.[Month] <= Month(@ToDate)
      )
   );
如果您有一个
年的索引
,您可以通过提交以下查询获得一个巨大的提升,该查询有机会查找:

SELECT *
FROM dbo.YourTable T
WHERE
   T.[Year] * 100 + T.[Month] BETWEEN 201111 AND 201202
   AND T.[Year] BETWEEN 2011 AND 2012; -- allows use of an index on [Year]
虽然这打破了在
表达式之间使用单个
表达式的要求,但它不会太痛苦,并且在年份索引中表现非常好

你也可以换桌子。坦率地说,为日期部分使用单独的数字,而不是使用日期数据类型的单个列是不好的。它不好的原因是因为你现在正面临着一个确切的问题——很难质疑

在一些保存字节非常重要的数据仓库场景中,我可以设想将日期存储为数字的情况(例如
201111
),但不建议这样做。最好的解决方案是将表更改为使用日期,而不是拆分月份和年份的数值。只需存储一个月的第一天,认识到它代表整个月

如果无法更改这些列的使用方式,但仍可以更改表,则可以添加持久化计算列:

ALTER Table dbo.YourTable
   ADD ActualDate AS (DateAdd(year, [Year] - 1900, DateAdd(month, [Month], '18991201')))
   PERSISTED;
有了它,您可以:

SELECT *
FROM dbo.YourTable
WHERE
   ActualDate BETWEEN '20111101' AND '20120201';
PERSISTED
关键字意味着,尽管您仍将获得扫描,但它不必对每一行进行任何计算,因为表达式是在每次插入或更新时计算的,并存储在该行中。但是,如果在此列上添加索引,则可以进行搜索,这将使其性能非常好(尽管总的来说,这仍然不如更改为使用实际日期列那么理想,因为这将占用更多空间,并会影响插入和更新):

小结:如果你真的不能以任何方式改变表格,那么你必须以某种方式做出妥协。如果将日期拆分为单独的列存储,则无法获得所需的简单语法,而该语法也会运行良好。

(Year>@FromYear或Year=@FromYear和Month>=@FromMonth)
(Year > @FromYear OR Year = @FromYear AND Month >= @FromMonth)
AND (Year < @ToYear OR Year = @ToYear AND Month <= @ToMonth)

并且(Year<@ToYear或Year=@ToYear AND Month您的示例表似乎表明每年和每月只有一条记录(如果它真的是按月汇总表的话)。如果是这样的话,即使在几十年的活动中,表中也可能积累很少的数据。串联表达式解决方案将起作用,并且性能(在本例中)不会成为问题:

SELECT * FROM Table WHERE ((Year * 100) + Month) BETWEEN 201111 AND 201202
如果情况并非如此,并且表中确实有大量记录(超过几千条记录),那么您有两种选择:

  • 将表更改为以YYYYMM格式(整数值或文本)存储年和月。此列可以替换当前年和索引列,也可以添加到当前年和索引列中(尽管这打破了正常形式)。为此列编制索引并对其进行查询

  • 创建一个单独的表,其中每年和每月有一条记录,还包括上述可索引列。在查询中,将此表连接回源表,并对较小表中的索引列执行查询


  • 如果要使用范围,则必须选择一个计算列(如concat(year,month)),这将对性能造成不利影响分别对两列进行查询,因为这样它可以使用索引。我想指出,这在功能上与我回答中的第一个查询相同。这里有6个条件和5个连词。我给出的第一个查询有完全相同的6个条件和5个连词,只是重新排列了一点。ErikE的版本在我看来更清晰特别是,你不必知道是否和或绑定更强。“我道歉,我应该更小心地发布我的答案。我发现你的答案通常使用相同的方法,除了有一些其他好的建议。请考虑修改你的查询,因为它在某些情况下不适用于<代码> @ FR。omDate
    @ToDate
    是同一年。如果您解决了这个问题,那么我将删除我的答案,因为它没有提供太多信息。这种表格设置(年和月作为单独的列)过去不止一次绊倒了我。很好的回答!你对我的情况绝对正确。我很快就会解决它。我认为#2没有意义——为什么要创建一个单独的表!?!?#1而你提供的查询是其他答案中已经提供的信息的副本。创建一个单独的表允许使用0存储可索引的值ut必须在数据表中的数万行中重复它们(如果存在那么多行)。它显著减少了维护索引值的计算量,规范了索引值与它们映射的年-月对之间的关系,并且可以在没有任何权限的情况下在原始表上实现。但是,它引入了一个额外的联接,这就是我提到不太正常的形式f的原因我明白你的意思了,拉里。现在你强调一对多的关系是有道理的
    (Year > @FromYear OR Year = @FromYear AND Month >= @FromMonth)
    AND (Year < @ToYear OR Year = @ToYear AND Month <= @ToMonth)
    
    SELECT * FROM Table WHERE ((Year * 100) + Month) BETWEEN 201111 AND 201202