最近六个“完整”月份的SQL

时间:2013-10-10 13:18:12

标签: sql sql-server

我的表包含一个日期时间列。我只需要过去6个月才能返回行。这可以通过

来完成
WHERE CloseTime >= DATEADD(Month, DATEDIFF(Month, 0, DATEADD(m, - 6, CURRENT_TIMESTAMP)), 0)

这为我提供了本月开始使用此脚本+ 6个月的数据。所以例如如果我今天运行这个脚本,我会得到本月+所有前几个月的数据,直到4月(04)。

现在我需要修改条件,所以如果我今天运行脚本,数据将只在03-09个月获得,不包括本月的日期(10)。

请问任何建议?

4 个答案:

答案 0 :(得分:5)

如果你想拥有前6个月,无论今天是第1,第3,第9,第29,无论如何,那么只需减去7个月。这是一种方法:将月中的第一个变为变量,然后在查询中使用开放式范围。

DECLARE @ThisMonth DATETIME;
SET @ThisMonth = DATEADD(MONTH, DATEDIFF(MONTH, '19000101', GETDATE()), '19000101');

SELECT...
WHERE CloseTime >= DATEADD(MONTH, -7, @ThisMonth)
  AND CloseTime <  @ThisMonth;

您也可以使用0代替'19000101',但我更喜欢明确的约会而不是隐含的速记(这是一个非常难以打破的习惯)。

如果你真的不喜欢变量,那么你可以通过重复表达来计算本月的第一个(并在范围的开头,从月数中减去7)来使查询复杂得多。 :

SELECT...
WHERE CloseTime >= DATEADD(MONTH, DATEDIFF(MONTH, '19000101', GETDATE())-7, '19000101')
  AND CloseTime <  DATEADD(MONTH, DATEDIFF(MONTH, '19000101', GETDATE()), '19000101');

呸。变量使这更加整洁。

答案 1 :(得分:1)

创建查询时,您不希望在搜索列上使用某个函数,因为它会导致全表扫描。

解决方案有效,应该在CloseTime上获取任何索引。

-- Get me data in months 3 (mar) to 9 (sep) of this year
select 
    *
from 
    my_table 
where 
    CloseTime between 
    DATEADD(d, -1, '03-01-2013') and DATEADD(d, +1, '09-20-2013')

如果表很小并且全表扫描不是问题,一个简单的解决方案是使用MONTH功能。

-- Get me data in months 3 (mar) to 9 (sep) of this year
select 
    *
from 
    my_table 
where 
    MONTH(CloseTime) IN (3,4,5,6,7,8,9) and YEAR(CloseTime) = 2013

答案 2 :(得分:0)

我看了亚伦的文章。这是一个非常好的阅读。

我想知道自那篇文章以来是否有一些未经过测试的新功能。

如果您使用2012,为什么不使用格式功能?从逻辑上讲,您希望日期变量为01天。查询计划仍然会进行聚簇索引扫描。

让我们看看这个解决方案如何使用Aaron的测试数据库进行叠加。

-- Use the sample
use [DateTesting]
go

-- Johns - log by 6 months
CREATE PROCEDURE dbo.Johns_LogBy6Months
   @date SMALLDATETIME
AS
BEGIN
   SET NOCOUNT ON;

   DECLARE @cmp_date SMALLDATETIME = format(@date, 'yyyyMM01');
   DECLARE @c INT;

   SELECT @c = COUNT(*)
        FROM dbo.SomeLogTable
        WHERE DateColumn >= dateadd(m, -7, @cmp_date) 
        AND DateColumn < @cmp_date
END
GO

-- Aarons - log by 6 months
CREATE PROCEDURE dbo.Aarons_LogBy6Months
   @date SMALLDATETIME
AS
BEGIN
   SET NOCOUNT ON;

   DECLARE @c INT;
   DECLARE @cmp_date SMALLDATETIME = DATEADD(MONTH, DATEDIFF(MONTH, '19000101', @date), '19000101');

   SELECT @c = COUNT(*)
        FROM dbo.SomeLogTable
        WHERE 
        DateColumn >=  dateadd(m, -7, @cmp_date) 
        AND DateColumn <  @cmp_date
END
GO

让我们对函数进行1000次调用。

-- Sample calls x 1000  
PRINT CONVERT(char(23), GETDATE(), 121);
GO

EXEC dbo.Johns_LogCountByDay @date = '20091005';
GO 1000

PRINT CONVERT(char(23), GETDATE(), 121);
GO

EXEC dbo.Aarons_LogBy6Months @date = '20091005';
GO 1000

PRINT CONVERT(char(23), GETDATE(), 121);
GO

这是执行时间。

2013-10-10 11:58:49.547

Beginning execution loop
Batch execution completed 1000 times.

2013-10-10 11:58:52.837

Beginning execution loop
Batch execution completed 1000 times.

2013-10-10 11:58:55.883

总之,对new format()函数的调用和对较小日期时间的隐式强制转换比使用两个字符串(日期)文字的dateadd()和datediff()花费更多的时间。

format()解决方案对我来说似乎更直观或自我记录。时差为3.3对3.0秒。

我必须为Aaron的解决方案提供速度测试。坚持日期变量的不等式比较。它们更快。

简而言之,我必须解决我的坏习惯。

答案 3 :(得分:0)

我有类似的要求。我需要过去六个月和接下来六个月的约会。但是就像你一样,我需要整整几个月的时间。因此,简单的getdate +-180不会。

我采用了更简单的方法。我得到年和月,然后将其转换为像201912这样的数字。然后执行一个between子句。它充满活力,我花了整整几个月的时间。

我敢肯定还有更复杂的方法,但这就是我想出的。

WHERE Year([ApptDate])*100 + Month([ApptDate]) between Year(getdate()-180)*100 + Month(getdate()-180) and Year(getdate()+180)*100 + Month(getdate()+180)