2017-05-10 1 views
4

Ich habe diese Tabelle:Count Anzahl der Tage pro Mitarbeiter Urlaub in einem Monat SQL Server nehmen

Vacationtbl:

ID Start  End 
    ------------------------- 
    01 04/10/17 04/12/17 
    01 04/27/17 05/02/17 
    02 04/13/17 04/15/17 
    02 04/17/17 04/20/17 
    03 06/14/17 06/22/17 

Employeetbl: 

ID Fname Lname 
------------------ 
01 John AAA 
02 Jeny BBB 
03 Jeby CCC 

Ich mag die Anzahl der Tage, jeder Mitarbeiter Urlaub nehmen im April rechnen .

Meine Frage:

SELECT 
    SUM(DATEDIFF(DAY, Start, End) + 1) AS Days 
FROM 
    Vacationtbl 
GROUP BY 
    ID 
  • 01 kehrt 9 (nicht korrekt)
  • 02 kehrt 7 (korrekte)

Wie ich die Abfrage beheben kann, so dass es bis die zählt Ende des Monats und endet am Monatsende. Zum Beispiel hat der April 30 Tage. In der zweiten Reihe sollte Mitarbeiter 014/27/17 bis 4/30/17 zählen. Und 05/02/17 ist für Mai.

Dank

Antwort

3

Der Tally/Kalender-Tabelle ist der Weg zu gehen. Sie können jedoch eine ad-hoc Tally-Tabelle verwenden.

Beispiel

Select Year = Year(D) 
     ,Month = Month(D) 
     ,ID 
     ,Days = count(*) 
From Vacationtbl A 
Cross Apply (
       Select Top (DateDiff(DAY,[Start],[End])+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),[Start]) 
       From master..spt_values 
      ) B 
-- YOUR OPTIONAL WHERE STATEMENT HERE -- 
Group By ID,Year(D),Month(D) 
Order By 1,2,3 

Returns

Year Month ID Days 
2017 4  01 7 
2017 4  02 7 
2017 5  01 2 

EDIT - All-ID zu zeigen, auch wenn Null Tage

Select ID 
     ,Year = Year(D) 
     ,Month = Month(D) 
     ,Days = sum(case when D between [Start] and [End] then 1 else 0 end) 
From (
     Select Top (DateDiff(DAY,'05/01/2017','05/31/2017')+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),'05/01/2017') 
     From master..spt_values 
    ) D 
Cross Join Vacationtbl B 
Group By ID,Year(D),Month(D) 
Order By 1,2,3 

Returns

ID Year Month Days 
1 2017 5  2 
2 2017 5  0 

dbFiddle if it Helps

EDIT - 2 Korrigiert für Überlappungen (Gaps und Inseln)

--Create Some Sample Data 
---------------------------------------------------------------------- 
Declare @Vacationtbl Table ([ID] varchar(50),[Start] date,[End] date) 
Insert Into @Vacationtbl Values 
(01,'04/10/17','04/12/17') 
,(01,'04/27/17','05/02/17') 
,(02,'04/13/17','04/15/17') 
,(02,'04/17/17','04/20/17') 
,(02,'04/16/17','04/17/17') -- << Overlap 
,(03,'05/16/17','05/17/17') 

-- The Actual Query 
---------------------------------------------------------------------- 
Select ID 
     ,Year = Year(D) 
     ,Month = Month(D) 
     ,Days = sum(case when D between [Start] and [End] then 1 else 0 end) 
From (Select Top (DateDiff(DAY,'04/01/2017','04/30/2017')+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),'04/01/2017') From master..spt_values) D 
Cross Join (
       Select ID,[Start] = min(D),[End] = max(D) 
       From (
         Select E.*,Grp = Dense_Rank() over (Order By D) - Row_Number() over (Partition By ID Order By D) 
         From (
           Select Distinct A.ID,D 
            From @Vacationtbl A 
            Cross Apply (Select Top (DateDiff(DAY,A.[Start],A.[End])+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),A.[Start]) From master..spt_values) B 
           ) E 
        ) G 
       Group By ID,Grp  
      ) B 
Group By ID,Year(D),Month(D) 
Order By 1,2,3 

Returns

ID Year Month Days 
1 2017 4  7 
2 2017 4  8 
3 2017 4  0 
+0

