2012-07-19 66 views
1

我正在重新访问我为报表编写的一些旧代码,当时我对SQL(MSSQL)还很陌生。它做它应该做的,但它不是最漂亮或最有效的。我怎样才能重写这个select语句使用组而不是使用循环

下面的虚拟代码模仿了我现在所拥有的东西。在这里,我试图计算过去5周内开放的合约数量。对于这个例子,如果合同的开始日期发生在给定周期之前,并且结束日期发生在给定周期间或之后,合同就被认为是开放的。

dbo.GetWeekStart(@date DATETIME,@NumOfWeeks INT,@FirstDayOfWeek CHAR(3))是将返回基于提供一种用于周指定数量的时间每周的第一天的功能。即SELECT * FROM dbo.GetWeekStart('20120719',-2,'MON')将于2012年7月19日之前返回2个星期一。

我该如何简化?我认为有人没有循环就能做到这一点,但我一直无法弄清楚。

DECLARE @RunDate DATETIME, 
    @Index INT, 
    @RowCount INT, 
    @WeekStart DATETIME, 
    @WeekEnd DATETIME 

DECLARE @Weeks TABLE 
(
    WeekNum INT IDENTITY(0,1), 
    WeekStart DATETIME, 
    WeekEnd DATETIME 
) 

DECLARE @Output TABLE 
(
    WeekStart DATETIME, 
    OpenContractCount INT 
) 

SET @RunDate = GETDATE() 

INSERT INTO @Weeks (WeekStart, WeekEnd) 
SELECT WeekStart, 
DATEADD(ss,-1,DATEADD(ww,1,WeekStart)) 
FROM dbo.[GetWeekStart](@RunDate, -5, 'MON') 

SET @RowCount = (SELECT COUNT(*) FROM @Weeks) 
SET @Index = 0 

WHILE @Index < @RowCount 
BEGIN 
    SET @WeekStart = (SELECT WeekStart FROM @Weeks WHERE WeekNum = @Idx) 
    SET @WeekEnd = (SELECT WeekEnd FROM @Weeks WHERE WeekNum = @Idx) 

    INSERT INTO @Output (WeekStart, OpenContractCount) 
    SELECT @WeekStart, 
    COUNT(*) 
    FROM Contracts c 
    WHERE c.StartDate <= @WeekEnd 
    AND ISNULL(c.EndDate, GETDATE()) >= @WeekStart 

    SET @Index = @Index + 1 
END 
SELECT * FROM @Output 

回答

0

我看不出为什么这是行不通的:

DECLARE @RunDate DATETIME = GETDATE() 

SELECT WeekStart, COUNT(*) 
FROM Contracts c 
     INNER JOIN dbo.[GetWeekStart](@RunDate, -5, 'MON') 
      ON c.StartDate < DATEADD(WEEK, 1, WeekStart) 
      AND (c.EndDate IS NULL OR c.EndDate >= @WeekStart) 
GROUP BY WeekStart 

我不知道你是如何中产生的日期你的函数,以防万一你正在使用循环/递归CTE,我将包含一个不使用循环/游标等的查询。

DECLARE @RunDate DATETIME = GETDATE() 

-- SET DATEFIRST AS 1 TO ENSURE MONDAY IS THE FIRST DAY OF THE WEEK 
-- CHANGE THIS TO SIMULATE CHANGING YOUR WEEKDAY INPUT TO db 
SET DATEFIRST 1 

-- SET RUN DATE TO BE THE START OF THE WEEK 
SET @RunDate = CAST(DATEADD(DAY, 1 - DATEPART(WEEKDAY, @RunDate), @RunDate) AS DATE) 

;WITH Weeks AS 
( SELECT TOP 5 -- CHANGE THIS TO CHANGE THE WEEKS TO RUN 
      DATEADD(WEEK, 1 - ROW_NUMBER() OVER(ORDER BY Object_ID), @RunDate) [WeekStart] 
    FROM sys.All_Objects 
) 
SELECT WeekStart, COUNT(*) 
FROM Contracts c 
     INNER JOIN Weeks 
      ON c.StartDate < DATEADD(WEEK, 1, WeekStart) 
      AND (c.EndDate IS NULL OR c.EndDate >= @WeekStart) 
GROUP BY WeekStart 
+0

我花了一点时间回到这里,但是这个解决方案对我来说效果很好,并且提供了比我原来的解决方案更高的性能。 – Curtis 2012-07-27 18:16:00

0

这篇快,但它应该工作

/*CTE generates Start & End Dates for 5 weeks 
Start Date = Sunday of week @ midnight 
End Date = Sunday of next week @ midnight 
*/ 
WITH weeks 
     AS (SELECT DATEADD(ww, -4, 
          CAST(FLOOR(CAST(GETDATE() - (DATEPART(dw, 
                  GETDATE()) - 1) AS FLOAT)) AS DATETIME)) AS StartDate 
      UNION ALL 
      SELECT DATEADD(wk, 1, StartDate) 
      FROM  weeks 
      WHERE DATEADD(wk, 1, StartDate) <= GETDATE() 
     ) 
SELECT w.StartDate , 
     COUNT(*) AS OpenContracts 
FROM dbo.Contracts c 
     LEFT JOIN weeks w ON c.StartDate < DATEADD(d, 7, w.StartDate) 
           AND ISNULL(c.EndDate, GETDATE()) >= w.StartDate 
GROUP BY w.StartDate 
相关问题