2015-02-23 116 views
1

請原諒我的經驗不足,我希望這不是一個愚蠢的問題,我卡住了,沒有其他地方可以轉身。我會保持它的一點是:SQL查詢 - 根據日期範圍收集數據 - 可能可變的列數

我想要的結果,像這樣收集工資數據: enter image description here

問題我已經是列的可變數目。我會給出一個日期範圍,並要求在給定範圍內每天返回一個出勤記錄,如果沒有數據存在,則需要返回一個空值。我使用WebAPI作爲中間層,所以我有能力執行進一步的數據操作來實現這個結果。

我的表如下所示:

enter image description here

我不能這需要做,任何物品/職位或任何會幫助我做到這一點誰的第一人?即使僞代碼也會有所幫助;什麼!

非常感謝advnace!

這是我已經能夠拿出,但我什至不知道如果可行的:

-- convert date range into days of month 
-- to ensure null values are included in data?? 
DECLARE @intFlag INT = 0; 
DECLARE @numberOfDays INT = DATEDIFF(DAY, @startDate, @endDate); 
DECLARE @TMP TABLE (DaysOfMonth date) 

WHILE (@intFlag <= @numberOfDays) 
BEGIN 
    INSERT INTO @TMP VALUES (DATEADD(DAY, @intFlag, @startDate)); 
    SET @intFlag = @intFlag + 1 
END 

-- select days in given data range so c# app can build header row 
-- would it help if I pivot this data? 
SELECT 
    DaysOfMonth 
FROM 
    @TMP 
ORDER BY 
    DaysOfMonth 

-- get a count for number of people 
DECLARE @count INT = 0; 
DECLARE @TMPPPL TABLE (Id int identity(1,0), PId Int) 

INSERT INTO 
    @TMPPPL 
SELECT 
    p.PersonId 
FROM 
    dbo.People p 
JOIN 
    dbo.UserTypes ut on p.UserType_UserTypeId = ut.UserTypeId and (ut.Code = 'caregiver' or ut.Code = 'director') 

DECLARE @numberOfPeople INT = (SELECT COUNT(1) FROM @TMPPPL) 

-- create and execute sproc to return row of data for each person 
WHILE (@count <= @numberOfPeople) 
BEGIN 

    -- STUCK HERE, This obviously won't work but what else can I do? 
    EXEC GetPersonAttendanceHours @personId, @startDate, @endDate; 

    SET @count = @count + 1 
END 
+1

嘗試查找SQL Server的PIVOT語法並查看是否有任何這些解決方案可以幫助您解決此問題。如果是這樣,發佈自己的答案並接受它。 – 2015-02-23 16:55:45

+0

Thnx對於這個建議,我已經看過了關鍵點,那個問題是關於日期到出席記錄(一旦日期成爲專欄)。我想nvarchar列哪些將包含所有的數據,並有wepAPI解析它...討厭或動態的SQL,非常不安全! – OverMars 2015-02-23 17:09:20

回答

2

這很有趣。我認爲這會做你想要的。第一測試數據:

CREATE TABLE people (PersonID int, Name varchar(30)) 

INSERT INTO people (PersonID, Name) 
SELECT 1, 'Kelly' 
UNION ALL SELECT 2, 'Dave' 
UNION ALL SELECT 3, 'Mike' 

CREATE TABLE attendances (PersonID int, SignIn datetime, SignOut datetime) 

INSERT INTO attendances (PersonID, SignIn, SignOut) 
SELECT 1, '1-Feb-2015 08:00', '1-Feb-2015 09:00' 
UNION ALL SELECT 1, '1-Feb-2015 12:00', '1-Feb-2015 12:30' 
UNION ALL SELECT 2, '2-Feb-2015 08:00', '2-Feb-2015 08:15' 
UNION ALL SELECT 1, '3-Feb-2015 08:00', '3-Feb-2015 09:00' 
UNION ALL SELECT 1, '4-Feb-2015 08:00', '4-Feb-2015 08:30' 
UNION ALL SELECT 2, '4-Feb-2015 08:00', '4-Feb-2015 10:00' 
UNION ALL SELECT 2, '6-Feb-2015 12:00', '6-Feb-2015 15:00' 
UNION ALL SELECT 3, '6-Feb-2015 15:00', '6-Feb-2015 17:00' 
UNION ALL SELECT 3, '8-Feb-2015 10:00', '8-Feb-2015 12:00' 

然後動態查詢:

DECLARE @startDate DATETIME='1-Feb-2015' 
DECLARE @endDate DATETIME='9-Feb-2015' 
DECLARE @numberOfDays INT = DATEDIFF(DAY, @startDate, @endDate) 

declare @dayColumns TABLE (delta int, colName varchar(12)) 

