2012-01-13 3 views
3

У меня есть следующая таблица, которая имеет отсутствие сотрудников:Суммирования и группировка количества записей в месяце

RecordId EmpID  ActivityCode DateFrom DateTo 
--------------------------------------------------------------- 
666542  1511  AB   29/01/2011 02/02/2011 
666986  1511  AB   11/11/2011 11/11/2011 
666996  1511  EL   13/11/2011 17/11/2011 
755485  1787  SL   01/11/2011 14/11/2011 
758545  1787  SL   15/11/2011 03/12/2011 
796956  1954  AB   09/11/2011 09/11/2011 
799656  1367  AB   09/11/2011 09/11/2011 
808845  1527  EL   16/11/2011 16/11/2011 
823323  1527  EL   17/11/2011 17/11/2011 
823669  1527  EL   18/11/2011 18/11/2011 
899555  1123  AB   09/11/2011 09/12/2011 
990990  1511  AB   12/11/2011 12/11/2011 

Теперь я хочу отчет, созданный с помощью хранимой процедуры, суммирующим при отсутствии дней для конкретного отсутствие кода для каждого месяца в данном году, например, если я хочу знать итоги отсутствия в 2011 году из предыдущей таблицы я получить что-то похожее на:

Month  TotalDays 
--------------------------------- 
JAN 2011  201 
FEB 2011  36 
MAR 2011  67 
APR 2011  91 
.... 

хранимая процедура будет иметь два Params (@ Год INT, @AbsCode NVARCHAR (3)).
Обратите внимание, что иногда запись перекрывается еще на один месяц (например, первая строка в таблице примеров), и это следует учитывать отдельно для каждого месяца. Я пробовал использовать петли, но не повезло. Я настолько слаб в TSQL.



UPDATE
Сейчас я использую пользовательскую функцию значения скалярного и хранимую процедуру для выполнения этой работы, его уродливые и трудно проследить. Вот это каким-либо образом:
Функция пользователя:

ALTER FUNCTION [dbo].[GetActivityTotalDaysInMonth] 
(
    @ActivityCode CHAR(3) 
    ,@Year INT 
    ,@Month INT 
) 
RETURNS INT 
AS 

BEGIN 

DECLARE @FirstDayOfMonth DATETIME 
DECLARE @LastDayOfMonth DATETIME 
SET @FirstDayOfMonth = CAST(CAST(@Year AS varchar) + '-' + CAST(@Month AS varchar) + '-' + CAST(1 AS varchar) AS DATETIME) 
SET @LastDayOfMonth = DATEADD(s, -1, DATEADD(M, 1, @FirstDayOfMonth))  

DECLARE @TotalDays INT 

SELECT @TotalDays = 
SUM(DATEDIFF(DAY, 
    (CASE WHEN ActivityDateFrom < @FirstDayOfMonth THEN @FirstDayOfMonth ELSE ActivityDateFrom END) 
, (CASE WHEN ActivityDateTo > @LastDayOfMonth THEN @LastDayOfMonth ELSE ActivityDateTo END))+1) 

FROM Activities 
WHERE 
[email protected] 
AND ((ActivityDateFrom < @FirstDayOfMonth AND ActivityDateTo >= @FirstDayOfMonth) 
OR (ActivityDateFrom >= @FirstDayOfMonth AND ActivityDateTo <= @LastDayOfMonth) 
OR (ActivityDateFrom <= @LastDayOfMonth AND ActivityDateTo > @LastDayOfMonth)) 
RETURN @TotalDays 
END 

Теперь я называю эту функцию внутри цикла в хранимой процедуре:

ALTER PROCEDURE GetAnnualActivityTotalDays 
(
    @ActivityCode CHAR(3) 
    ,@Year INT 
) 
AS 
BEGIN 
SET NOCOUNT ON; 

DECLARE @Stats TABLE 
([Month] NVARCHAR(50), TotalDays INT) 

DECLARE @MonthNo INT 
DECLARE @Month DATETIME 

SET @MonthNo = 1 

WHILE @MonthNo <= 12 
BEGIN 

    SET @Month = CAST(CAST(@Year AS varchar) + '-' + CAST(@MonthNo AS varchar) + '-' + CAST(1 AS varchar) AS DATETIME) 

    INSERT INTO @Stats ([Month], TotalDays) 
    SELECT UPPER(SUBSTRING(DATENAME(mm, @Month), 1, 3)) + ', ' + CAST(@Year AS NVARCHAR), 
    dbo.GetActivityTotalDaysInMonth(@ActivityCode 
       ,@Year 
       ,@MonthNo 
       ,@Base) 

    SET @MonthNo = @MonthNo + 1 
END 

SELECT * FROM @Stats 
END 

Как вы можете видеть, что это некрасиво код, который Я считаю, что это можно сделать более простым способом. Любые предложения?

+0

Что вы сделали до сих пор? –

+0

Это довольно неприятная структура стола. Ввод таких диапазонов даты, а не отдельных дат может привести к тому, что всевозможные проблемы, такие как сотрудник 123, отсутствуют дважды в одну дату (поскольку диапазоны перекрываются). – JNK

+0

@JNK, Нет, этого никогда не произойдет. Эта таблица используется для всех видов деятельности, а не только отсутствия и ее так полезной в этом дизайне, единственной проблемой, которая была до сих пор, является это. –

ответ

0

Вам нужно будет создать таблицу календаря, которая позволит вам легко подсчитать дни каждого месяца, которые охватывают начальную и конечную даты. Например, recordid = 666542 имеет 3 дня в январе и 2 дня в феврале. Вы были бы в состоянии получить этот номер по запросу, как

select calyear, calmonth, caldate 
from calendar 
    join activities on calendar.caldate between activities.activitydatefrom and activities.activitydateto 
where activitycode = 'AB' 

Если вы заключаете, что в общем табличном выражении можно выполнить агрегацию запросов впоследствии на КТР.

with mycte as (
select calyear, calmonth, caldate 
from calendar 
    join activities on calendar.caldate between activities.activitydatefrom and activities.activitydateto 
where activitycode = 'AB' 
) 

select calyear, calmonth, count(caldate) 
from mycte 
group by calyear, calmonth 
order by calyear, calmonth 

Для создания таблицы календаря вы можете использовать код, подобный

create table calendar (calyear, calmonth, caldate) 

declare @numdays int --number of days to generate in the calendar 
declare @datestart datetime --the date to begin from in the calendar 

set @numdays = 365 
set @datestart = 'jan 1 2011'; 

with num as (
select 0 number 
union 
select 1 number 
union 
select 2 number 
union 
select 3 number 
union 
select 4 number 
union 
select 5 number 
union 
select 6 number 
union 
select 7 number 
union 
select 8 number 
union 
select 9 number 

), 

numberlist as (
select ((hundreds.number * 100) + (tens.number * 10) + ones.number) n 
from num hundreds 
    cross join num tens 
    cross join num ones 
where ((hundreds.number * 100) + (tens.number * 10) + ones.number) < @numdays 
) 

insert into calendar (calyear, calmonth, caldate) 
select 
    datepart(yy,dateadd(dd,n,@datestart)) calyear, 
    datepart(mm,dateadd(dd,n,@datestart)) calmonth, 
    dateadd(dd,n,@datestart)caldate 
from numberlist 
Смежные вопросы