I have a record set as under
AGREEMENTID FeedbackDate DispositionCode
0003SBML00151 2017-03-08 00:00:00.000 PTP
0004SBHL00705 2017-03-17 00:00:00.000 BPTP
0007SBML01987 NULL NULL
0026MSS00108 2017-05-20 00:00:00.000 PTP
0026MSS00108 2017-03-22 00:00:00.000 PTP
0026MSS00108 2016-12-30 00:00:00.000 BPTP
0026MSS00108 2016-12-29 00:00:00.000 BPTP
0026MSS00108 2016-12-28 00:00:00.000 BPTP
0037SBHL02361 NULL NULL
0038SBML00291 2017-05-04 00:00:00.000 PTP
0038SBML00291 2017-04-24 00:00:00.000 BPTP
0038SBML00291 2017-04-11 00:00:00.000 NC
0038SBML00291 2016-12-22 00:00:00.000 PTP
0038SBML00291 2016-12-09 00:00:00.000 DC
The desired output will be
AGREEMENTID L1 L2 L3 L4 L5
0003SBML00151 PTP NULL NULL NULL NULL
0004SBHL00705 BPTP NULL NULL NULL NULL
0007SBML01987 NULL NULL NULL NULL NULL
0026MSS00108 PTP PTP BPTP BPTP BPTP
0037SBHL02361 NULL NULL NULL NULL NULL
0038SBML00291 PTP BPTP NC PTP DC
SQL Schema
Declare #T table(AGREEMENTID varchar(50),FeedbackDate varchar(50),DispositionCode varchar(10))
Insert into #T
Select '0003SBML00151','2017-03-08 00:00:00.000','PTP' union all
Select '0004SBHL00705','2017-03-17 00:00:00.000','BPTP' union all
Select '0007SBML01987',NULL,NULL union all
Select '0026MSS00108','2017-05-20 00:00:00.000','PTP' union all
Select '0026MSS00108','2017-03-22 00:00:00.000','PTP' union all
Select '0026MSS00108','2016-12-30 00:00:00.000','BPTP' union all
Select '0026MSS00108','2016-12-29 00:00:00.000','BPTP' union all
Select '0026MSS00108','2016-12-28 00:00:00.000','BPTP' union all
Select '0037SBHL02361',NULL,NULL union all
Select '0038SBML00291','2017-05-04 00:00:00.000','PTP' union all
Select '0038SBML00291','2017-04-24 00:00:00.000','BPTP' union all
Select '0038SBML00291','2017-04-11 00:00:00.000','NC' union all
Select '0038SBML00291','2016-12-22 00:00:00.000','PTP' union all
Select '0038SBML00291','2016-12-09 00:00:00.000','DC'
Select *
From #T
Here is my attempt
;with cte1 as(
SELECT AGREEMENTID, abc = STUFF(
(SELECT '.' + DispositionCode
FROM #T t1
WHERE t1.AGREEMENTID = t2.AGREEMENTID --and t1.Rn = t2.Rn
FOR XML PATH (''))
, 1, 1, '') from #T t2
group by AGREEMENTID)
--select *
--from cte1
,cte2 as(
select AGREEMENTID,
X= IIF(charindex('.',abc,1) is null,'NULL VALUE',IIF(charindex('.',abc,1) = 0,'SINGLE VALUE','MULTIPLE VALUE'))
--,COL1 = SUBSTRING(abc,1,IIF(charindex('.',abc,1) = NULL,0,IIF(charindex('.',abc,1) = 0,len(abc),(charindex('.',abc,1)-1))))
--,charindex('.',abc,1)
,abc
from cte1)
,cte3 as(
select
AGREEMENTID
,COL1 =IIF(X = 'NULL VALUE', NULL,IIF(X='SINGLE VALUE',SUBSTRING(abc,1,len(abc)),SUBSTRING(abc,1,(charindex('.',abc,1)-1))))
,abc
,OtherCols = IIF(X = 'MULTIPLE VALUE',SUBSTRING(abc,charindex('.',abc,1)+1,len(abc)),'')
from cte2)
select
AGREEMENTID
,L1 = IIF(COL1 is null, '--',COL1)
,l2 = IIF(PARSENAME(OtherCols,4)is null, '--',PARSENAME(OtherCols,4))
,l3 = IIF(PARSENAME(OtherCols,3)is null, '--',PARSENAME(OtherCols,3))
,l4 = IIF(PARSENAME(OtherCols,2)is null, '--',PARSENAME(OtherCols,2))
,l5 = IIF(PARSENAME(OtherCols,1)is null, '--',PARSENAME(OtherCols,1))
From cte3
Disadvantages
a) Slow query
b) Failed for the below case
AGREEMENTID FeedbackDate DispositionCode
0002SBML01241 2017-05-04 00:00:00.000 Today
0002SBML01241 2017-04-24 00:00:00.000 PTP
0002SBML01241 2017-04-11 00:00:00.000 PTP
0002SBML01241 2016-12-22 00:00:00.000 PTP
Actual
AGREEMENTID L1 l2 l3 l4 l5
0002SBML01241 Today -- PTP PTP PTP
Expected
AGREEMENTID L1 l2 l3 l4 l5
0002SBML01241 Today PTP PTP PTP --
This will help in case for dynamic levels:
Declare #T table(AGREEMENTID varchar(50),FeedbackDate varchar(50),DispositionCode varchar(10))
Insert into #T
Select '0003SBML00151','2017-03-08 00:00:00.000','PTP' union all
Select '0004SBHL00705','2017-03-17 00:00:00.000','BPTP' union all
Select '0007SBML01987',NULL,NULL union all
Select '0026MSS00108','2017-05-20 00:00:00.000','PTP' union all
Select '0026MSS00108','2017-03-22 00:00:00.000','PTP' union all
Select '0026MSS00108','2016-12-30 00:00:00.000','BPTP' union all
Select '0026MSS00108','2016-12-29 00:00:00.000','BPTP' union all
Select '0026MSS00108','2016-12-28 00:00:00.000','BPTP' union all
Select '0037SBHL02361',NULL,NULL union all
Select '0038SBML00291','2017-05-04 00:00:00.000','PTP' union all
Select '0038SBML00291','2017-04-24 00:00:00.000','BPTP' union all
Select '0038SBML00291','2017-04-11 00:00:00.000','NC' union all
Select '0038SBML00291','2016-12-22 00:00:00.000','PTP' union all
Select '0038SBML00291','2016-12-09 00:00:00.000','DC'
Select 'L'+convert(varchar(255),ROW_NUMBER()over(partition by AGREEMENTID order by AGREEMENTID))'rno',*
into test
From #T order by AGREEMENTID
declare #Levels nvarchar(max),#SQL NVARCHAR(MAX)
select #Levels= STUFF((select DISTINCT ','+rno from test
for xml path('')),1,1,'')
SET #SQL=
'SELECT AGREEMENTID,'+#Levels+'
FROM (select AGREEMENTID,DispositionCode,rno from test
)as TEMP'+'
PIVOT'+'
(MAX(DispositionCode)
FOR rno IN ('+#Levels+')
)AS pvt'
exec sp_executesql #SQL
Sample data to get the result
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL
DROP TABLE #Temp
Declare #T table
(AGREEMENTID varchar(50),
FeedbackDate varchar(50),
DispositionCode varchar(10)
)
Insert into #T
SELECT '0003SBML00151','2017-03-08 00:00:00.000','PTP' union all
SELECT '0004SBHL00705','2017-03-17 00:00:00.000','BPTP' union all
SELECT '0007SBML01987',NULL,NULL union all
SELECT '0026MSS00108','2017-05-20 00:00:00.000','PTP' union all
SELECT '0026MSS00108','2017-03-22 00:00:00.000','PTP' union all
SELECT '0026MSS00108','2016-12-30 00:00:00.000','BPTP' union all
SELECT '0026MSS00108','2016-12-29 00:00:00.000','BPTP' union all
SELECT '0026MSS00108','2016-12-28 00:00:00.000','BPTP' union all
SELECT '0037SBHL02361',NULL,NULL union all
SELECT '0038SBML00291','2017-05-04 00:00:00.000','PTP' union all
SELECT '0038SBML00291','2017-04-24 00:00:00.000','BPTP' union all
SELECT '0038SBML00291','2017-04-11 00:00:00.000','NC' union all
SELECT '0038SBML00291','2016-12-22 00:00:00.000','PTP' union all
SELECT '0038SBML00291','2016-12-09 00:00:00.000','DC'
SELECT *,
'L'
+ Cast(Row_number()OVER(partition BY dispositioncode ORDER BY agreementid
)AS
VARCHAR(10)) AS Lcolumn
INTO #temp
FROM #T
Dynamic sql process to get the result
DECLARE #DyColumn NVARCHAR(max), #DyColumn2 NVARCHAR(max),
#Sql NVARCHAR(max)
SELECT #DyColumn = STUFF((SELECT DISTINCT ', ' + QUOTENAME(lcolumn)
FROM #temp
FOR xml path ('')), 1, 2, '')
SELECT #DyColumn2 = STUFF((SELECT DISTINCT ', ' + 'ISNULL('+QUOTENAME(lcolumn) +','+'''0'''+') AS '++QUOTENAME(lcolumn)
FROM #temp
FOR xml path ('')), 1, 2, '')
SET #Sql=N' SELECT AGREEMENTID,' + #DyColumn2
+ ' FROM ( SELECT * FROM #Temp ) AS Src PIVOT ( MIN(DispositionCode) FOR Lcolumn IN ('
+ #DyColumn + ') )AS Pvt '
PRINT #Sql
EXEC (#Sql)
I'm able to achieve the desired output, considering you're not going to have more Levels than [L5] in that case you need to have dynamic PIVOT solution.
For Dynamic Pivot solution you can check Krishnaraj's answer.
[Live Demo]
SELECT *
FROM
(
Select AGREEMENTID,
DispositionCode,
'L' + CAST(ROW_NUMBER() OVER(Partition By AGREEMENTID ORDER BY FeedbackDate DESC) AS VARCHAR(255)) as RowNum
From #T
) as PivotSource
PIVOT
(
MAX(DispositionCode) FOR RowNum IN ([L1], [L2], [L3], [L4], [L5])
) as Pvt;
Related
I have the following sample data:
--Table 1:
CREATE TABLE tbl_Emp_1
(
EmpID INT,
ColDate DATE
);
INSERT INTO tbl_Emp_1 VALUES(1,'2019-11-01');
INSERT INTO tbl_Emp_1 VALUES(2,'2019-11-02');
INSERT INTO tbl_Emp_1 VALUES(3,'2019-11-11');
INSERT INTO tbl_Emp_1 VALUES(4,'2019-11-12');
INSERT INTO tbl_Emp_1 VALUES(9,'2019-11-13');
INSERT INTO tbl_Emp_1 VALUES(6,'2019-11-16');
INSERT INTO tbl_Emp_1 VALUES(408,'2019-11-25');
--Table 2:
CREATE TABLE tbl_Emp_2
(
EmpID INT,
ColDate DATE
);
INSERT INTO tbl_Emp_2 VALUES(11,'2019-11-02');
INSERT INTO tbl_Emp_2 VALUES(22,'2019-11-06');
INSERT INTO tbl_Emp_2 VALUES(22,'2019-11-08');
INSERT INTO tbl_Emp_2 VALUES(33,'2019-11-10');
INSERT INTO tbl_Emp_2 VALUES(44,'2019-11-15');
--Table 3:
CREATE TABLE tbl_Emp_3
(
EmpID INT,
ColDate DATE
);
INSERT INTO tbl_Emp_3 VALUES(111,'2019-11-12');
INSERT INTO tbl_Emp_3 VALUES(222,'2019-11-16');
INSERT INTO tbl_Emp_3 VALUES(333,'2019-11-17');
INSERT INTO tbl_Emp_3 VALUES(444,'2019-11-19');
INSERT INTO tbl_Emp_3 VALUES(5,'2019-11-22');
--Now I will create View of these tables.
CREATE VIEW vw_Emp AS
SELECT *,1 AS TableID FROM tbl_Emp_1
UNION ALL
SELECT *,2 AS TableID FROM tbl_Emp_2
UNION ALL
SELECT *,3 AS TableID FROM tbl_Emp_3;
Expected Output:
EmpID ColDate
--------------------------------
2 2019-11-02 ---TABLE 1 Starts
NULL 2019-11-03 - 2019-11-10
3 2019-11-11
9 2019-11-13
NULL 2019-11-14 - 2019-11-15
6 2019-11-16
NULL 2019-11-17 - 2019-11-24
408 2019-11-25
11 2019-11-02 ---TABLE 2 Data Starts
NULL 2019-11-03 - 2019-11-05
22 2019-11-06
NULL 2019-11-07
22 2019-11-08
NULL 2019-11-09
33 2019-11-10
NULL 2019-11-11 - 2019-11-14
44 2019-11-15
111 2019-11-12 ---TABLE 3 Data Starts
NULL 2019-11-13 - 2019-11-15
222 2019-11-16
333 2019-11-17
NULL 2019-11-18
444 2019-11-19
NULL 2019-11-20 - 2019-11-21
5 2019-11-22
About the output: Display absent dates of Employee and display emp data of one date before and after those dates(employee not exists dates).
My try:
DECLARE #TableID INT,
#MinDate DATE,
#MaxDate DATE;
DECLARE Cur_Get_MinMax1 CURSOR FOR
SELECT TableID,
(SELECT MIN(ColDate) FROM vw_Emp WHERE TableID = v1.TableID),
(SELECT MAX(ColDate) FROM vw_Emp WHERE TableID = v1.TableID)
FROM vw_Emp v1
GROUP BY TableID;
IF OBJECT_ID('tempdb..#TempEmpData') IS NOT NULL
DROP TABLE #TempEmpData;
CREATE TABLE #TempEmpData
(
Dates DATE,
TableID int
);
OPEN Cur_Get_MinMax1;
FETCH NEXT FROM Cur_Get_MinMax1 INTO
#TableID,
#MinDate,
#MaxDate;
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT(#TableID);
PRINT(#MinDate);
PRINT(#MaxDate);
INSERT INTO #TempEmpData
SELECT TOP (DATEDIFF(DAY, #MinDate, #MaxDate) + 1)
Date = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, #MinDate),
#TableID
FROM sys.all_objects a
CROSS JOIN sys.all_objects b
FETCH NEXT FROM Cur_Get_MinMax1 INTO
#TableID,
#MinDate,
#MaxDate;
END;
CLOSE Cur_Get_MinMax1;
DEALLOCATE Cur_Get_MinMax1;
Query 1:
SELECT v.EmpID,t.Dates
FROM #TempEmpData t
LEFT JOIN vw_Emp v ON v.ColDate = t.Dates AND v.TableID = t.TableID
ORDER BY t.TableID,t.Dates;
Edit:
Query 2:
;WITH CTE AS
(
SELECT DISTINCT TableID,Dates,EmpID,
coalesce(stuff((select distinct CAST(MIN(Dates) as varchar(10))+'~'+ CAST(MAX(Dates) as varchar(10)) from #TempEmpData t1 where a.rr = 1 AND t1.Dates=a.Dates for xml path('')),1,0,''),cast(Dates as varchar(10))) Coldate
FROM
(
SELECT v.EmpID,
t.Dates,
t.TableID,
RANK() OVER(ORDER BY v.EmpID) rr
FROM vw_Emp v
RIGHT JOIN #TempEmpData t ON v.ColDate = t.Dates AND v.TableID = t.TableID
GROUP BY t.TableID,v.EmpID,t.Dates,v.TableID
) a
)
SELECT EmpID,ColDate
FROM CTE
ORDER BY TableID,Dates
this uses window function LAG() and LEAD() to find previous and next ColDate based on ColDate ordering.
The first query returns the before and after row when a discontinued date is encounter. The second query returns the date range of the discontinued date.
; with
tbl_Emp as
(
select tbl = 1, EmpID, ColDate from tbl_Emp_1
union all
select tbl = 2, EmpID, ColDate from tbl_Emp_2
union all
select tbl = 3, EmpID, ColDate from tbl_Emp_3
),
cte as
(
select *,
prevColDate = LAG(ColDate) over (partition by tbl order by ColDate),
nextColDate = LEAD(ColDate) over (partition by tbl order by ColDate)
from tbl_Emp
)
-- first query
select c.tbl,
c.EmpID,
c.ColDate,
EndDate = NULL
from cte c
where c.ColDate <> dateadd(day, +1, prevColDate)
or c.ColDate <> dateadd(day, -1, nextColDate)
union all
-- second query
select c.tbl,
EmpID = NULL,
ColDate = dateadd(day, 1, c.ColDate),
EndDate = dateadd(day, -1, nextColDate)
from cte c
where c.ColDate <> dateadd(day, -1, nextColDate)
order by tbl, ColDate;
Note : i didn't concatenate the ColDate and EndDate as what you have shown in your expected result.
SELECT CAST(NULL AS INT) AS EmpId, DATEADD(day, 1, PreviousDate) AS StartDate, DATEADD(day, -1, ColDate) AS EndDate
FROM
(
SELECT ColDate, LAG(ColDate) OVER(ORDER BY ColDate) AS PreviousDate, LEAD(ColDate) OVER(ORDER BY ColDate) AS NextDate
FROM (SELECT DISTINCT ColDate FROM dbo.tbl_Emp_1) AS src
) AS thedates
WHERE ColDate <> DATEADD(day, 1, PreviousDate)
SELECT CAST(NULL AS INT) AS EmpId, StartDate, EndDate
FROM
(
SELECT DATEADD(day, 1, sd.StartDate) AS StartDate, DATEADD(day, -1, MIN(ed.EndDate)) AS EndDate
FROM
(
--start dates of missing ranges
SELECT ColDate AS StartDate
FROM dbo.tbl_Emp_1 as a
WHERE NOT EXISTS(SELECT * FROM dbo.tbl_Emp_1 AS b WHERE b.ColDate = DATEADD(day, 1, a.ColDate))
) AS sd
JOIN
(
--end dates of missing ranges
SELECT ColDate AS EndDate
FROM dbo.tbl_Emp_1 as a
WHERE NOT EXISTS(SELECT * FROM dbo.tbl_Emp_1 AS b WHERE b.ColDate = DATEADD(day, -1, a.ColDate))
) AS ed ON sd.StartDate < ed.EndDate
GROUP BY sd.StartDate
) AS emptyperiods
Here is my ask:
Go through the code and understand it.
As first solution, query should complete within 10 secs for 30 input
It should be working with good performance for 100 input as well.
My code:
/**************************************************
Populating the Array values in table variable
**************************************************/
DECLARE #PUZZLE table(
ID int IDENTITY(1,1) NOT NULL,
Value int NOT NULL)
/****Sample 1*****/
INSERT INTO #PUZZLE (value)
--SELECT 0 UNION ALL
--SELECT -22 UNION ALL
--SELECT -33 UNION ALL
--SELECT -44 UNION ALL
--SELECT 55 UNION ALL
--SELECT -100 UNION ALL
--SELECT 100 UNION ALL
--SELECT 10 UNION ALL
--SELECT -30 UNION ALL
--SELECT -60 UNION ALL
--SELECT -60 UNION ALL
SELECT -60 UNION ALL
SELECT -10 UNION ALL
SELECT 10 UNION ALL
SELECT 10 UNION ALL
SELECT -10 UNION ALL
SELECT 0 UNION ALL
SELECT -22 UNION ALL
SELECT -33 UNION ALL
SELECT -44 UNION ALL
SELECT 55 UNION ALL
SELECT -100 UNION ALL
SELECT 100 UNION ALL
SELECT 10 UNION ALL
SELECT -30 UNION ALL
SELECT -60 UNION ALL
SELECT -60 UNION ALL
SELECT -60 UNION ALL
SELECT -10 UNION ALL
SELECT 10 UNION ALL
SELECT 10
/**************************************************
Populating possible hierarchy/path
**************************************************/
DECLARE #puzHierarchy table (parentid int, childid int,value int)
INSERT #puzHierarchy (parentid,childid,value)
SELECT *-- INTO #puzHierarchy
FROM (
SELECT NULL AS ParentId,ID AS ChildId, Value
FROM #PUZZLE
WHERE ID = (SELECT MIN(ID) FROM #PUZZLE)
UNION ALL
SELECT B.Id,C.ID,C.Value
FROM #PUZZLE B
JOIN #PUZZLE C
ON C.ID > B.ID AND C.ID < (B.ID + 7)
) A
--SELECT * FROM #puzHierarchy order by parentid
/*******************************************************
Logic using recursive CTE to get the path with max value
*******************************************************/
;WITH children AS
(
SELECT ParentId
,CAST(ISNULL(CAST(ParentId AS NVARCHAR) + '->' ,'') + CAST(ChildId AS NVARCHAR) AS NVARCHAR(Max)) AS Path
,value As PathValue
FROM #puzHierarchy
WHERE ChildId = (SELECT MAX(ChildId) FROM #puzHierarchy)
UNION ALL
SELECT t.ParentId
,list= CAST(ISNULL(CAST(t.ParentId AS NVARCHAR) + '->' ,'') + d.Path AS NVARCHAR(Max))
,(t.value+d.PathValue) As PathValue
FROM #puzHierarchy t
INNER JOIN children AS d
ON t.ChildId = d.ParentId
)
SELECT [Path],PathValue
FROM children c
WHERE ParentId IS NULL
AND c.PathValue = (SELECT max(PathValue) FROM children WHERE ParentId IS NULL)
A. Your code goes through too many cycles/data unrelated to result you want.
B. After running your sample data, the results are not accurate.
Parentid Path PathValue
NULL 1->3->4->6->10->12->13->19->20 145
NULL 1->3->4->10->12->13->19->20 145
The first result is wrong.
Basically you just want starting from ParentId IS NULL and ChildId = 1, among ParentId = 1 finding which ChildId has the MAX value, this ChildId becomes ParentID to find next MAX value, and so on.
;WITH cte_base AS (SELECT Parentid
, Childid
, Value
, ROW_NUMBER() OVER(PARTITION BY Parentid ORDER BY Value DESC) AS Rownum
FROM #puzHierarchy
), cte_re AS (SELECT ParentId
, Childid
, CAST(CAST(ChildId AS NVARCHAR) AS NVARCHAR(Max)) AS Path
, Value As PathValue
, Rownum
FROM cte_base
WHERE Parentid IS NULL
UNION ALL
SELECT b.parentid, b.childid
, CAST(Path + '->' + ISNULL(CAST(b.parentid AS NVARCHAR) ,'') AS NVARCHAR(Max))
,(b.value + r.PathValue) As PathValue
, b.Rownum
FROM cte_base AS b
INNER JOIN cte_re AS r
ON b.Parentid = r.childid
where b.Rownum = 1
)
SELECT *
FROM cte_re
(I changed your sample table variable to a temporary table.)
I Would like the first date of each group to repeat for the rest of the rows withing each group
You could use window expressions and grouping;
FIRST_VALUE (Transact-SQL)
You would need to partition by your first column. to get the split of A and B.
For example;
with cteTempData
(
[Code]
, [Date]
)
as
(
select 'A',cast('2015-9-4' as date)
union all select 'A','2015-9-4'
union all select 'A','2015-9-4'
union all select 'A','2015-9-16'
union all select 'B','2015-9-16'
union all select 'B','2015-9-22'
union all select 'B','2015-9-22'
union all select 'B','2015-10-26'
union all select 'B','2015-10-30'
)
select
[Code]
, [Date]
, FIRST_VALUE([Date]) over (partition by [Code] order by [Date]) as [First_Date]
from cteTempData
Using the first_value syntax also allows you to work with other columns in that ordered record....
with cteTempData
(
[Code]
, [Date]
, [Comment]
)
as
(
select 'A',cast('2015-9-4' as date),'One'
union all select 'A','2015-9-4','Two'
union all select 'A','2015-9-4','Three'
union all select 'A','2015-9-16','Four'
union all select 'B','2015-9-16','Five'
union all select 'B','2015-9-22','Six'
union all select 'B','2015-9-22','Seven'
union all select 'B','2015-10-26','Eight'
union all select 'B','2015-10-30','Nine'
)
select
[Code]
, [Date]
, FIRST_VALUE([Date]) over (partition by [Code] order by [Date]) as [First_Date]
, FIRST_VALUE([Comment]) over (partition by [Code] order by [Date]) as [First_Comment]
from cteTempData
Use MIN() Over ()
Declare #Table table (Grp varchar(25),Date date)
Insert into #Table values
('A','2015-09-04'),
('A','2015-09-05'),
('A','2015-09-10'),
('B','2015-10-04'),
('B','2015-10-05'),
('B','2015-10-10')
Select *
,GrpDate = min(Date) over (Partition By Grp)
From #Table
Returns
Grp Date GrpDate
A 2015-09-04 2015-09-04
A 2015-09-05 2015-09-04
A 2015-09-10 2015-09-04
B 2015-10-04 2015-10-04
B 2015-10-05 2015-10-04
B 2015-10-10 2015-10-04
You could use MIN with the OVER-clause
SELECT t.ColumnA,
DateCol = MIN( t.DateCol ) OVER ( PARTITION BY t.ColumnA ),
OtherColumns
FROM dbo.TableName t
you can go with a CROSS JOIN or FIRST_VALUE.
Declare #Yourtable table (groupCol varchar(25),firstDate date)
Insert into #Yourtable values
('A','2015-09-04'),
('A','2015-09-05'),
('A','2015-09-10'),
('B','2015-10-04'),
('B','2015-10-05'),
('B','2015-10-10')
SELECT a.*,b.firstDate
FROM #Yourtable a
CROSS JOIN (SELECT groupCol,MIN(firstDate) firstDate
FROM #Yourtable b
GROUP BY groupCol)b
WHERE a.groupCol =b.groupCol
OR
SELECT a.*,FIRST_VALUE(a.firstDate) OVER (PARTITION BY groupCol ORDER BY groupCol ASC) AS firstDate
FROM #Yourtable a
I have these three tables .
CREATE TABLE Employees (
EmpID INT IDENTITY,
EmpName VARCHAR(100)
)
GO
INSERT INTO Employees(EmpName)
SELECT 'John Torres' UNION ALL
SELECT 'Irina Williams'
SELECT * FROM Employees
GO
CREATE TABLE PayrollWeek(
WeekID INT IDENTITY,
EmpID INT,
WeekStart DATETIME,
WeekEnd DATETIME
)
GO
INSERT INTO PayrollWeek(EmpID,WeekStart,WeekEnd)
SELECT 1,'11-20-2011','11-26-2011' UNION ALL
SELECT 2,'11-27-2011','12-03-2011' UNION ALL
SELECT 1,'11-27-2011','12-03-2011'
SELECT * FROM PayrollWeek
GO
CREATE TABLE EmployeeVisits(
ID INT,
EmpID INT,
VisitDate DATETIME,
StartTime VARCHAR(5),
EndTime VARCHAR(5),
EarningCode VARCHAR(100)
)
GO
INSERT INTO EmployeeVisits(ID,EmpID,VisitDate,StartTime,EndTime,EarningCode)
SELECT 1,1,'11-20-2011','10:00','12:00','Sat-Sun1' UNION ALL
SELECT 2,1,'11-21-2011','13:30','16:00','Mon-Fri1' UNION ALL
SELECT 3,1,'11-22-2011','14:00','15:00','Mon-Fri1' UNION ALL
SELECT 4,1,'11-24-2011','10:00','14:00','Mon-Fri1' UNION ALL
SELECT 5,1,'11-25-2011','13:30','16:00','Mon-Fri1' UNION ALL
SELECT 6,1,'11-26-2011','14:00','15:00','Sat-Sun1' UNION ALL
SELECT 7,2,'11-27-2011','09:00','11:00','Sat-Sun1' UNION ALL
SELECT 8,2,'11-28-2011','07:00','12:00','Mon-Fri1' UNION ALL
SELECT 9,2,'11-29-2011','09:00','11:00','Mon-Fri1' UNION ALL
SELECT 10,2,'12-03-2011','07:00','12:00','Sat-Sun1'
Expected Result is this
RecordType EmpId EmpName WeekStart Weekend EarningCode Hour
H 1 John Torres 11/20/2011 11/26/2011
D Sat-Sun1 3
D Mon-Fri1 10
H 2 Irina Williams 11/27/2011 12/3/2011
D Sat-Sun1 7
D Mon-Fri1 7
Here is my answer I try this using Union, Group By .
SELECT 'H' AS RecordType
,emp.EmpID
,EmpName
,Cast(MIN(WeekStart) AS VARCHAR(106)) AS StartTime
,cast(Max(VisitDate) AS VARCHAR(106)) AS EndTime
,'' AS EarningCode
,'' AS TimeDiff
FROM EmployeeVisits E
JOIN PayrollWeek P ON e.EmpID = P.EmpID
JOIN Employees Emp ON Emp.EmpID = e.EmpID
GROUP BY emp.EmpID
,EmpName
UNION
SELECT 'D' AS RecordType
,EmpID
,'' AS EmpName
,'' AS StartTime
,'' AS EndTime
,EarningCode
,cast(sum(dateDiff(MINUTE, StartTime, EndTime)) / 60 AS VARCHAR(100)) AS TimeDiff
FROM EmployeeVisits
GROUP BY EmpID
,EarningCode
ORDER BY EmpID
,RecordType DESC
I am using SQL Server 2008. I have data by each employee for each day. Below is the sample data.
WITH RawData as
(
SELECT '10001' AS EmpNo,'2015-01-01' as AttendanceDate,'FS' AS ShiftCode UNION
SELECT '10001','2015-01-02','WO' UNION
SELECT '10001','2015-01-03','FS' UNION
SELECT '10001','2015-01-04','FS' UNION
SELECT '10001','2015-01-05','FS' UNION
SELECT '10001','2015-01-06','FS' UNION
SELECT '10001','2015-01-07','FS' UNION
SELECT '10001','2015-01-08','FS' UNION
SELECT '10001','2015-01-09','WO' UNION
SELECT '10001','2015-01-10','FS' UNION
SELECT '10001','2015-01-11','FS' UNION
SELECT '10001','2015-01-12','FS' UNION
SELECT '10001','2015-01-13','FS' UNION
SELECT '10001','2015-01-14','FS' UNION
SELECT '10001','2015-01-15','FS' UNION
SELECT '10001','2015-01-16','WO' UNION
SELECT '10001','2015-01-17','FS' UNION
SELECT '10001','2015-01-18','FS' UNION
SELECT '10001','2015-01-19','FS' UNION
SELECT '10001','2015-01-20','FS' UNION
SELECT '10001','2015-01-21','FS' UNION
SELECT '10001','2015-01-22','FS' UNION
SELECT '10001','2015-01-23','WO' UNION
SELECT '10001','2015-01-24','FS' UNION
SELECT '10001','2015-01-25','FS' UNION
SELECT '10001','2015-01-26','FS' UNION
SELECT '10001','2015-01-27','FS' UNION
SELECT '10001','2015-01-28','FS' UNION
SELECT '10001','2015-01-29','FS' UNION
SELECT '10001','2015-01-30','WO' UNION
SELECT '10001','2015-01-31','FS' UNION
SELECT '10002','2015-01-01','FS' UNION
SELECT '10002','2015-01-02','WO' UNION
SELECT '10002','2015-01-03','WO' UNION
SELECT '10002','2015-01-04','FS' UNION
SELECT '10002','2015-01-05','FS' UNION
SELECT '10002','2015-01-06','FS' UNION
SELECT '10002','2015-01-07','FS' UNION
SELECT '10002','2015-01-08','FS' UNION
SELECT '10002','2015-01-09','WO' UNION
SELECT '10002','2015-01-10','WO' UNION
SELECT '10002','2015-01-11','FS' UNION
SELECT '10002','2015-01-12','FS' UNION
SELECT '10002','2015-01-13','FS' UNION
SELECT '10002','2015-01-14','FS' UNION
SELECT '10002','2015-01-15','FS' UNION
SELECT '10002','2015-01-16','WO' UNION
SELECT '10002','2015-01-17','WO' UNION
SELECT '10002','2015-01-18','FS' UNION
SELECT '10002','2015-01-19','FS' UNION
SELECT '10002','2015-01-20','FS' UNION
SELECT '10002','2015-01-21','FS' UNION
SELECT '10002','2015-01-22','FS' UNION
SELECT '10002','2015-01-23','WO' UNION
SELECT '10002','2015-01-24','WO' UNION
SELECT '10002','2015-01-25','FS' UNION
SELECT '10002','2015-01-26','FS' UNION
SELECT '10002','2015-01-27','FS' UNION
SELECT '10002','2015-01-28','FS' UNION
SELECT '10002','2015-01-29','FS' UNION
SELECT '10002','2015-01-30','WO' UNION
SELECT '10002','2015-01-31','WO')
SELECT * FROM RawData Order By EmpNo,AttendanceDate
How to write SQL Query to get following output based on this sample data ? The workweek of each employee starts on a Day after weekly off and it can be any day (mon, tue etc). The shift code denotes WO: weekly off, FS: First Shift, SS: Second Shift.
EmpNo WeekFrom WeekTo
10001 2015-01-01 2015-01-02
10001 2015-01-03 2015-01-09
10001 2015-01-10 2015-01-16
10001 2015-01-17 2015-01-23
10001 2015-01-24 2015-01-30
10001 2015-01-31 2015-01-31
10002 2015-01-01 2015-01-03
10002 2015-01-04 2015-01-10
10002 2015-01-11 2015-01-17
10002 2015-01-18 2015-01-24
10002 2015-01-25 2015-01-31
Got a solution. But its taking quite a long time on live table with 1 Million rows. Have I done something wrong in a query ? Or there is a better way of doing this.
WITH RawData as
(
-- Insert above data here.
)
,ProcessData AS (
SELECT EmpNo,AttendanceDate,ShiftCode,RowID = ROW_NUMBER() OVER (
ORDER BY EmpNo, AttendanceDate
), WeekNo = 1 FROM RawData
)
,FinalData
AS (
SELECT EmpNo, AttendanceDate, ShiftCode, RowID, WeekNo = 1
FROM ProcessData DA
WHERE RowID = 1
UNION ALL
SELECT DA.EmpNo, DA.AttendanceDate, DA.ShiftCode, DA.RowID,
WeekNo = (CASE WHEN FinalData.EmpNo != DA.EmpNo THEN 1 ELSE FinalData.WeekNo + (CASE WHEN (FinalData.ShiftCode = 'WO' AND DA.ShiftCode != 'WO') THEN 1 ELSE 0 END) END)
FROM FinalData
INNER JOIN ProcessData DA ON DA.RowID = FinalData.RowID + 1
)
SELECT EmpNo, MIN(AttendanceDate) AS StartDate, MAX(AttendanceDate) AS EndDate, WeekNo
FROM FinalData
GROUP BY EmpNo, WeekNo
ORDER BY EmpNo, WeekNo
Try this:
SQL Fiddle
;WITH RawData AS (
-- Your insert statements here
),
Cte AS(
SELECT *,
RN = ROW_NUMBER() OVER(PARTITION BY EmpNo, grp ORDER BY AttendanceDate DESC)
FROM (
SELECT *,
grp = DATEADD(DAY, -ROW_NUMBER() OVER(PARTITION BY EmpNo ORDER BY AttendanceDate), AttendanceDate)
FROM RawData
WHERE ShiftCode = 'WO'
)t
),
CteWeekOff AS(
SELECT EmpNo, AttendanceDate, ShiftCode FROM cte WHERE RN = 1
),
CteFinal AS(
SELECT
EmpNo,
WeekFrom = MIN(AttendanceDate),
Weekto = MAX(AttendanceDate)
FROM (
SELECT *,
grp = DATEADD(DAY, - ROW_NUMBER() OVER(PARTITION BY EmpNo ORDER BY AttendanceDate), AttendanceDate)
FROM RawData
WHERE ShiftCode <> 'WO'
)t
GROUP BY EmpNo, grp
)
SELECT
EmpNo,
WeekFrom = x.WeekFrom,
WeekTo = w.AttendanceDate
FROM CteWeekOff w
CROSS APPLY(
SELECT TOP 1 WeekFrom
FROM CteFinal r
WHERE
r.EmpNo = w.EmpNo
AND r.WeekFrom <= w.AttendanceDate
ORDER BY r.WeekFrom DESC
)x(WeekFrom)
UNION ALL
SELECT
EmpNo,
WeekFrom = x.WeekFrom,
WeekTo = t.AttendanceDate
FROM (
SELECT *, RN = ROW_NUMBER() OVER(PARTITION BY EmpNo ORDER BY AttendanceDate DESC)
FROM RawData
)t
CROSS APPLY(
SELECT TOP 1 AttendanceDate
FROM CteFinal r
WHERE
r.EmpNo = t.EmpNo
AND r.WeekFrom < t.AttendanceDate
ORDER BY r.WeekFrom DESC
)x(WeekFrom)
WHERE
RN = 1
AND ShiftCode <> 'WO'
ORDER BY EmpNo, WeekFrom
Finally this worked. 5 seconds on 230,000 records. I will go ahead with my solution. Thanks for your time. Hope this solution helps someone.
-- Step 1 : Save it to temp table
SELECT EmpNo,AttendanceDate,ShiftCode,RowID = ROW_NUMBER() OVER (
ORDER BY EmpNo, AttendanceDate
), WeekNo = 1 into #RawData FROM -- My table
-- Step 2 : Use temp table
;WITH FinalData
AS (
SELECT EmpNo, AttendanceDate, ShiftCode, RowID, WeekNo = 1
FROM #RawData DA
WHERE RowID = 1
UNION ALL
SELECT DA.EmpNo, DA.AttendanceDate, DA.ShiftCode, DA.RowID,
WeekNo = (CASE WHEN FinalData.EmpNo != DA.EmpNo THEN 1 ELSE FinalData.WeekNo + (CASE WHEN (FinalData.ShiftCode = 'WO' AND DA.ShiftCode != 'WO') THEN 1 ELSE 0 END) END)
FROM FinalData
INNER JOIN #RawData DA ON DA.RowID = FinalData.RowID + 1
)
SELECT EmpNo, MIN(AttendanceDate) AS StartDate, MAX(AttendanceDate) AS EndDate, WeekNo
FROM FinalData
GROUP BY EmpNo, WeekNo
ORDER BY EmpNo, WeekNo
OPTION (MAXRECURSION 0)