-- Produce 1 row for each day in the report. Note that this is limited by the 
-- number of objects in sysobjects (which is about 2000 so it's a high limit) 
-- Each row contains a delta date offset, @startDate+delta gives each date to report 
-- which is converted to a valid SQL column name in the format colYYYYMMDD 
INSERT INTO @dayColumns (delta, colName) 
SELECT delta, 'col'+CONVERT(varchar(12),DATEADD(day,delta,@startDate),112) as colName from (
    select (ROW_NUMBER() OVER (ORDER BY sysobjects.id))-1 as delta FROM sysobjects 
) daysAhead 
WHERE delta<[email protected] 

-- Create a comma seperated list of columns to report 
DECLARE @cols AS NVARCHAR(MAX)= '' 
SELECT @cols=CASE WHEN @cols='' THEN @cols ELSE @cols+',' END + colName FROM @dayColumns ORDER BY delta 
DECLARE @totalHours AS NVARCHAR(MAX)= '' 
SELECT @totalHours=CASE WHEN @totalHours='' THEN '' ELSE @totalHours+' + ' END + 'ISNULL(' + colName +',0)' FROM @dayColumns ORDER BY delta 

-- Produce a SQL statement which outputs a variable number of pivoted columns 
DECLARE @query AS NVARCHAR(MAX) 
SELECT @query= 
'declare @days TABLE (reportDay date, colName varchar(12)) 

INSERT INTO @days (reportDay, colName) 
SELECT DATEADD(day,Delta,'''+CONVERT(varchar(22),@startDate,121)+'''), ''col''+CONVERT(varchar(12),DATEADD(day,delta,'''+CONVERT(varchar(22),@startDate,121)+'''),112) as colName from (
    select (ROW_NUMBER() OVER (ORDER BY sysobjects.id))-1 as Delta FROM sysobjects 
) daysAhead 
WHERE Delta<='+CAST(@numberOfDays as varchar(10))+' 

SELECT p.Name, pivotedAttendance.*,'[email protected]+' as totalHours FROM (
    SELECT * FROM (
    select p.PersonID, d.colName, CAST(DATEDIFF(MINUTE, a.SignIn, a.SignOut)/60.0 as decimal(5,1)) as hrsAttendance 
    from @days d 
    CROSS JOIN people p 
    LEFT OUTER JOIN attendances a ON a.PersonID=p.PersonID AND CAST(a.SignOut as DATE)=d.reportDay 
) as s 
    PIVOT (
    SUM(hrsAttendance) FOR colName in ('[email protected]+') 
) as pa 
) as pivotedAttendance 
INNER JOIN people p on p.PersonID=pivotedAttendance.PersonID' 

-- Run the query 
EXEC (@query) 

其以相似的格式,以您的示例生成的數據,所有的報表範圍內的天數和每個人一行。從上面我看到:

Example output

出於列,你應該能夠列名轉換爲顯示器能夠日期(只解析YYYYMMDD出列名)。該日期不能直接用作列名,因爲它會產生無效的列名。

SQL小提琴示例here

+0

如果你喜歡看零值而不是零值,那麼在DATEDIFF計算中增加一個ISNULL,例如'CAST(ISNULL(DATEDIFF(MINUTE,a.SignIn,a.SignOut)/60.0,0)作爲小數點(5,1))作爲hrsAttendance' – Elliveny 2015-02-23 20:46:21

+0

哇,無語...時候給它一去! thnx一堆! – OverMars 2015-02-23 20:51:59

1

這是我爲了顯示已經做了一個主題的變化時間表或出席。我希望類似的東西能與你的報告一起工作。這裏是你的存儲過程的開始:

DECLARE @iDay INT = 0; 
DECLARE @countDays INT = DATEDIFF(DAY, @startDate, @endDate); 
DECLARE @tempDates TABLE ([tempDate] DATE); 
DECLARE @filterDates NVARCHAR; 
WHILE (@iDay <= @countDays) 
BEGIN 
    INSERT INTO @tempDates VALUES (DATEADD(DAY, @iDay, @startDate)); 
    SET @iDay = @iDay + 1; 
END; 
SELECT @filterDates = STUFF(
    (SELECT N''',''' + CONVERT(NVARCHAR, [tempDate], 103) FROM @tempDates FOR XML PATH('')), 
    1, 
    2, 
    '' 
); 

你是在正確的軌道與您的建議。下一個查詢會在您將它傳遞給您之前獲取您的數據。

SELECT [People].[Person_PersonID], [tempDates].[tempDate], [Attendances].[SignIn], [Attendances].[SignOut], 
    MIN([Attendances].[SignOut], DATEADD(DAY, 1, [tempDates].[tempDate])) 
    - MAX([Attendances].[SignIn], [tempDates].[tempDate]) * 24 AS [numHours] 
FROM [People] 
CROSS JOIN @tempDates [tempDates] 
LEFT JOIN [Attendances] 
    ON (
    ([Attendances].[SignIn] < DATEADD(DAY, 1, [tempDates].[tempDate])) 
    AND ([Attendances].[SignOut] > [tempDates].[tempDate]) 
); 

一旦我們對前一個查詢的結果感到滿意,就用PIVOT替代它,看起來應該像這樣。

SELECT * 
FROM (
    SELECT [People].[PersonID], [tempDates].[tempDate], [Attendances].[SignIn], [Attendances].[SignOut], 
    MIN([Attendances].[SignOut], DATEADD(DAY, 1, [tempDates].[tempDate])) 
    - MAX([Attendances].[SignIn], [tempDates].[tempDate]) * 24 AS [numHours] 
    FROM [People] 
    CROSS JOIN @tempDates [tempDates] 
    LEFT JOIN [Attendances] 
    ON (
     ([Attendances].[SignIn] < DATEADD(DAY, 1, [tempDates].[tempDate])) 
     AND ([Attendances].[SignOut] > [tempDates].[tempDate]) 
    ) 
) AS [DatedAttendance] 
PIVOT (
    SUM([numHours]) FOR ([tempDate] IN (@filterDates)) 
) AS [PivotAttendance] 
ORDER BY [PersonID] 
+0

非常感謝您抽出時間,我會立即學習並嘗試。 – OverMars 2015-02-23 19:32:57

+0

不客氣。發佈該答案後,我意識到了一些事情。 1)您可能需要在子查詢(_e.g._,'CONVERT(NVARCHAR,[tempDate],103)')中格式化[tempDate]',這樣PIVOT才能正常工作。 2)您可能需要排除'[DatedAttendance]'中返回的'[SignIn]'和'[SignOut]'字段,以便'[PersonID]'和'[tempDate]'仍然是'GROUP'ed' BY'。 – 2015-02-23 19:40:15