2012-07-19 68 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 
相關問題