4

我有一个表,其中包含年、月和几个数字列

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

现在,我想SELECT在 2011 年 11 月到 2012 年 2 月之间行。请注意,我希望查询使用范围。就好像我在表中有一个日期列..

4

3 回答 3

7

想出一种将 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)
      )
   );

但是,您不想使用这样的结构是可以理解的,因为它非常笨拙。所以这是一个折衷查询,它至少使用数字计算,并且将使用比日期到字符串转换计算更少的 CPU(尽管不足以弥补强制扫描,这是真正的性能问题)。

SELECT *
FROM dbo.YourTable T
WHERE
   T.[Year] * 100 + T.[Month] BETWEEN 201111 AND 201202;

如果你有一个索引Year,你可以通过提交如下查询得到一个很大的提升,有机会去寻找:

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]

虽然这打破了您对使用单个BETWEEN表达式的要求,但它并不会带来太多痛苦,并且在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关键字意味着虽然您仍然会进行扫描,但它不必对每一行进行任何计算,因为表达式是在每个 INSERT 或 UPDATE 上计算并存储在该行中的。但是如果你在这个列上添加一个索引,你可以得到一个搜索,这将使它表现得非常好(尽管总而言之,这仍然不如更改为使用实际的日期列理想,因为它会占用更多的空间和将影响插入和更新):

CREATE NONCLUSTERED INDEX IX_YourTable_ActualDate ON dbo.YourTable (ActualDate);

总结:如果你真的不能以任何方式改变表,那么你将不得不以某种方式做出妥协。当您的日期被拆分为单独的列存储时,将无法获得您想要的也能很好地执行的简单语法。

于 2012-12-20T01:56:27.127 回答
2
(Year > @FromYear OR Year = @FromYear AND Month >= @FromMonth)
AND (Year < @ToYear OR Year = @ToYear AND Month <= @ToMonth)
于 2012-12-20T02:24:31.370 回答
1

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

SELECT * FROM Table WHERE ((Year * 100) + Month) BETWEEN 201111 AND 201202

如果不是这种情况,并且表中确实有大量记录(超过几千条记录),那么您有两种选择:

  1. 将您的表更改为以 YYYYMM 格式(整数值或文本)存储年份和月份。此列可以替换您当前的年份和索引列,也可以添加到它们中(尽管这违反了正常形式)。索引此列并对其进行查询。

  2. 创建一个单独的表,其中包含每年和每月一条记录以及如上所述的可索引列。在您的查询中,将此表连接回源表并针对较小表中的索引列执行查询。

于 2012-12-20T02:33:58.363 回答