Related
I have table of products and their sales quantity in months.
Product Month Qty
A 2018-01-01 5
A 2018-02-01 3
A 2018-05-01 5
B 2018-08-01 10
B 2018-10-01 12
...
I'd like to first fill in the data gap between each product's min and max dates like below:
Product Month Qty
A 2018-01-01 5
A 2018-02-01 3
A 2018-03-01 0
A 2018-04-01 0
A 2018-05-01 5
B 2018-08-01 10
B 2018-09-01 0
B 2018-10-01 12
...
Then I would need to perform an accumulation of each product's sales quantity by month.
Product Month total_Qty
A 2018-01-01 5
A 2018-02-01 8
A 2018-03-01 8
A 2018-04-01 8
A 2018-05-01 13
B 2018-08-01 10
B 2018-09-01 10
B 2018-10-01 22
...
I fumbled over the "cross join" clause, however it seems to generate some unexpected results for me. Could someone help to give a hint how I can achieve this in SQL?
Thanks a lot in advance.
I think a recursive CTE is a simple way to do this. The code is just:
with cte as (
select product, min(mon) as mon, max(mon) as end_mon
from t
group by product
union all
select product, dateadd(month, 1, mon), end_mon
from cte
where mon < end_mon
)
select cte.product, cte.mon, coalesce(qty, 0) as qty
from cte left join
t
on t.product = cte.product and t.mon = cte.mon;
Here is a db<>fiddle.
Hi i think this example can help you and perform what you excepted :
CREATE TABLE #MyTable
(Product varchar(10),
ProductMonth DATETIME,
Qty int
);
GO
CREATE TABLE #MyTableTempDate
(
FullMonth DATETIME
);
GO
INSERT INTO #MyTable
SELECT 'A', '2019-01-01', 214
UNION
SELECT 'A', '2019-02-01', 4
UNION
SELECT 'A', '2019-03-01', 50
UNION
SELECT 'B', '2019-01-01', 214
UNION
SELECT 'B', '2019-02-01', 10
UNION
SELECT 'C', '2019-04-01', 150
INSERT INTO #MyTableTempDate
SELECT '2019-01-01'
UNION
SELECT '2019-02-01'
UNION
SELECT '2019-03-01'
UNION
SELECT '2019-04-01'
UNION
SELECT '2019-05-01'
UNION
SELECT '2019-06-01'
UNION
SELECT '2019-07-01';
------------- FOR NEWER SQL SERVER VERSION > 2005
WITH MyCTE AS
(
SELECT T.Product, T.ProductMonth AS 'MMonth', T.Qty
FROM #MyTable T
UNION
SELECT T.Product, TD.FullMonth AS 'MMonth', 0 AS 'Qty'
FROM #MyTable T, #MyTableTempDate TD
WHERE NOT EXISTS (SELECT 1 FROM #MyTable TT WHERE TT.Product = T.Product AND TD.FullMonth = TT.ProductMonth)
)
-- SELECT * FROM MyCTE;
SELECT Product, MMonth, Qty, SUM( Qty) OVER(PARTITION BY Product ORDER BY Product
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as 'TotalQty'
FROM MyCTE
ORDER BY Product, MMonth ASC;
DROP TABLE #MyTable
DROP TABLE #MyTableTempDate
I have other way to perform this in lower SQL Server Version (like 2005 and lower)
It's a SELECT on SELECT if it's your case let me know and i provide some other example.
You can create the months with a recursive CTE
DECLARE #MyTable TABLE
(
ProductID CHAR(1),
Date DATE,
Amount INT
)
INSERT INTO #MyTable
VALUES
('A','2018-01-01', 5),
('A','2018-02-01', 3),
('A','2018-05-01', 5),
('B','2018-08-01', 10),
('B','2018-10-01', 12)
DECLARE #StartDate DATE
DECLARE #EndDate DATE
SELECT #StartDate = MIN(Date), #EndDate = MAX(Date) FROM #MyTable
;WITH dates AS (
SELECT #StartDate AS Date
UNION ALL
SELECT DATEADD(Month, 1, Date)
FROM dates
WHERE Date < #EndDate
)
SELECT A.ProductID, d.Date, COALESCE(Amount,0) AS Amount, COALESCE(SUM(Amount) OVER(PARTITION BY A.ProductID ORDER BY A.ProductID, d.Date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW),0) AS Total
FROM
(
SELECT ProductID, MIN(date) as DateStart, MAX(date) as DateEnd
FROM #MyTable
GROUP BY ProductID -- As I read in your comments that you need different min and max dates per product
) A
JOIN dates d ON d.Date >= A.DateStart AND d.Date <= A.DateEnd
LEFT JOIN #MyTable T ON A.ProductID = T.ProductID AND T.Date = d.Date
ORDER BY A.ProductID, d.Date
Try this below
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL
DROP TABLE #Temp
;WITH CTE(Product,[Month],Qty)
AS
(
SELECT 'A','2018-01-01', 5 UNION ALL
SELECT 'A','2018-02-01', 3 UNION ALL
SELECT 'A','2018-05-01', 5 UNION ALL
SELECT 'B','2018-08-01', 10 UNION ALL
SELECT 'D','2018-10-01', 12
)
SELECT ct.Product,[MonthDays],ct.Qty
INTO #Temp
FROM
(
SELECT c.Product,[Month],
ISNULL(Qty,0) AS Qty
FROM CTE c
)ct
RIGHT JOIN
(
SELECT -- This code is to get month data
CONVERT(VARCHAR(10),'2018-'+ RIGHT('00'+CAST(MONTH(DATEADD(MM, s.number, CONVERT(DATETIME, 0)))AS VARCHAR),2) +'-01',120) AS [MonthDays]
FROM master.dbo.spt_values s
WHERE [type] = 'P' AND s.number BETWEEN 0 AND 11
)DT
ON dt.[MonthDays] = ct.[Month]
SELECT
MAX(Product)OVER(ORDER BY [MonthDays])AS Product,
[MonthDays],
ISNULL(Qty,0) Qty,
SUM(ISNULL(Qty,0))OVER(ORDER BY [MonthDays]) As SumQty
FROM #Temp
Result
Product MonthDays Qty SumQty
------------------------------
A 2018-01-01 5 5
A 2018-02-01 3 8
A 2018-03-01 0 8
A 2018-04-01 0 8
A 2018-05-01 5 13
A 2018-06-01 0 13
A 2018-07-01 0 13
B 2018-08-01 10 23
B 2018-09-01 0 23
D 2018-10-01 12 35
D 2018-11-01 0 35
D 2018-12-01 0 35
First of all, i would divide month and year to get easier with statistics.
I will give you an example query, not based on your table but still helpful.
--here i create the table that will be used as calendar
Create Table MA_MonthYears (
Month int not null ,
year int not null
PRIMARY KEY ( month, year) )
--/////////////////
-- here i'm creating a procedure to fill the ma_monthyears table
declare #month as int
declare #year as int
set #month = 1
set #year = 2015
while ( #year != 2099 )
begin
insert into MA_MonthYears(Month, year)
select #month, #year
if #month < 12
set #month=#month+1
else
set #month=1
if #month = 1
set #year = #year + 1
end
--/////////////////
--here you are the possible result you are looking for
select SUM(Ma_saledocdetail.taxableamount) as Sold, MA_MonthYears.month , MA_MonthYears.year , item
from MA_MonthYears left outer join MA_SaleDocDetail on year(MA_SaleDocDetail.DocumentDate) = MA_MonthYears.year
and Month(ma_saledocdetail.documentdate) = MA_MonthYears.Month
group by MA_SaleDocDetail.Item, MA_MonthYears.year , MA_MonthYears.month
order by MA_MonthYears.year , MA_MonthYears.month
How to do this please help.
I have the following data. This is a data sample for week. I want single row per EmployeeName / CrewID (unless there are 2 values for a Employee in a single date).
If ORIGINAL DATA is like this :
2018-08-05 2018-08-06 2018-08-07 2018-08-08 2018-08-09 2018-08-10 2018-08-11 CrewID EmployeeName
------------ ------------ ------------ ------------ ------------ ------------ ------------ -------- ----------------
NULL 174 173 172 171 NULL NULL 9 Kanhaiya
NULL NULL NULL NULL NULL 178 NULL 9 Kanhaiya
NULL 174 173 172 171 NULL NULL 8 Santanu Maulik
NULL NULL NULL NULL NULL 178 NULL 8 Santanu Maulik
The OUTPUT of the above original should be in single row per person as below.
2018-08-05 2018-08-06 2018-08-07 2018-08-08 2018-08-09 2018-08-10 2018-08-11 CrewID EmployeeName
------------ ------------ ------------ ------------ ------------ ------------ ------------ -------- ----------------
NULL 174 173 172 171 178 NULL 9 Kanhaiya
NULL 174 173 172 171 178 NULL 8 Santanu Maulik
but still the data may be spitted in multiple rows per employee for certain condition like if the employee has multiple values for a single date eg. '2018-08-10'
If ORIGINAL DATA is like this :
2018-08-05 2018-08-06 2018-08-07 2018-08-08 2018-08-09 2018-08-10 2018-08-11 CrewID EmployeeName
------------ ------------ ------------ ------------ ------------ ------------ ------------ -------- --------------
NULL 174 173 172 171 NULL NULL 9 Kanhaiya
NULL NULL NULL NULL 163 178 NULL 9 Kanhaiya
The OUTPUT of the above original data should be like below.
2018-08-05 2018-08-06 2018-08-07 2018-08-08 2018-08-09 2018-08-10 2018-08-11 CrewID EmployeeName
------------ ------------ ------------ ------------ ------------ ------------ ------------ -------- --------------
NULL 174 173 172 171 178 NULL 9 Kanhaiya
NULL NULL NULL NULL 163 NULL NULL 9 Kanhaiya
I have managed to get the data with a stored procedure.
This is the main logic section of the stored procedure:
ALTER PROCEDURE [dbo].[GetDataForCustomWeekViewReport]
#Week AS INT,
#typeOfData AS VARCHAR(10)
AS
BEGIN
DECLARE #weekAdjustAdd INT
SET NOCOUNT ON;
SET DATEFIRST 7;
SET #weekAdjustAdd = (#Week - 1) * 7
DECLARE #SQLQuery AS NVARCHAR(MAX)
DECLARE #PivotColumns AS NVARCHAR(MAX)
-----------------------------
-- Get unique values of pivot column
SELECT
#PivotColumns= COALESCE(#PivotColumns + ',','') + QUOTENAME(WORKDAYS)
FROM
(SELECT DISTINCT WORKDAYS
FROM
(SELECT
DATEADD(DAY, #weekAdjustAdd, CAST(DATEADD(WEEK, DATEDIFF(DAY, -1, GETDATE()) / 7, -1) AS date)) AS WORKDAYS
UNION
SELECT
DATEADD(DAY, #weekAdjustAdd, CAST(DATEADD(WEEK, DATEDIFF(DAY, -1, GETDATE()) / 7, 0) AS date)) AS WORKDAYS
UNION
SELECT
DATEADD(DAY, #weekAdjustAdd, CAST(DATEADD(WEEK, DATEDIFF(DAY, -1, GETDATE()) / 7, 1) AS date)) AS WORKDAYS
UNION
SELECT
DATEADD(DAY, #weekAdjustAdd, CAST(DATEADD(WEEK, DATEDIFF(DAY, -1, GETDATE()) / 7, 2) AS date)) AS WORKDAYS
UNION
SELECT
DATEADD(DAY, #weekAdjustAdd, CAST(DATEADD(WEEK, DATEDIFF(DAY, -1, GETDATE()) / 7, 3) AS date)) AS WORKDAYS
UNION
SELECT
DATEADD(DAY, #weekAdjustAdd, CAST(DATEADD(WEEK, DATEDIFF(DAY, -1, GETDATE()) / 7, 4) AS date)) AS WORKDAYS
UNION
SELECT
DATEADD(DAY, #weekAdjustAdd, CAST(DATEADD(WEEK, DATEDIFF(DAY, -1, GETDATE()) / 7, 5) AS date)) AS WORKDAYS
) i) AS PivotExample
IF (#typeOfData = 'f')
BEGIN
--Get unique values of pivot column
--Create the dynamic query with all the values for
--pivot column at runtime
--',JOBID, JobInfo,CrewID, EmployeeName,Color
-- ,JI.INumber + '' - '' + JI.ITitle AS JobInfo, M.Color,
SET #SQLQuery =
N'
DECLARE #Week AS int
DECLARE #weekAdjustAdd int
SET #Week=1
SET DATEFIRST 7;
SET #weekAdjustAdd = (#Week - 1) * 7
SELECT ' + #PivotColumns + ', CrewID, EmployeeName
FROM (
SELECT J.ID AS JOBID, C.ID AS CrewID ,c.CrewName AS EmployeeName , JI.ID AS JOBINSTANCE_ID,JI.WORKDAYS
FROM [dbo].[Job] J
LEFT JOIN [dbo].[Job_Instances] JI ON J.ID=JI.JOBID
LEFT JOIN [dbo].[Instance_Employee_Relation] IER ON JI.ID=IER.JobInstanceID
LEFT JOIN [dbo].[Crew] C ON C.Id = IER.EMPLOYEEID
INNER JOIN [dbo].[Manager] M ON M.Id = JI.ManagerID
INNER JOIN dbo.JobType JT ON JT.ID = JI.JobTypeID
WHERE ( C.EmployeeTypeID=1 OR C.EmployeeTypeID IS NULL) AND JI.TYPE = ''F'' AND JI.WORKDAYS BETWEEN DATEADD(DAY,'+CONVERT(VARCHAR(20) ,#weekAdjustAdd)+', CAST(DATEADD(WEEK, DATEDIFF(DAY, -1, GETDATE()) / 7, -1) AS date)) AND DATEADD(DAY,'+ CONVERT(VARCHAR(20) ,#weekAdjustAdd)+', CAST(DATEADD(WEEK, DATEDIFF(DAY, -1, GETDATE()) / 7, 5) AS date))
)i
PIVOT( SUM(JobInstance_ID)
FOR [WORKDAYS] IN (' + #PivotColumns + ')) AS P ORDER BY CASE WHEN EmployeeName IS NULL THEN 1 ELSE 0 END, EmployeeName'
--Execute dynamic query
EXEC sp_executesql #SQLQuery
END
Here is the main schema section for the related tables.
UPDATE
#Ven tried to help me out & did a good job but his answer is working if i have only 2 rows per employee but if i have more than 2 rows per employee his solution is not working.
Original Data
2018-08-05 2018-08-06 2018-08-07 2018-08-08 2018-08-09 2018-08-10 2018-08-11 CrewID EmployeeName
------------ ------------ ------------ ------------ ------------ ------------ ------------ -------- ----------------
NULL 174 173 172 171 NULL NULL 9 Kanhaiya
NULL NULL NULL NULL NULL 178 NULL 9 Kanhaiya
NULL NULL NULL NULL 183 182 NULL 8 Santanu Maulik
NULL NULL NULL NULL NULL 178 NULL 8 Santanu Maulik
NULL 174 173 172 171 NULL NULL 8 Santanu Maulik
Output with #Ven's Solution (did not work For CrewID 8 as he has 3 Rows but worked for CrewID 9 as he has 2 Rows.)
CrewID EmployeeName 2018-08-05 2018-08-06 2018-08-07 2018-08-08 2018-08-09 2018-08-10 2018-08-11
-------- ---------------- ------------ ------------ ------------ ------------ ------------ ------------ ------------
8 Santanu Maulik NULL NULL NULL NULL NULL 178 NULL
8 Santanu Maulik NULL NULL NULL NULL 183 182 NULL
8 Santanu Maulik NULL 174 173 172 171 NULL NULL
9 Kanhaiya NULL 174 173 172 171 178 NULL
Work this out in 3 different steps to satisfy requirements. There is no other dirty way of doing this :)
1) Get previous row and next row in table by self join (left join)
2) Case expression to get value from next row where value is null in first row and non-null
in second row
2) case Expression if both rows are equal then null value in second row
DECLARE #table TABLE (
id INT identity(1, 1)
,[2018-08-05] INT
,[2018-08-06] INT
,[2018-08-07] INT
,[2018-08-08] INT
,[2018-08-09] INT
,[2018-08-10] INT
,[2018-08-11] INT
,CrewID INT
,EmployeeName VARCHAR(20)
)
INSERT #table
------------ ------------ ------------ ------------ ------------ ------------ ------------ -------- ----------------
SELECT NULL
,174
,173
,172
,171
,NULL
,NULL
,9
,'Kanhaiya'
UNION ALL
SELECT NULL
,NULL
,NULL
,NULL
,163
,178
,NULL
,9
,'Kanhaiya'
UNION ALL
SELECT NULL
,174
,173
,172
,171
,NULL
,NULL
,8
,'Santanu Maulik'
UNION ALL
SELECT NULL
,NULL
,NULL
,NULL
,NULL
,178
,NULL
,8
,'Santanu Maulik';
Script:
WITH CTE
AS (
SELECT rownum = ROW_NUMBER() OVER (
PARTITION BY p.crewid ORDER BY p.crewID
)
,p.*
FROM #table p
)
,ct2
AS (
SELECT TOP 100 PERCENT cte.CrewID
,cte.employeename
,CASE WHEN cte.[2018-08-05] IS NULL THEN nex.[2018-08-05] ELSE cte.[2018-08-05] END [2018-08-05]
,CASE WHEN cte.[2018-08-06] IS NULL THEN nex.[2018-08-06] ELSE cte.[2018-08-06] END [2018-08-06]
,CASE WHEN cte.[2018-08-07] IS NULL THEN nex.[2018-08-07] ELSE cte.[2018-08-07] END [2018-08-07]
,CASE WHEN cte.[2018-08-08] IS NULL THEN nex.[2018-08-08] ELSE cte.[2018-08-08] END [2018-08-08]
,CASE WHEN cte.[2018-08-09] IS NULL THEN nex.[2018-08-09] ELSE cte.[2018-08-09] END [2018-08-09]
,CASE WHEN cte.[2018-08-10] IS NULL THEN nex.[2018-08-10] ELSE cte.[2018-08-10] END [2018-08-10]
,CASE WHEN cte.[2018-08-11] IS NULL THEN nex.[2018-08-11] ELSE cte.[2018-08-11] END [2018-08-11]
FROM CTE
LEFT JOIN CTE prev ON prev.rownum = CTE.rownum - 1
LEFT JOIN CTE nex ON nex.rownum = CTE.rownum + 1
ORDER BY cte.CrewID
)
,ct3
AS (
SELECT DISTINCT CrewID
,EmployeeName
,CASE WHEN [2018-08-05] = LEAD([2018-08-05]) OVER (
PARTITION BY crewid ORDER BY [2018-08-05]
) THEN NULL ELSE [2018-08-05] END [2018-08-05]
,CASE WHEN [2018-08-06] = LEAD([2018-08-06]) OVER (
PARTITION BY crewid ORDER BY [2018-08-06]
) THEN NULL ELSE [2018-08-06] END [2018-08-06]
,CASE WHEN [2018-08-07] = LEAD([2018-08-07]) OVER (
PARTITION BY crewid ORDER BY [2018-08-07]
) THEN NULL ELSE [2018-08-07] END [2018-08-07]
,CASE WHEN [2018-08-08] = LEAD([2018-08-08]) OVER (
PARTITION BY crewid ORDER BY [2018-08-08]
) THEN NULL ELSE [2018-08-08] END [2018-08-08]
,CASE WHEN [2018-08-09] = LEAD([2018-08-09]) OVER (
PARTITION BY crewid ORDER BY [2018-08-09]
) THEN NULL ELSE [2018-08-09] END [2018-08-09]
,CASE WHEN [2018-08-10] = LEAD([2018-08-10]) OVER (
PARTITION BY crewid ORDER BY [2018-08-10]
) THEN NULL ELSE [2018-08-10] END [2018-08-10]
,CASE WHEN [2018-08-11] = LEAD([2018-08-11]) OVER (
PARTITION BY crewid ORDER BY [2018-08-11]
) THEN NULL ELSE [2018-08-11] END [2018-08-11]
FROM ct2
)
SELECT *
FROM ct3
WHERE isnull([2018-08-05], 0) + isnull([2018-08-06], 0) + isnull([2018-08-07], 0) + isnull([2018-08-08], 0) + isnull([2018-08-09], 0) + isnull([2018-08-10], 0) + isnull([2018-08-11], 0) > 0
Hi how do i fill up the null value with e.g id, name
from the left join table
SQL:
DECLARE #startDate DATETIME, #endDate DATETIME
SELECT #startDate = '2015-11-18', #endDate = '2015-11-20' --yyyy-mm-dd
;WITH Calender AS (
SELECT #startDate AS WorkDate
UNION ALL
SELECT WorkDate + 1 FROM Calender
WHERE WorkDate + 1 <= #endDate
), -- generate date from to --
cte AS (
SELECT COUNT(*) OVER() AS 'total' ,ROW_NUMBER()OVER (ORDER BY c.dt DESC) as row
, c.*
FROM Calender d
LEFT JOIN clockL c ON c.dt = d.WorkDate AND c.id = 17
) -- select user log and add missing dates --
SELECT *
FROM cte
--WHERE row BETWEEN 0 AND 15
--option (maxrecursion 0)
Result:
total row badgenumber id name title deptname ld dt min mout ain aout nin nout
----------- -------------------- ----------- ----------- ---------------------------------------- -------------------- ------------------------------ ---------------- ------------ ------------ ------------ ------------ ------------ ------------ ------------
3 1 1 17 James HQ 2015-11-19 08:31 2015-11-19 08:31 NOC NOC NOC NOC NOC
3 2 1 17 James HQ 2015-11-18 20:37 2015-11-18 08:30 13:14 16:28 20:37 NOC NOC
3 3 NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL
^ ^ ^ ^ ^
should be 1 shoud be 17 James HQ 2015-11-20
thank you for looking :), i looked and tried but failed.
I have two different scenarios. In the first scenario I need something like:
create table test
(
ItemID int,
ItemStartDate datetime,
ItemEndDate datetime,
itemType varchar(100)
)
Table test:
ItemID ItemStartDate ItemEndDate itemType
------ ------------- ----------- --------
item_1 1/1/2011 3/2/2011 value A
item_1 3/3/2011 12/31/2011 value A
item_2 1/3/2011 12/31/2011 value B
It should show only two records:
ItemID ItemStartDate ItemEndDate itemType
------ ------------- ----------- --------
item_1 1/1/2011 12/31/2011 value A
item_2 1/1/2011 12/31/2011 value B
Scenario 2.
Here I would like to split data value to separate year periods if it's across multiple years.
Table test
create table #Scenario_2
(
ItemID int,
priceStartDate datetime,
priceEndDate datetime,
price int
)
item startdate enddate value
---- --------- ---------- -----
11 1/1/2011 5/4/2013 500
12 7/1/2013 11/12/2013 600
It should show like
item startdate enddate value
---- --------- ---------- -----
11 1/1/2011 12/31/2011 500
11 1/1/2012 12/31/2012 500
11 1/1/2013 5/4/2013 500
12 7/1/2013 11/12/2013 600
Please advise how I can achieve this.
Try this. from your question this is what i understood!!
SCENARIO 2
----------
CREATE TABLE #datt
(
itemid int,startd DATE,endat DATE,price int
)
INSERT INTO #datt
VALUES (11,'2011-01-01','2013-05-04',500),
(12,'2013-7-1','2013-11-12',600)
;WITH cte
AS (SELECT itemid,
startd st,
case when year(endat)<> YEAR(startd) then Dateadd(yy, Year(startd) - 1899, -1)
else endat end ed,price
FROM #datt
UNION ALL
SELECT a.itemid,
Dateadd(yy, 1, st),
CASE
WHEN Dateadd(yy, 1, ed) > b.endat THEN b.endat
ELSE Dateadd(yy, 1, ed)
END,a.price
FROM cte a
JOIN #datt b
ON a.itemid = b.itemid
AND a.ed < b.endat)
SELECT *
FROM cte order by itemid,st
For scenario1 you could see this answer.
For scenario2 there also have a similar answer you could reference.
But your question can be simplified like this:
with dates as
(
select number,cast(ltrim(number*10000+1231) as date) as dt
from master..spt_values
inner join
(select min(year(startdate)) as s_year
,max(year(enddate)) as e_year
from Scenario_2) as y
on number between y.s_year and y.e_year AND TYPE='P'
)
select
s.item
,case when year(dt) = year(startdate)
then startdate
else dateadd(year,-1,dateadd(day,1,dt)) end --or cast(ltrim(year(dt)*10000+101) as date)
,case when year(dt) = year(enddate)
then enddate
else dt end
,s.value
from
Scenario_2 s
inner join
dates d
on
d.number between year(s.startdate) and year(s.enddate)
SQL FIDDLE DEMO
I have a table like the following.
ID StartDate EndDate AttributeA AttributeB
-- --------- ------- ---------- ----------
1 1/1/2009 2/1/2009 0 C
1 2/1/2009 3/1/2009 1 C
1 3/1/2009 4/1/2009 1 C
2 1/1/2010 2/1/2010 0 D
2 3/1/2010 4/1/2010 1 D
The date range is used to know for what time period the rest of the Attributes were valid, the problem i have is that there are several consecutive time ranges where the Attributes ramain the same, what I would like is to obtain the same data but without the duplicate rows.
From the previous example, my expected end result would be like this:
ID StartDate EndDate AttributeA AttributeB
-- --------- ------- ---------- ----------
1 1/1/2009 2/1/2009 0 C
1 2/1/2009 4/1/2009 1 C
2 1/1/2010 2/1/2010 0 D
2 3/1/2010 4/1/2010 1 D
What I did was merge the 2nd and 3rd row into one (All attribute except the date were the same), but I kept the StartDate of the 2nd and the endDate of the 3rd row.
I first thought of grouping by the values obtaining the MAX and MIN like this
SELECT ID, MIN(StartDate), MAX(EndDate), attributeA, attributeB
FROM MyTable
Group BY ID, AttributeA, AttributeB
But as soon as I run it I realized that when the attributes change several times and go back to their original value I'd end up with overlapping intervals. I've been stuck for a while now trying to figure out how to fix this problem.
Here's an example of what I meant in my previous statement.
When the initial data looks like the following:
ID StartDate EndDate AttributeA AttributeB
-- --------- ------- ---------- ----------
1 1/1/2009 2/1/2009 0 C
1 2/1/2009 3/1/2009 0 D
1 3/1/2009 4/1/2009 0 D
1 4/1/2009 5/1/2009 1 D
1 6/1/2010 6/1/2009 0 D
Grouping the results would end up like the following
ID StartDate EndDate AttributeA AttributeB
-- --------- ------- ---------- ----------
1 1/1/2009 2/1/2009 0 C
1 2/1/2009 6/1/2009 0 D
1 4/1/2009 5/1/2009 1 D
And what I'd like to obtain is this
ID StartDate EndDate AttributeA AttributeB
-- --------- ------- ---------- ----------
1 1/1/2009 2/1/2009 0 C
1 2/1/2009 4/1/2009 0 D
1 4/1/2009 5/1/2009 1 D
1 6/1/2010 6/1/2009 0 D
Any help would be welcomed :)
EDIT: I'll be uploading some sample data soon to make my problem a bit easier to understand.
EDIT2: Here's a script with some of my data. From that sample what I'd like to obtain are the following rows.
ID StartDate EndDate A B C D E F
-- --------- ------- -- -- -- -- -- --
708513 1980-01-01 2006-07-23 15 ASDB A ACT 130 0
708513 2006-07-24 2009-12-08 15 ASDB A ACT 130 2
708513 2009-12-09 2010-01-12 0 ASDB A ACT 130 2
708513 2010-01-13 2079-05-30 15 ASDB A ACT 130 2
EDITED, following comments. Try:
;with cte as (
select m1.ID, m1.StartDate, m1.EndDate, m1.a, m1.b, m1.c, m1.d, m1.e, m1.f
from sampledata m1
where not exists
(select null from sampledata m0
where m1.ID = m0.ID and
m1.a = m0.a and
m1.b = m0.b and
m1.c = m0.c and
m1.d = m0.d and
m1.e = m0.e and
m1.f = m0.f and
dateadd(day, -1, m1.StartDate) = m0.EndDate)
union all
select m1.ID, m1.StartDate, m2.EndDate, m1.a, m1.b, m1.c, m1.d, m1.e, m1.f
from cte m1
join sampledata m2
on m1.ID = m2.ID and
m1.a = m2.a and
m1.b = m2.b and
m1.c = m2.c and
m1.d = m2.d and
m1.e = m2.e and
m1.f = m2.f and
dateadd(day, 1, m1.EndDate) = m2.StartDate)
select ID, StartDate, max(EndDate) EndDate, a, b, c, d, e, f
from cte
group by ID, StartDate, a, b, c, d, e, f
OPTION (MAXRECURSION 32767)
I made a version without recursion if someone is interested. I didn't really figure out how to add extra columns not used in comparison in the previous example.
IF OBJECT_ID('tempdb..#test') IS NOT NULL drop table #test
create table #test (
id int identity(1, 1)
, ship nvarchar(64)
, color nvarchar(16)
, [length] int
, height int
, [type] nvarchar(16)
, country nvarchar(16)
, StartDate date
)
insert into #test(ship, color, [length], height, [type], country, StartDate)
values
('Ship 1', 'Blue', 200, 13, 'sailboat', 'sweden', '2019-01-01')
, ('Ship 1', 'Blue', 200, 13, 'sailboat', 'sweden', '2019-02-01')
, ('Ship 1', 'Blue', 200, 13, 'sailboat', 'sweden', '2019-03-01')
, ('Ship 1', 'Red', 200, 13, 'motorboat', 'sweden', '2019-11-01')
, ('Ship 1', 'Blue', 200, 13, 'sailboat', 'sweden', '2019-12-01')
, ('Ship 2', 'Green', 400, 27, 'RoRo', 'denmark', '2019-02-01')
;
with step1 as (
select t.*
, [EndDate] = dateadd(day, -1, lead(t.StartDate, 1, '9999-12-31') over(partition by t.ship order by t.StartDate))
from #test t
where 1 = 1
)
, step2 as (
select t.*
-- Check if preceeding row with same attribute has enddate between this startdate
, [IdenticalPreceeding] = case
when t.StartDate = dateadd(day, 1, lag(t.EndDate, 1, '1900-01-01') over (partition by t.ship, t.color, t.[length], t.height, t.[type], t.country order by t.Startdate)) then 1
else 0
end
from step1 t
)
select t.*
, [EndDateFinal] = dateadd(day, -1, lead(t.StartDate, 1, '9999-12-31') over(partition by t.ship order by t.StartDate))
from step2 t
where 1 = 1
-- Remove rows with identical preceeders
and t.IdenticalPreceeding = 0
order by t.ship
, t.StartDate