Woh, es ist kompliziert, aber es hat funktioniert. Vielen Dank. –

+1

@JennyTran Wenn dies wirklich geholfen hat, können Sie die Antwort annehmen –

+0

Hallo John, habe ich die Antwort bereits akzeptiert? Ich weiß nicht wie ich das machen soll? Ich habe gerade auf den oberen Pfeil oben geklickt? Ist das richtig? Vielen Dank. –

0

Sie können für diese Art der Sache einen Kalender oder Daten-Tabelle verwenden.

Für nur 152kb im Speicher, können Sie 30 Jahre Daten in einer Tabelle haben mit diesem:

/* dates table */ 
declare @fromdate date = '20000101'; 
declare @years int = 30; 
/* 30 years, 19 used data pages ~152kb in memory, ~264kb on disk */ 
;with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n)) 
select top (datediff(day, @fromdate,dateadd(year,@years,@fromdate))) 
    [Date]=convert(date,dateadd(day,row_number() over(order by (select 1))-1,@fromdate)) 
into dbo.Dates 
from n as deka cross join n as hecto cross join n as kilo 
       cross join n as tenK cross join n as hundredK 
order by [Date]; 
create unique clustered index ix_dbo_Dates_date 
    on dbo.Dates([Date]); 

Ohne den eigentlichen Schritt der Erstellung einer Tabelle zu nehmen, können Sie es in einem common table expression verwenden mit nur diese :

declare @fromdate date = '20170401'; 
declare @thrudate date = '20170430'; 
;with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n)) 
, dates as (
    select top (datediff(day, @fromdate, @thrudate)+1) 
     [Date]=convert(date,dateadd(day,row_number() over(order by (select 1))-1,@fromdate)) 
    from n as deka cross join n as hecto cross join n as kilo 
       cross join n as tenK cross join n as hundredK 
    order by [Date] 
) 
select [Date] 
from dates; 

Verwenden Sie entweder wie so:

select 
    v.Id 
    , count(*) as VacationDays 
from Vacationtbl v 
    inner join Dates d 
    on d.Date >= v.[Start] 
    and d.Date <= v.[End] 
where d.Date >= '20170401' 
    and d.Date <= '20170430' 
group by v.Id 

rextester Demo (Tabelle): http://rextester.com/PLW73242

rextester Demo (CTE): http://rextester.com/BCY62752

kehrt:

+----+--------------+ 
| Id | VacationDays | 
+----+--------------+ 
| 01 |   7 | 
| 02 |   7 | 
+----+--------------+ 

Anzahl und Kalender Tabellenverweis:

+0

Die Rückkehr ist richtig, aber ich kann nicht Tally oder Daten-Tabelle verwenden. Vielen Dank. –

+0

@JennyTran Eine Demo für die 'cte' Version hinzugefügt: http://rextester.com/BCY62752 – SqlZim

1

Ohne Termine Tabelle, könnten Sie

select Id 
    ,sum(case when [end]>'20170430' and [start]<'20170401' then datediff(day,'20170401','20170430')+1 
       when [end]>'20170430' then datediff(day,[start],'20170430')+1 
       when [start]<'20170401' then datediff(day,'20170401',[end])+1 
      else datediff(day,[start],[end])+1 
     end) as VacationDays 
from Vacationtbl 
where [start] <= '20170430' and [end] >= '20170401' 
group by Id 

Es gibt 3 Bedingungen hier

verwenden
  • Beginn ist vor diesem Monat und das Ende ist nach diesem Monat . In diesem Fall subtrahieren Sie das Ende und das Anfangsdatum des Monats.
  • Ende ist nach Monatsende und Start ist im Monat, in diesem Fall subtrahieren Monat Enddatum von Anfang an.
  • Start ist vor diesem Monat, aber das Ende ist in dem Monat. In diesem Fall subtrahieren Sie das Monatsanfangsdatum und das Enddatum.

Edit: Auf der Grundlage der Stellungnahme der OP, dass die zukünftigen Termine haben aufgenommen werden,

