2015-02-23 73 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