I am sorry if this question has been answered before, but I cannot find it. Perhaps of my bad keywords.
I have this table:
CREATE TABLE test1
(
Employee VARCHAR(10),
Band VARCHAR(10),
StartDate DATE,
EndDate DATE
)
INSERT INTO test1
VALUES ('Emp1', 'Band1', '2009-01-01', '2010-12-31'),
('Emp1', 'Band1', '2011-01-01', '2012-12-31'),
('Emp1', 'Band1', '2013-01-01', '2013-08-31'),
('Emp1', 'Band2', '2013-09-01', '2013-12-31'),
('Emp1', 'Band2', '2014-01-01', '2014-06-30'),
('Emp1', 'Band1', '2014-07-01', '2014-12-31'),
('Emp1', 'Band1', '2015-01-01', '2018-08-31'),
('Emp2', 'Band1', '2012-01-01', '2014-12-31'),
('Emp2', 'Band1', '2015-01-01', '2018-03-31')
Results in this table:
Employee Band StartDate EndDate
----------------------------------------
Emp1 Band1 2009-01-01 2010-12-31
Emp1 Band1 2011-01-01 2012-12-31
Emp1 Band1 2013-01-01 2013-08-31
Emp1 Band2 2013-09-01 2013-12-31
Emp1 Band2 2014-01-01 2014-06-30
Emp1 Band1 2014-07-01 2014-12-31
Emp1 Band1 2015-01-01 2018-08-31
Emp2 Band1 2012-01-01 2014-12-31
Emp2 Band1 2015-01-01 2018-03-31
What I want to create is a result table grouping each employee with the band and the minimum start date and maximum end date, but when an intermediate record (band) exist in the middle between the similar band, the end date should be capped and the start date of the next group in similar band should reset again.
Employee Band StartDate EndDate
----------------------------------------
Emp1 Band1 2009-01-01 2013-08-31
Emp1 Band2 2013-09-01 2014-06-30
Emp1 Band1 2014-07-01 2018-08-31
Emp2 Band1 2012-01-01 2018-03-31
I have tried CTE to get the max and min of each band and compare it with the original table, but I still fail. I also tried using lead and lag but still fail.
Nice to have
Supposed the second record's EndDate is 2012-02-01, I prefer the result is still one record for the first Band1 group.
Employee Band StartDate EndDate
----------------------------------------
Emp1 Band1 2009-01-01 2013-08-31
With each row of the result only differing in Band, I would able to figure out how long an employee has stayed in a certain band before moving to other band (different band).
But it is something nice to have.
Apparently you want to group rows whenever an employee changes band. This is straight forward with window functions. The following solution adds a "change" flag to rows whenever the band changes. Be advised that it ignores gaps. Add DATEDIFF check to the case statement to find the actual amount of time the person was associated with a band:
DECLARE #test1 TABLE(
Employee VARCHAR(10),
Band VARCHAR(10),
StartDate DATE,
EndDate DATE
);
INSERT INTO #test1 VALUES
('Emp1', 'Band1', '2009-01-01', '2010-12-31'),
('Emp1', 'Band1', '2011-01-01', '2012-12-31'),
('Emp1', 'Band1', '2013-01-01', '2013-08-31'),
('Emp1', 'Band2', '2013-09-01', '2013-12-31'),
('Emp1', 'Band2', '2014-01-01', '2014-06-30'),
('Emp1', 'Band1', '2014-07-01', '2014-12-31'),
('Emp1', 'Band1', '2015-01-01', '2018-08-31'),
('Emp2', 'Band1', '2012-01-01', '2014-12-31'),
('Emp2', 'Band1', '2015-01-01', '2018-03-31');
WITH cte1 AS (
SELECT *,
CASE WHEN LAG(Band) OVER (PARTITION BY Employee ORDER BY StartDate) = Band /* AND DATEDIFF(...) */ THEN 0 ELSE 1 END AS Chg
FROM #test1
), cte2 AS (
SELECT *,
SUM(Chg) OVER (PARTITION BY Employee ORDER BY StartDate) AS Grp
FROM cte1
)
SELECT Employee, Band, MIN(StartDate), Max(EndDate)
FROM cte2
GROUP BY Employee, Band, Grp
DB Fiddle
This is the intermediate result in case you want to see how it works:
| Employee | Band | StartDate | EndDate | Chg | Grp |
|----------|-------|---------------------|---------------------|-----|-----|
| Emp1 | Band1 | 01/01/2009 00:00:00 | 31/12/2010 00:00:00 | 1 | 1 |
| Emp1 | Band1 | 01/01/2011 00:00:00 | 31/12/2012 00:00:00 | 0 | 1 |
| Emp1 | Band1 | 01/01/2013 00:00:00 | 31/08/2013 00:00:00 | 0 | 1 |
| Emp1 | Band2 | 01/09/2013 00:00:00 | 31/12/2013 00:00:00 | 1 | 2 |
| Emp1 | Band2 | 01/01/2014 00:00:00 | 30/06/2014 00:00:00 | 0 | 2 |
| Emp1 | Band1 | 01/07/2014 00:00:00 | 31/12/2014 00:00:00 | 1 | 3 |
| Emp1 | Band1 | 01/01/2015 00:00:00 | 31/08/2018 00:00:00 | 0 | 3 |
| Emp2 | Band1 | 01/01/2012 00:00:00 | 31/12/2014 00:00:00 | 1 | 1 |
| Emp2 | Band1 | 01/01/2015 00:00:00 | 31/03/2018 00:00:00 | 0 | 1 |
This is commonly known as Gaps and Islands.
One approach
Example
Declare #YourTable Table ([Employee] varchar(50),[Band] varchar(50),[Start] date,[End] date)
Insert Into #YourTable Values
('Emp1','Band1','2009-01-01','2010-12-31')
,('Emp1','Band1','2011-01-01','2012-12-31')
,('Emp1','Band1','2013-01-01','2013-08-31')
,('Emp1','Band2','2013-09-01','2013-12-31')
,('Emp1','Band2','2014-01-01','2014-06-30')
,('Emp1','Band1','2014-07-01','2014-12-31')
,('Emp1','Band1','2015-01-01','2018-08-31')
,('Emp2','Band3','2012-01-01','2014-12-31')
,('Emp2','Band3','2015-01-01','2018-03-31')
;with cte as (
Select *,Grp = sum(Flg) over (Partition By Employee Order by [End])
From (
Select *,Flg = IsNull(datediff(DAY,Lag([End],1) over (Partition By Employee,Band Order by [End]) ,[Start]) - 1,1)
From #YourTable
) A
)
Select Employee
,Band
,[Start] = min([Start])
,[End] = max([End])
From cte
Group By Employee,Band,Grp
Order by Employee,max([End])
Returns
Employee Band Start End
Emp1 Band1 2009-01-01 2013-08-31
Emp1 Band2 2013-09-01 2014-06-30
Emp1 Band1 2014-07-01 2018-08-31
Emp2 Band3 2012-01-01 2018-03-31
If it helps with the visualization, the CTE produces the following
Notice the Flag and Group Columns
Related
I need to sum up values from Money column for each WeekNumber.
Now I have view:
WeekNumber | DayTime | Money
---------------------------------------
1 | 2012-01-01 | 20.4
1 | 2012-01-02 | 30.5
1 | 2012-01-03 | 55.1
2 | 2012-02-01 | 67.3
2 | 2012-02-02 | 33.4
3 | 2012-03-01 | 11.8
3 | 2012-03-04 | 23.9
3 | 2012-03-05 | 34.3
4 | 2012-04-01 | 76.6
4 | 2012-04-02 | 90.3
Tsql:
SELECT datepart(week,DayTime) AS WeekNumber, DayTime, Money FROM dbo.Transactions
In conclusion, I would like to get something like this:
WeekNumber | DayTime | Sum
---------------------------------------
1 | 2012-01-01 | 106
2 | 2012-02-02 | 100.7
3 | 2012-03-03 | 470
4 | 2012-04-01 | 166.9
DayTime should be random for each Week Number but exists in column DayTime from view above.
Please, be free to write your ideas. Thanks.
SELECT datepart(week,DayTime) AS WeekNumber
, MIN(DayTime) DayTime --<-- Instead of random get first date from your data in that week
, SUM(Money) AS [Sum]
FROM dbo.Transactions
GROUP BY datepart(week,DayTime)
Try this
SELECT datepart(week,DayTime) AS WeekNumber, SUM(Money) FROM dbo.Transactions GROUP BY WeekNumber
As you will have number of rows for each week you cannot get DayTime with the same table. There are other ways to add that too like JOIN
Change your SQL to sum the money column. Like this
SELECT
datepart(week,DayTime) AS WeekNumber,
DayTime, Money = SUM(Money)
FROM dbo.Transactions
GROUP BY datepart(week,DayTime),DayTime
SELECT datepart(week, DayTime) AS WeekNumber
,MIN(DayTime)
,SUM(MONEY)
FROM dbo.Transactions
GROUP BY datepart(week, DayTime)
I currently have the following table:
+-----+-----------------------------+------------------------------+
| ID | StartDate | EndDate |
+-----+-----------------------------+------------------------------|
| 1 | 2017-07-24 08:00:00.000 | 2017-07-29 08:00:00.000 |
| 2 | 2017-07-25 08:00:00.000 | 2017-07-28 08:00:00.000 |
| 3 | 2017-07-25 08:00:00.000 | 2017-07-26 08:00:00.000 |
+-----+-----------------------------+------------------------------+
I would like to know the count of the ID's that were not Closed on each date.
So for example, I wan't to know the count of open ID's on 2017-07-26 00:00:00.000. This would be all 3 in this case.
Another example: I wan't to know the count of open ID's on 2017-07-29 00:00:00.000. Which would be result to 1. Only ID=1 is Not yet closed at that date.
I have tried using another solution here on StackOverflow, but I can't quite figure why it is giving me false results.
declare #dt date, #dtEnd date
set #dt = getdate()-7
set #dtEnd = dateadd(day, 100, #dt);
WITH CTEt1 (SupportCallID, StartDate, EndDate, Onhold)
as
(SELECT SupportCallID
,OpenDate
,MAX(CASE WHEN StatusID IN('19381771-8E81-40C5-8E36-62A7DB0A2A99', '95C7A5FB-2389-4D14-9DAE-A08BFCC3B09A', 'D5429790-3B43-4462-9E1E-2466EA29AC74') then CONVERT(DATE, LastChangeDate) end) EndDate
,OnHold
FROM [ClienteleITSM_Prod_Application].[dbo].[SupportCall]
group by SupportCallID, OpenDate, OnHold
)
SELECT dates.myDate,
(SELECT COUNT(*)
FROM CTEt1
WHERE myDate BETWEEN StartDate and EndDate
)
FROM
(select dateadd(day, number, #dt) mydate
from
(select distinct number from master.dbo.spt_values
where name is null
) n
where dateadd(day, number, #dt) < #dtEnd) dates
If you use a cte to create a table of dates that span the range of dates in your source table, you can easily left join from that to your source table and count up the rows returned:
declare #t table(ID int,StartDate datetime,EndDate datetime);
insert into #t values (1,'2017-07-24 08:00:00.000','2017-07-29 08:00:00.000'),(2,'2017-07-25 08:00:00.000','2017-07-28 08:00:00.000'),(3,'2017-07-25 08:00:00.000','2017-07-26 08:00:00.000');
declare #StartDate datetime = (select min(StartDate) from #t);
declare #EndDate datetime = (select max(EndDate) from #t);
-- Table with 10 rows in to be joined together to create a large tally table (10 * 10 * 10 * etc)
with t(t) as (select t from (values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t(t))
-- Add the row_number of the tally table to your start date to generate all dates within your data range
,d(d) as (select top(datediff(d,#StartDate,#EndDate)+1) dateadd(d,row_number() over (order by (select null))-1,#StartDate) from t t1,t t2,t t3)
select d.d
,count(t.ID) as OpenIDs
from d
left join #t as t
on(d.d between cast(t.StartDate as date) and t.EndDate)
group by d.d
order by d.d;
Output:
+-------------------------+---------+
| d | OpenIDs |
+-------------------------+---------+
| 2017-07-24 08:00:00.000 | 1 |
| 2017-07-25 08:00:00.000 | 3 |
| 2017-07-26 08:00:00.000 | 3 |
| 2017-07-27 08:00:00.000 | 2 |
| 2017-07-28 08:00:00.000 | 2 |
| 2017-07-29 08:00:00.000 | 1 |
+-------------------------+---------+
Using T-SQL, I want a new column that will show me the first day of each month, for the current year of getdate().
After that I need to count the rows on this specific date. Should I do it with CTE or a temp table?
If 2012+, you can use DateFromParts()
To Get a List of Dates
Select D = DateFromParts(Year(GetDate()),N,1)
From (values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)) N(N)
Returns
D
2017-01-01
2017-02-01
2017-03-01
2017-04-01
2017-05-01
2017-06-01
2017-07-01
2017-08-01
2017-09-01
2017-10-01
2017-11-01
2017-12-01
Edit For Trans Count
To get Transactions (assuming by month). It becomes a small matter of a left join to created Dates
-- This is Just a Sample Table Variable for Demonstration.
-- Remove this and Use your actual Transaction Table
--------------------------------------------------------------
Declare #Transactions table (TransDate date,MoreFields int)
Insert Into #Transactions values
('2017-02-18',6)
,('2017-02-19',9)
,('2017-03-05',5)
Select TransMonth = A.MthBeg
,TransCount = count(B.TransDate)
From (
Select MthBeg = DateFromParts(Year(GetDate()),N,1)
,MthEnd = EOMonth(DateFromParts(Year(GetDate()),N,1))
From (values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)) N(N)
) A
Left Join #Transactions B on TransDate between MthBeg and MthEnd
Group By A.MthBeg
Returns
TransMonth TransCount
2017-01-01 0
2017-02-01 2
2017-03-01 1
2017-04-01 0
2017-05-01 0
2017-06-01 0
2017-07-01 0
2017-08-01 0
2017-09-01 0
2017-10-01 0
2017-11-01 0
2017-12-01 0
For an adhoc table of months for a given year:
declare #year date = dateadd(year,datediff(year,0,getdate() ),0)
;with Months as (
select
MonthStart=dateadd(month,n,#year)
from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11)) t(n)
)
select MonthStart
from Months
rextester demo: http://rextester.com/POKPM51023
returns:
+------------+
| MonthStart |
+------------+
| 2017-01-01 |
| 2017-02-01 |
| 2017-03-01 |
| 2017-04-01 |
| 2017-05-01 |
| 2017-06-01 |
| 2017-07-01 |
| 2017-08-01 |
| 2017-09-01 |
| 2017-10-01 |
| 2017-11-01 |
| 2017-12-01 |
+------------+
The first part: dateadd(year,datediff(year,0,getdate() ),0) adds the number of years since 1900-01-01 to the date 1900-01-01. So it will return the first date of the year. You can also swap year for other levels of truncation: year, quarter, month, day, hour, minute, second, et cetera.
The second part uses a common table expression and the table value constructor (values (...),(...)) to source numbers 0-11, which are added as months to the start of the year.
Not sure why you require recursive... But for first day of month you can try query like below:
Select Dateadd(day,1,eomonth(Dateadd(month, -1,getdate())))
declare #year date = dateadd(year,datediff(year,0,getdate() ),0)
;WITH months(MonthNumber) AS
(
SELECT 0
UNION ALL
SELECT MonthNumber+1
FROM months
WHERE MonthNumber < 11
)
select dateadd(month,MonthNumber,#year)
from months
Using SQL Server 2012 I need to get the datediff of all dates in a Log table which has the same column, for example:
ID | Version | Status | Date
-----------------------------------------------------
12345 | 1 | new | 2014-05-01 00:00:00.000
12345 | 2 | up | 2014-05-02 00:00:00.000
12345 | 3 | appr | 2014-05-03 00:00:00.000
67890 | 1 | new | 2014-05-04 00:00:00.000
67890 | 2 | up | 2014-05-08 00:00:00.000
67890 | 3 | rej | 2014-05-13 00:00:00.000
I need to get the date diff of all sequential dates (date between 1, 2 and date between 2, 3)
I have tried creating a while but with no luck!
Your help is really appreciated!
This Calculates DateDiff as per your query "date diff of all sequential dates",if not sequential,it will just show same date.Further please don't use Reserved Keywords as Column names
SELECT ID,
[VERSION],
[STATUS],
[DATE],
CASE WHEN LEAD([DATE]) OVER (PARTITION BY ID ORDER BY [VERSION])=DATEADD(DAY,1,[DATE])
THEN CAST(DATEDIFF(DAY,[DATE],LEAD([DATE]) OVER (PARTITION BY ID ORDER BY VERSION)) AS VARCHAR(5))
ELSE [DATE] END AS DATEDIFFF
FROM
#TEMP
Another way with OUTER APPLY (get the previous value) :
SELECT t.*,
DATEDIFF(day,p.[Date],t.[Date]) as dd
FROM YourTable t
OUTER APPLY (
SELECT TOP 1 *
FROM YourTable
WHERE ID = t.ID AND [DATE] < t.[Date] AND [Version] < t.[Version]
ORDER BY [Date] DESC
) as p
Output:
ID Version Status Date dd
12345 1 new 2014-05-01 00:00:00.000 NULL
12345 2 up 2014-05-02 00:00:00.000 1
12345 3 appr 2014-05-03 00:00:00.000 1
67890 1 new 2014-05-04 00:00:00.000 NULL
67890 2 up 2014-05-08 00:00:00.000 4
67890 3 rej 2014-05-13 00:00:00.000 5
Note: If you are using SQL Server 2012 then better use LEAD and LAG functions.
I have a table [pricelist]
startHour
endHour
Price
and another table that contains the [actualuse]
startDate
endDate
user
My data
pricelist:
startHour | endHour | price
----------------------------
00:00 | 07:59 | 10
08:00 | 15:59 | 20
16:00 | 23:59 | 5
actualUse:
startDate | endDate | jobId
-------------------------------------------
12/10/2014 08:30 | 12/10/2014 15:20| 1
12/10/2014 07:30 | 12/10/2014 18:20| 2
12/10/2014 07:30 | 13/10/2014 16:20| 3
12/10/2014 09:30 | 13/10/2014 00:20| 4
I try to get for every gob all rows in pricelist the are belong to. For example for jobId 1 I will get
startDate | endDate | jobId |price
---------------------------------------------------
12/10/2014 08:30 | 12/10/2014 15:20| 1 |20
for jobId 2
startDate | endDate | jobId |price
---------------------------------------------------
12/10/2014 07:30 | 12/10/2014 07:59| 2 |10
12/10/2014 08:00 | 12/10/2014 15:59| 2 |20
12/10/2014 16:00 | 12/10/2014 18:20| 2 |5
for jobId 3
startDate | endDate | jobId |price
---------------------------------------------------
12/10/2014 07:30 | 12/10/2014 07:59| 3 |10
12/10/2014 08:00 | 12/10/2014 15:59| 3 |20
12/10/2014 16:00 | 12/10/2014 23:59| 3 |5
13/10/2014 00:00 | 13/10/2014 07:59| 3 |10
13/10/2014 08:00 | 13/10/2014 15:59| 3 |20
13/10/2014 16:00 | 13/10/2014 16:20| 3 |5
Here is a possible working solution. I know the cursor is a bit ugly but I had to create another table with every possibles dates between the date ranges. You might be able to refactor some date computation too (I assumed you were working with TIME datatype)
/*
create table pricelist
(
startHour time,
endHour time,
price decimal(18,2)
)
create table actualuse
(
startDate datetime,
endDate datetime,
jobId int
)
insert pricelist values
('00:00','07:59',10),
('08:00','15:59',20),
('16:00','23:59',5)
set dateformat dmy
insert actualuse values
('12/10/2014 08:30','12/10/2014 15:20',1),
('12/10/2014 07:30','12/10/2014 18:20',2),
('12/10/2014 07:30','13/10/2014 16:20',3),
('12/10/2014 09:30','13/10/2014 00:20',4)
*/
BEGIN TRY DROP TABLE #actualUseDays END TRY
BEGIN CATCH END CATCH
CREATE TABLE #actualUseDays (
startDate DATETIME
,endDate DATETIME
,jobId INT
)
DECLARE #startDate DATETIME
,#endDate DATETIME
,#jobId INT;
DECLARE cur CURSOR FORWARD_ONLY FOR SELECT * FROM actualuse
OPEN cur;
FETCH NEXT FROM cur INTO #startDate ,#endDate ,#jobId
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT #actualUseDays
SELECT #startDate
,iif(CAST(#endDate AS DATE) <> CAST(#startDate AS DATE), DATEADD(day, DATEDIFF(day, '19000101', cast(#startDate AS DATE)), CAST(CAST('23:59:59' AS TIME) AS
DATETIME2(7))), #endDate)
,#jobId
UNION
SELECT CAST(DATEADD(DAY, number + 1, #startDate) AS DATE) [Date]
,iif(CAST(#endDate AS DATE) <> CAST(DATEADD(DAY, number + 1, #startDate) AS DATE), DATEADD(day, DATEDIFF(day, '19000101', CAST(DATEADD(DAY, number + 1,
#startDate) AS DATE)), CAST(CAST('23:59:59' AS TIME) AS DATETIME2(7))), #endDate)
,#jobId
FROM master..spt_values
WHERE type = 'P'
AND DATEADD(DAY, number + 1, CAST(#startDate AS DATE)) < #endDate
FETCH NEXT FROM cur INTO #startDate ,#endDate ,#jobId
END
CLOSE cur;
DEALLOCATE cur;
/*
#actualUseDays now contains :
startDate endDate jobId
----------------------- ----------------------- -----------
2014-10-12 08:30:00.000 2014-10-12 15:20:00.000 1
2014-10-12 07:30:00.000 2014-10-12 18:20:00.000 2
2014-10-12 07:30:00.000 2014-10-12 23:59:59.000 3
2014-10-13 00:00:00.000 2014-10-13 16:20:00.000 3
2014-10-12 09:30:00.000 2014-10-12 23:59:59.000 4
2014-10-13 00:00:00.000 2014-10-13 00:20:00.000 4
*/
SELECT iif(CAST(a.startDate AS TIME) > p.startHour, startDate, DATEADD(day, DATEDIFF(day, '19000101', CAST(startDate AS DATE)), CAST(startHour AS DATETIME2(7)))) AS
startDate
,iif(CAST(a.endDate AS TIME) < p.endHour, endDate, DATEADD(day, DATEDIFF(day, '19000101', CAST(endDate AS DATE)), CAST(endHour AS DATETIME2(7)))) AS endDate
,jobId
,price
FROM #actualUseDays a
INNER JOIN pricelist p
ON CAST(a.startDate AS TIME) <= p.endHour
AND CAST(a.endDate AS TIME) >= p.startHour
ORDER BY jobId
,iif(CAST(a.startDate AS TIME) > p.startHour, startDate, DATEADD(day, DATEDIFF(day, '19000101', CAST(startDate AS DATE)), CAST(startHour AS DATETIME2(7))))
Results :
startDate endDate jobId price
--------------------------- --------------------------- ----------- ---------------------------------------
2014-10-12 08:30:00.0000000 2014-10-12 15:20:00.0000000 1 20.00
2014-10-12 07:30:00.0000000 2014-10-12 07:59:00.0000000 2 10.00
2014-10-12 08:00:00.0000000 2014-10-12 15:59:00.0000000 2 20.00
2014-10-12 16:00:00.0000000 2014-10-12 18:20:00.0000000 2 5.00
2014-10-12 07:30:00.0000000 2014-10-12 07:59:00.0000000 3 10.00
2014-10-12 08:00:00.0000000 2014-10-12 15:59:00.0000000 3 20.00
2014-10-12 16:00:00.0000000 2014-10-12 23:59:00.0000000 3 5.00
2014-10-13 00:00:00.0000000 2014-10-13 07:59:00.0000000 3 10.00
2014-10-13 08:00:00.0000000 2014-10-13 15:59:00.0000000 3 20.00
2014-10-13 16:00:00.0000000 2014-10-13 16:20:00.0000000 3 5.00
2014-10-12 09:30:00.0000000 2014-10-12 15:59:00.0000000 4 20.00
2014-10-12 16:00:00.0000000 2014-10-12 23:59:00.0000000 4 5.00
2014-10-13 00:00:00.0000000 2014-10-13 00:20:00.0000000 4 10.00