/*This recursive cte generates the month start and end dates with in a given time frame 
For Eg: all the month start and end dates for 2017 
Change the start and end period as needed*/ 
with dates (month_start_date,month_end_date) as 
(select cast('2017-01-01' as date),cast(eomonth('2017-01-01') as date) 
union all 
select dateadd(month,1,month_start_date),eomonth(dateadd(month,1,month_start_date)) from dates 
where month_start_date < '2017-12-01' 
) 
--End recursive cte 
--Query logic is the same as above 
select v.Id 
,year(d.month_start_date) as yr,month(d.month_start_date) as mth 
,sum(case when v.[end]>d.month_end_date and v.[start]<d.month_start_date then datediff(day,d.month_start_date,d.month_end_date)+1 
      when v.[end]>d.month_end_date then datediff(day,v.[start],d.month_end_date)+1 
      when v.[start]<d.month_start_date then datediff(day,d.month_start_date,v.[end])+1 
    else datediff(day,v.[start],v.[end])+1 
    end) as VacationDays 
from dates d 
join Vacationtbl v on v.[start] <= d.month_end_date and v.[end] >= d.month_start_date 
group by v.id,year(d.month_start_date),month(d.month_start_date) 
+0

Es ist dynamisch. Wie mache ich es für 12 Monate und fortgeschrittene Monate in der Zukunft? –

+0

Können Sie in diesem Fall die erwartete Ausgabe anzeigen? –

+0

7 für 2 Mitarbeiter –

0

Angenommen, Sie nur einen Monat wollen, und Sie wollen alle Tage zählen, können Sie dies mit Arithmetik tun. Eine separate Kalendertabelle ist nicht erforderlich. Der Vorteil ist Leistung.

Ich denke, das wäre einfacher, wenn SQL Server least() und greatest() unterstützt, aber case tun:

select id, 
     sum(1 + datediff(day, news, newe)) as vacation_days_april 
from vactiontbl v cross apply 
    (values (case when [start] < '2017-04-01' then cast('2017-04-01' as date) else [start] end), 
      (case when [end] >= '2017-05-01' then cast('2017-04-30' as date) else [end] end) 
    ) v(news, newe) 
where news <= newe 
group by id; 

du in jedem Monat leicht erweitern können:

with m as (
     select cast('2017-04-01' as date) as month_start, 
      cast('2017-04-30' as date) as month_end 
    ) 

select id, 
     sum(1 + datediff(day, news, newe)) as vacation_days_aprile 
from m cross join 
    vactiontbl v cross apply 
    (values (case when [start] < m.month_start then m.month_start else [start] end), 
      (case when [end] >= m.month_end then m.month_end else [end] end) 
    ) v(news, newe) 
where news <= newe 
group by id; 

Sie selbst verwenden können, eine ähnliche Idee, die sich auf mehrere Monate erstreckt, mit einer anderen Zeile für jeden Benutzer und jeden Monat.

+0

Es ist dynamisch, wie mache ich es für 12 Monate und fortgeschrittene Monate in der Zukunft? Ich verwende weder Tally-Tabelle noch Dates-Tabellen. Ich bin Neuling, also suche ich nach etwas einfachem hiiiii. Vielen Dank! –

+0

Ich weiß nicht, was du mit dynamic meinst. Legen Sie einfach die Liste der gewünschten Daten als Zeilen in das CTE und fügen Sie dann den Monatstart zu "Gruppe von" und "Auswahl" hinzu. –

+0

@SqlZim. . . Vielen Dank. –

0

die Sie interessieren,

declare @Vacationtbl table(ID int,Startdate date,Enddate date) 
insert into @Vacationtbl VALUES 
(1 ,'04/10/17','04/12/17') 
,(1 ,'04/27/17','05/02/17') 
,(2 ,'04/13/17','04/15/17') 
,(2 ,'04/17/17','04/20/17') 
-- somehow convert your input into first day of month 
Declare @firstDayofGivenMonth date='2017-04-01' 
Declare @LasttDayofGivenMonth date=dateadd(day,-1,dateadd(month,datediff(month,0,@firstDayofGivenMonth)+1,0)) 

;with CTE as 
(
select * 
,case when Startdate<@firstDayofGivenMonth then @firstDayofGivenMonth else Startdate end NewStDT 
,case when Enddate>@LasttDayofGivenMonth then @LasttDayofGivenMonth else Enddate end NewEDT 
from @Vacationtbl 
) 

SELECT 
SUM(DATEDIFF(DAY, NewStDT, NewEDT) + 1) AS Days 
FROM 
CTE 
GROUP BY 
ID 
Verwandte Themen