issue in a date range creation - sql-server

I have been creating a date range, but in some cases a have a problem:
This is what I have: TABLE_1
date customer_id status total
---- ------------- -------- -------
20120201 1 a 10
20120202 1 a 20
20120203 1 b 20
20120204 1 b 20
20120205 1 a 20
20120206 1 a 20
20120201 2 d 30
20120202 2 e 40
After the execution of my procedure, I have this: TABLE_2
customer_id status start_date end_date
------------- -------- ----------- ---------
1 a 20120201 NULL
1 b 20120203 20120131
2 d 20120201 20120201
2 e 20120202 NULL
But this is what i want, a table with date ranges based on customer_id and status (end_date represents register with most recent date): TABLE_3
customer_id status start_date end_date
------------- -------- ----------- ---------
1 a 20120201 20120202
1 b 20120203 20120204
1 a 20120205 NULL
2 d 20120201 20120201
2 e 20120202 NULL
My store procedure look like this:
;WITH TEMP AS (
SELECT
Date
customer_id
status
FROM table_1
GROUP BY
date,
customer_id,
status
)
,TEMP2 AS (
SELECT
ID = ROW_NUMBER() OVER(PARTITION BY customer_id ORDER BY MAX(date) DESC),
start_date = MIN(date),
end_date = MAX(date),
[customer_id],
[status]
FROM TEMP
GROUP BY
[customer_id],
[status]
)
SELECT
A.customer_id,
A.status,
A.start_date,
end_date = DATEADD(DAY,-1,B.start_date)
FROM TEMP2 A
LEFT JOIN TEMP2 B
ON A.customer_id = B.customer_id
AND A.ID = B.ID + 1
I know my error is in the creation of CTE TEMP2, because this code can´t discriminate for a customer_id with a status with two occurrences in different ranges of time, based on the 'group by' sentence
I can´t figure out how to do that...

Try this. Hope it works now.
DECLARE #table_1 TABLE (
date DATETIME,
customer_id INT,
status CHAR(1),
total INT
)
INSERT #table_1 (date, customer_id, status, total)
VALUES
('20120201', 1, 'a', 10),
('20120202', 1, 'a', 20),
('20120203', 1, 'b', 20),
('20120204', 1, 'b', 20),
('20120205', 1, 'a', 20),
('20120206', 1, 'a', 20),
('20120201', 2, 'd', 30),
('20120202', 2, 'e', 40)
;WITH CTE_1 AS (
SELECT
customer_id,
status,
date,
ROW_NUMBER() OVER(PARTITION BY customer_id ORDER BY date ASC) AS seq
FROM #table_1
),
CTE_2 AS (
SELECT
customer_id,
status,
date,
seq,
1 AS flg,
1 AS seq2
FROM CTE_1
WHERE
seq = 1
UNION ALL
SELECT
CTE_1.customer_id,
CTE_1.status,
CTE_1.date,
CTE_1.seq,
CASE WHEN CTE_2.status = CTE_1.status THEN 0 ELSE 1 END,
CASE WHEN CTE_2.status = CTE_1.status THEN CTE_2.seq2 ELSE CTE_2.seq2 + 1 END
FROM CTE_1
INNER JOIN CTE_2
ON CTE_1.customer_id = CTE_2.customer_id
AND CTE_1.seq = CTE_2.seq + 1
)
SELECT
st.customer_id,
st.status,
st.date AS start_date,
DATEADD(DAY, -1, en.date) AS end_date
FROM CTE_2 AS st
LEFT JOIN CTE_2 AS en
ON st.customer_id = en.customer_id
AND st.seq2 = en.seq2 - 1
AND en.flg = 1
WHERE
st.flg = 1
ORDER BY
st.customer_id,
st.seq2

Related

SQL Query to calculate inventory on the first of each month over a date range

SQL Server 2012-2017. Can this be done with a CTE? Trying to avoid using a cursor.
I have a report request to return the inventory for the first of each month between dateFrom and dateTo, excluding the inventory on the dateFrom and dateTo.
Inventory is tracked by status history for each item. Each status history is coded as either in-inventory or out-of-inventory.
There could be many status history entries with an in-inventory status to track process steps. There will be another status history entry when the item is shipped, broken, lost, etc, and those are coded as out-of-inventory.
For reporting, an item is in inventory if the most recent status in status history before the reporting date is one that we code as in-inventory.
DECLARE #dateFrom dateTime, #dateTo dateTime
SET #dateFrom = '2-Nov-2017'
SET #dateTo = '20-Feb-2018 23:59:59.9'
--this proves out the dates are calculating correctly
;WITH cteDateTest AS
(
SELECT
1 roundCount,
DATEADD(M, DATEDIFF(M, 0, #dateFrom), 31) invDate --returns first of the month following dateFrom
UNION ALL
SELECT
roundCount + 1,
DATEADD(M, 1, invDate) --this one would go into the row_number join
FROM
cteDateTest
WHERE
DATEADD(M, 1, invDate) < #DateTo
)
SELECT * FROM cteDateTest
I've simplified the tables, using temp tables to be explicit that these are NOT the issue but created for ease in others understanding the problem.
CREATE TABLE #tempItems
(
id INT PRIMARY KEY,
itemDesc NVARCHAR (15) NULL,
isActive BIT
)
INSERT INTO #tempItems (id, itemDesc, isActive)
SELECT 1, 'widget 1',1 UNION ALL
SELECT 2, 'toy 2',1 UNION ALL
SELECT 3, 'something 3',1 UNION ALL
SELECT 4, 'prize 4',1
CREATE TABLE #tempStatusHistory
(
historyID INT PRIMARY KEY,
itemId INT,
itemStatus NVARCHAR (25) NULL,
statusDate DATETIME,
statusIsInInventory BIT,
)
INSERT INTO #tempStatusHistory (historyID, itemId, itemStatus, statusDate, statusIsInInventory)
SELECT 1, 1, 'in receiving', '2017-10-10',1 UNION ALL
SELECT 2, 1, 'in test', '2017-10-11',1 UNION ALL
SELECT 3, 1, 'on shelves', '2017-10-31',1 UNION ALL
SELECT 4, 2, 'in receiving', '2017-11-15',1 UNION ALL
SELECT 5, 2, 'in test', '2017-11-16',1 UNION ALL
SELECT 6, 2, 'on shelves', '2017-12-17',1 UNION ALL
SELECT 7, 2, 'sold', '2017-12-24',0 UNION ALL
SELECT 8, 3, 'in test', '2017-11-18',1 UNION ALL
SELECT 9, 3, 'in repair', '2017-12-19',1 UNION
SELECT 10, 3, 'returned to vendor', '2018-02-03',0 UNION ALL
SELECT 11, 4, 'in receiving', '2018-01-20',1 UNION ALL
SELECT 12, 4, 'on shelves', '2018-01-21',1 UNION ALL
SELECT 13, 4, 'sold', '2018-03-20',0
--select * from #tempStatusHistory
/* Per above data:
widget 1 in inventory all these months. Toy 2 in Dec 1 inventory. something 3 in Dec1, Jan 1, Feb 1 inventory, prize 4 in Feb 1 and Mar 1 inventory
Dec 1 inventory = 3 (widget 1, toy 2, something 3)
Jan 1 inventory = 2 (widget 1, something 3)
Feb 1 inventory = 3 (widget 1, something 3, prize 4)
Mar 1 inventory = 2 (widget 1, prize 4)
*/
--Our normal way of getting inventory for #dateFrom
SELECT
SH.historyId historyId, I.itemDesc
FROM
#tempItems I
JOIN
(SELECT
ROW_NUMBER () OVER (PARTITION BY itemId ORDER BY statusDate DESC) AS [Index],
itemId, historyId, statusDate, statusIsInInventory
FROM
#tempStatusHistory
WHERE
statusDate < #dateFrom) SH ON I.id = SH.itemId AND SH.[Index] = 1
WHERE
SH.statusIsInInventory = 1
--trying to pull inventory for each month between #dateFrom and #dateTo (exclusive of the end dates)
--anchor part of cte
;WITH cteInv AS
(
SELECT
1 roundCount,
DATEADD(M, DATEDIFF(M, 0, #dateFrom), 31) invDate,
DATEADD(M, 1, #dateFrom) staticDate,
1 linkField,
SH.historyId historyId,
I.itemDesc
FROM
#tempItems I
JOIN
(SELECT
ROW_NUMBER () OVER (PARTITION BY itemId ORDER BY statusDate DESC) AS [Index],
itemId, historyId, statusDate, statusIsInInventory
FROM
#tempStatusHistory
WHERE
statusDate < DATEADD(M, DATEDIFF(M, 0, #dateFrom), 31)) SH ON I.id = SH.itemId AND SH.[Index] = 1
WHERE
SH.statusIsInInventory = 1
--recursive part
UNION ALL
SELECT
roundCount + 1
, DATEADD(M,1,invDate)
, DATEADD(M,1,#dateFrom) staticDate
, 1
, SH.historyId
, I.itemDesc
FROM #tempItems I
--invDate not happy below
JOIN (SELECT ROW_NUMBER () OVER (PARTITION BY itemId ORDER BY statusDate DESC) AS [Index], itemId, historyId, statusDate, statusIsInInventory
FROM #tempStatusHistory WHERE statusDate < DATEADD(M,1,invDate)) SH ON I.id = SH.itemId AND SH.[Index] = 1
JOIN cteInv C ON I.isActive = C.linkField
WHERE DATEADD(M,1,invDate)< #dateTo AND SH.statusIsInInventory = 1
)
SELECT * from cteInv order by roundCount, invDate, itemDesc
drop table #tempItems
drop table #tempStatusHistory
the reference to invDate in the status history link shows as an error "invalid column name invDate". I can't figure a way around this. I also suspect an issue because if I replace invDate with #dateFrom in the same spot, I had expected the same inventory result for each month calculation, but it started multiplying itself.
Is cte a good solution for this? Is there a better way?
Thanks for anyone helping me on my first post here.
Addition: Expected output would be:
roundCount invDate linkField historyId itemDesc
-----------------------------------------------------------
1 2017-12-01 1 8 something 3
1 2017-12-01 1 5 toy 2
1 2017-12-01 1 3 widget 1
2 2018-01-01 1 9 something 3
2 2018-01-01 1 5 toy 2
3 2018-02-01 1 12 prize 4
3 2018-02-01 1 9 something 3
3 2018-02-01 1 3 widget 1
You are actually very very closed. Just need one OUTER APPLY
-- this is your cteDateTest query
;WITH
cteDateTest AS
(
SELECT
1 roundCount
,DATEADD(M,DATEDIFF(M,0,#dateFrom),31) invDate --returns first of the month following dateFrom
UNION ALL
SELECT
roundCount + 1
,DATEADD(M,1,invDate) --this one would go into the row_number join
FROM cteDateTest
WHERE DATEADD(M,1,invDate)< #DateTo
)
SELECT *
from cteDateTest d
OUTER APPLY
(
-- this is your normal query of getting inventory for #dateFrom
SELECT SH.historyID
, I.itemDesc
FROM #tempItems I
INNER JOIN
(
SELECT ROW_NUMBER () OVER (PARTITION BY itemId ORDER BY statusDate DESC) AS [Index],
itemId, historyID, statusDate, statusIsInInventory
FROM #tempStatusHistory
WHERE statusDate < d.invDate -- change to invDate from cteDateTest
) SH ON I.id = SH.itemId
AND SH.[Index] = 1
WHERE SH.statusIsInInventory = 1
) h

If Value is present in two consecutive months , display only one month in sql

I would want to check ID in consecutive months, IF Same ID is present in two consecutive months then consider that ID only for 1st month.
If ID's are not in consecutive month then show the distinct ID's grouped by start date month.(We consider only start date)
For example, ID 1 is present in start date months january and Feb , then Distinct count of this ID will be 1 in Jan, how ever ID 2 and 3 are
present in Jan and March and Feb and May Resp, now I would like to see this distinct count of ID in Jan and March.
Current Data
Table1:
ID StartDate EndDate
1 2017-01-12 2017-01-28
1 2017-01-19 2017-01-28
1 2017-01-29 2017-02-11
1 2017-02-01 2017-02-11
1 2017-02-19 2017-02-24
2 2017-01-12 2017-01-28
2 2017-01-19 2017-01-28
2 2017-03-09 2017-03-20
3 2017-02-12 2017-02-28
3 2017-02-19 2017-02-28
3 2017-05-05 2017-05-29
3 2017-05-09 2017-05-29
I tried with below logic bt I know I am missing on something here.
select t.* from Table1 t
join Table1 t t1
on t1.ID=t.ID
and datepart(mm,t.StartDate)<> datepart(mm,t1.StartDate)+1
Expected Result:
DistinctCount StartDateMonth(In Numbers)
1 1(Jan)
2 1(Jan)
2 3(March)
3 2(Feb)
3 5(May)
Any help is appreciated!
Here's my solution. The thinking for this is:
1) Round all the dates to the first of the month, then work with the distinct dataset of (ID, StartDateRounded). From your dataset, the result should look like this:
ID StartDateRounded
1 2017-01-01
1 2017-02-01
2 2017-01-01
2 2017-03-01
3 2017-02-01
3 2017-05-01
2) From this consolidated dataset, find all records by ID that do not have a record for the previous month (which means it's not a consecutive month and thus is a beginning of a new data point). This is your final dataset
with DatesTable AS
(
SELECT DISTINCT ID
,DATEADD(month,DateDiff(month,0,StartDate),0) StartDateRounded
,DATEADD(month,DateDiff(month,0,StartDate)+1,0) StartDateRoundedPlusOne
FROM Table1
)
SELECT t1.ID, DatePart(month,t1.StartDateRounded) AS StartDateMonth
FROM DatesTable t1
LEFT JOIN DatesTable t2
ON t1.ID = t2.ID
AND t1.StartDateRounded = t2.StartDateRoundedPlusOne
WHERE t2.ID IS NULL; --Verify no record exists for prior month
sqlfiddler for reference. Let me know if this helps
Just need to take advantage of the lag on the inner query to compare values between rows, and apply the logic in question on the middle query, and then do a final select.
/*SAMPLE DATA*/
create table #table1
(
ID int not null
, StartDate date not null
, EndDate date null
)
insert into #table1
values (1, '2017-01-12', '2017-01-28')
, (1, '2017-01-19', '2017-01-28')
, (1, '2017-01-29', '2017-02-11')
, (1, '2017-02-01', '2017-02-11')
, (1, '2017-02-19', '2017-02-24')
, (2, '2017-01-12', '2017-01-28')
, (2, '2017-01-19', '2017-01-28')
, (2, '2017-03-09', '2017-03-20')
, (3, '2017-02-12', '2017-02-28')
, (3, '2017-02-19', '2017-02-28')
, (3, '2017-05-05', '2017-05-29')
, (3, '2017-05-09', '2017-05-29')
/*ANSWER*/
--Final Select
select c.ID
, c.StartDateMonth
from (
--Compare record values to rule a record in/out based on OP's logic
select b.ID
, b.StartDateMonth
, case when b.StartDateMonth = b.StartDateMonthPrev then 0 --still the same month?
when b.StartDateMonth = b.StartDateMonthPrev + 1 then 0 --immediately prior month?
when b.StartDateMonth = 1 and b.StartDateMonthPrev = 12 then 0 --Dec/Jan combo
else 1
end as IncludeFlag
from (
--pull StartDateMonth of previous record into current record
select a.ID
, datepart(mm, a.StartDate) as StartDateMonth
, lag(datepart(mm, a.StartDate), 1, NULL) over (partition by a.ID order by a.StartDate asc) as StartDateMonthPrev
from #table1 as a
) as b
) as c
where 1=1
and c.IncludeFlag = 1
Output:
+----+----------------+
| ID | StartDateMonth |
+----+----------------+
| 1 | 1 |
| 2 | 1 |
| 2 | 3 |
| 3 | 2 |
| 3 | 5 |
+----+----------------+
Try the below query,
SELECT ID,MIN(YEARMONTH) AS YEARMONTH
FROM (
SELECT ID
,YEAR([StartDate])*100+MONTH([StartDate]) AS YEARMONTH
,LAG(YEAR([StartDate])*100+MONTH([StartDate]))
OVER(ORDER BY ID) AS PREVYEARMONTH
,ROW_NUMBER() OVER(ORDER BY ID) AS ROW_NO
FROM #Table1
GROUP BY ID,((YEAR([StartDate])*100)+MONTH([StartDate]))
) AS T
GROUP BY ID
,(CASE WHEN YEARMONTH - PREVYEARMONTH > 1 THEN ROW_NO ELSE 0 END)
ORDER BY ID
Output:
ID YEARMONTH
1 201701
2 201701
2 201703
3 201702
3 201705
Thank you all guys. most of the logic seemed to work..but I tried just with below one and I Was good with thiis.
SELECT t1.ID, DatePart(month,t1.Startdate) AS StartDateMonth
FROM DatesTable t1
LEFT JOIN DatesTable t2
ON t1.ID = t2.ID
AND DatePart(month,t1.Startdate) = DatePart(month,t2.Startdate)+1
WHERE t2.ID IS NULL;
Thanks again
Ok, I wrote my first query without checking, believed that will work correctly. This is my updated version, should be faster than other solutions
select
id
, min(st)%12 --this will return start month
, min(st)/12 + 1 --this will return year, just in case if you need it
from (
select
id, st, gr = st - row_number() over (partition by ID order by st)
from (
select
distinct ID, st = (year(StartDate) - 1) * 12 + month(StartDate)
from
#table2
) t
) t
group by id, gr

how can i find changes in a specific column and get the old value

Good morning all
I would appreciate any help you can give me in this subject
I have a table that grows in time with the same Id1
but some time Id2 change , like a historic of a park.
I would like to find the best way with a query to retrieve
the rows where id2 changes and time
example if table contents are
Id1 Id2 time
1 1 10:00
1 1 10:30
1 2 10:40
1 2 10:45
1 2 11:00
1 3 11:45
1 3 12:45
query output would be
Id1 oldId2 newId2 time
1 1 2 10:40
1 2 3 11:45
i have done with a stored procedure, but I was wondering of there is a faster/cleaner way to get this
thanks in advance
You can do this by Ranking functions..
Schema:
CREATE TABLE #TAB (Id1 INT,Id2 INT, timeS TIME )
INSERT INTO #TAB
SELECT 1 AS Id1 , 1 Id2, '10:00' AS timeS
UNION ALL
SELECT 1, 1, '10:30'
UNION ALL
SELECT 1, 2, '10:40'
UNION ALL
SELECT 1, 2, '10:45'
UNION ALL
SELECT 1, 2, '11:00'
UNION ALL
SELECT 1, 3, '11:45'
UNION ALL
SELECT 1, 3, '12:45'
Now do select with ROW_NUMBER and CTE for retrieving previous/next row values.
;WITH CTE
AS (
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS RNO
,ID1
,ID2
,timeS
FROM (
SELECT ROW_NUMBER() OVER (PARTITION BY ID2 ORDER BY TIMES) AS SNO
,*
FROM #TAB
) A
WHERE SNO = 1
)
SELECT C1.Id1
,C1.Id2 AS OLD_ID2
,C2.Id2 AS NEW_ID2
,C2.timeS
FROM CTE C1
LEFT JOIN CTE C2 ON C1.RNO + 1 = C2.RNO
WHERE C2.Id1 IS NOT NULL
Result:
+-----+---------+---------+------------------+
| Id1 | OLD_ID2 | NEW_ID2 | timeS |
+-----+---------+---------+------------------+
| 1 | 1 | 2 | 10:40:00.0000000 |
| 1 | 2 | 3 | 11:45:00.0000000 |
+-----+---------+---------+------------------+
Note: If you want to get Previous/Next Row values into current row, you can use LEAD LAG functions. But they support only in SQL Server 2012+.
The above Left Join with CTE will work for lower versions too.
declare #t table (Id1 int, Id2 int, [time] time)
insert into #t
select 1, 1, '10:00' union
select 1, 1, '10:30' union
select 1, 2, '10:40' union
select 1, 2, '10:45' union
select 1, 2, '11:00' union
select 1, 3, '11:45' union
select 1, 3, '12:45'
select Id1, oldId = (select top 1 id2 from #t where Id1=t.Id1 and Id2 < t.Id2 order by id2, time desc), newId = id2, time = min(time)
from #t t
where id2 > 1
group by Id1, id2
i have done some changes to the code from Shakeer Mirza.
the pratical problem that originated the question in the first place is:
i have a table that represents the history of an equipment. Being machine internal id(Num_TPA).
Each time there is a malfunction, the machine is replaced by another it keeps the same Num_TPA but Serial_number changes
i needed to know what is the historic on internal_id->Num_TPA . the new and the old serial_number , and the date of replacement
and this is what it came out.
;WITH CTE
AS (
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS RNO
,[Num_TPA]
,[Serial_number]
,[Time]
,a.SNO
FROM (
SELECT ROW_NUMBER() OVER (PARTITION BY [Num_TPA]
ORDER BY [Data_Hora_Ficheiro]) AS SNO
,*
FROM tab_values
) A
WHERE SNO > 1
)
SELECT C1.[Num_TPA]
,C1.[Serial_number] AS OLD_ID2
,C2.[Serial_number] AS NEW_ID2
,C2.[Data_Hora_Ficheiro]
,c2.SNO
,c2.RNO
FROM tab_values C1
LEFT JOIN CTE C2 ON (
C1.[Num_TPA] = C2.[Num_TPA]
AND c1.[Serial_number] != c2.[Serial_number]
AND C2.[Time] > c1.TIME
)
WHERE C2.[Num_TPA] IS NOT NULL
AND SNO = 2
UNION
SELECT C1.[Num_TPA]
,C1.[Serial_number] AS OLD_ID2
,C2.[Serial_number] AS NEW_ID2
,C2.[Data_Hora_Ficheiro]
,c2.SNO
,c2.RNO
FROM CTE C1
LEFT JOIN CTE C2 ON (
C1.SNO + 1 = C2.SNO
AND C1.[Num_TPA] = C2.[Num_TPA]
)
WHERE C2.[Num_TPA] IS NOT NULL
AND C2.SNO > 2

retrieve quantity data from two tables by id and date range

I have two table like
ItemTable_One
id itemID Date qty
===================================================
1 1 2015-07-1 10
2 1 2015-07-3 20
3 2 2015-07-5 30
4 2 2015-07-7 40
ItemTable_Two
id itemID Date qty
===================================================
1 1 2015-07-2 50
2 1 2015-07-4 60
3 3 2015-07-6 70
4 3 2015-07-8 80
I want to retrieve data where itemID is equal to 1 in a date range.
For example ( date range between 2015-07-1 and 2015-07-30 )
itemID Date ItemTableOne_qty ItemTableTwo_qty
============================================================================
1 2015-07-1 10 0
1 2015-07-3 20 0
1 2015-07-2 0 50
1 2015-07-4 0 60
I've tried union join and subquery to do it, but I'm very weak in sql query.
You can use UNION ALL to get all the data you need:
SELECT ItemTable_One.itemID, ItemTable_One.Date, ItemTable_One.qty as ItemTableOne_qty, 0 as ItemTableTwo_qty
FROM ItemTable_One
WHERE ItemTable_One.itemID = 1 AND
ItemTable_One.Date BETWEEN '2015-07-01' AND '2015-08-01'
UNION ALL
SELECT ItemTable_Two.itemID, ItemTable_Two.Date, 0 as ItemTableOne_qty, ItemTable_Two.qty as ItemTableTwo_qty
FROM ItemTable_One
WHERE ItemTable_Two.itemID = 1 AND
ItemTable_Two.Date BETWEEN '2015-07-01' AND '2015-08-01'
You can use a FULL OUTER JOIN:
SELECT COALESCE(t1.itemID, t2.itemID) AS itemID,
COALESCE(t1.[Date], t2.[Date]) AS [Date],
COALESCE(t1.qty, 0) AS ItemTableOne_qty,
COALESCE(t2.qty, 0) AS ItemTableTwo_qty
FROM ItemTable_One AS t1
FULL OUTER JOIN ItemTable_Two AS t2 ON t1.itemID = t2.itemID AND t1.[Date] = t2.[Date]
WHERE COALESCE(t1.itemID, t2.itemID) = 1 AND
COALESCE(t1.[Date], t2.[Date]) BETWEEN '2015-07-01' AND '2015-07-31'
ORDER BY COALESCE(t1.[Date], t2.[Date])
This will put records having the same [Date] value in the source tables, into the same row of the output table.
If records of ItemTable_One always have separate [Date] values from records of ItemTable_Two, then the UNION solution proposed in other answers is preferable.
Demo here
Try this:
select itemID, Date, qty as ItemTableOne_qty, 0 as ItemTableTwo_qty
from ItemTable_One
where ItemID = 1
and date >= '20150701'
and date < '20150731'
union all
select itemID, Date, 0 as ItemTableOne_qty, qty as ItemTableTwo_qty
from ItemTable_Two
where ItemID = 1
and date >= '20150701'
and date < '20150731'
The upper limit for date is intentionally < than the wanted date +1 so that in case it's a datetime that has the time in it, the last day will be included too.
You do it with a FULL JOIN or a UNION ALL (depending on exact required output)
FULL JOIN
A FULL JOIN will allow you to get results like you show as long as there are no identical dates between the 2 tables. If such dates are present in both tables, you'd get a single row per date with both values filled.
The query to use is:
SELECT COALESCE(t1.itemID, t2.itemID) itemID, COALESCE(t1.Date, t2.Date) Date,
ISNULL(t1.qty, 0) ItemTableOne_qty, ISNULL(t2.qty, 0) ItemTableTwo_qty
FROM ItemTable_One t1 FULL JOIN ItemTable_Two t2
ON t1.itemID = t2.itemID AND t1.Date = t2.Date
WHERE COALESCE(t1.itemID, t2.itemID) = 1 AND
COALESCE(t1.Date, t2.Date) BETWEEN '2015-07-01' AND '2015-08-01'
UNION ALL
A UNION ALL will allow you to get resutls like you show and will create duplicate rows where the same date exists in both tables. There will always be at least 1 '0' value in any row.
The query to use is:
SELECT itemID, Date, qty ItemTableOne_qty, 0 ItemTableTwo_qty
FROM ItemTable_One
WHERE itemID = 1 AND Date BETWEEN '2015-07-01' AND '2015-08-01'
UNION ALL
SELECT itemID, Date, 0 ItemTableOne_qty, qty ItemTableTwo_qty
FROM ItemTable_Two
WHERE itemID = 1 AND Date BETWEEN '2015-07-01' AND '2015-08-01'
this will be the other way we can achieve the same result basing on your sample data
declare #ItemTable_One table (id int, itemID int, Date date, qty int)
insert into #ItemTable_One values
(1, 1, '2015-07-1', 10),
(2, 1, '2015-07-3', 20),
(3, 2, '2015-07-5', 30),
(4, 2, '2015-07-7', 40)
declare #ItemTable_Two table (id int, itemID int, Date date, qty int)
insert into #ItemTable_Two values
(1, 1, '2015-07-2', 50),
(2, 1, '2015-07-4', 60) ,
(3, 3, '2015-07-6', 70) ,
(4, 3, '2015-07-8', 80)
;with CTE AS (
select i.itemID As ItemID1,ii.itemID As ItemID2,i.Date As Dated1,ii.Date As Dated2,i.qty as qty,ii.qty As qty1 from #ItemTable_One i
CROSS APPLY (select * from #ItemTable_Two )ii
where i.id = ii.id AND i.itemID = ii.itemID
)
select * from (
Select ItemID1 As item,Dated1 AS Date, qty,'' as qty1 from CTE
UNION
Select ItemID2 As item,Dated2 AS Date,'' as qty,qty1 from CTE)T
--ORDER BY t.qty desc ,t.qty1
Use inline view to simplify horrid sql used in prior answers:
SELECT *
FROM (
SELECT COALESCE(t1.itemID, t2.itemID) AS itemID,
COALESCE(t1.[Date], t2.[Date]) AS [Date],
COALESCE(t1.qty, 0) AS ItemTableOne_qty,
COALESCE(t2.qty, 0) AS ItemTableTwo_qty
FROM ItemTable_One AS t1 FULL OUTER JOIN ItemTable_Two AS t2
ON t1.itemID = t2.itemID AND t1.[Date] = t2.[Date]
) AS v
WHERE v.itemID = 1 AND
v.[Date] BETWEEN '2015-07-01' AND '2015-07-31'
ORDER BY v.[Date]

Collapsing records with adjacent start and end dates

I have the following data in a table in SQL Server 2008 R2:
ID Code StartDate EndDate
10001 3 2014-07-25 2014-07-28
10001 3 2014-07-29 2014-10-06
10001 3 2014-10-07 2014-10-10
10001 1 2014-10-11 2014-10-31
10001 1 2014-11-01 2014-11-15
10001 3 2014-11-16 2014-11-25
10001 3 2014-11-26 NULL
20002 3 2014-07-25 2014-07-28
20002 3 2014-07-29 2014-10-06
20002 3 2014-10-07 NULL
30003 3 2014-07-25 2014-11-13
30003 3 2014-11-14 2014-11-24
30003 2 2014-11-25 NULL
I want to "collapse" any records with the same Code, and adjacent EndDate and StartDate. The results should be:
ID Code StartDate EndDate
10001 3 2014-07-25 2014-10-10
10001 1 2014-10-11 2014-11-15
10001 3 2014-11-16 NULL
20002 3 2014-07-25 NULL
30003 3 2014-07-25 2014-11-24
30003 2 2014-11-25 NULL
I've been trying to use various sub-queries and the ROW_NUMBER() function, but just can't get it to work. I suspect this would be easily done with a CTE, but I haven't been able to wrap my head around how those work in order to try it here. Any ideas?
Since your ranges are continuous, the problem essentially becomes a gaps-and-islands one.
© Andriy M
Thanks to Steve Ford for the table:
declare #EventLog table
(
Id int,
Code tinyint,
StartDate date,
EndDate date null
)
insert into #EventLog
values
(10001, 3, '2014-07-25', '2014-07-28'),
(10001, 3, '2014-07-29', '2014-10-06'),
(10001, 3, '2014-10-07', '2014-10-10'),
(10001, 1, '2014-10-11', '2014-10-31'),
(10001, 1, '2014-11-01', '2014-11-15'),
(10001, 3, '2014-11-16', '2014-11-25'),
(10001, 3, '2014-11-26', null),
(20002, 3, '2014-07-25', '2014-07-28'),
(20002, 3, '2014-07-29', '2014-10-06'),
(20002, 3, '2014-10-07', null),
(30003, 3, '2014-07-25', '2014-11-13'),
(30003, 3, '2014-11-14', '2014-11-24'),
(30003, 2, '2014-11-25', null);
Thanks Andriy M for the solution:
declare #MaxDate date = '9999-12-31';
with cte as
(
select *,
g = row_number() over (partition by Id order by StartDate)
- row_number() over (partition by Id, Code order by StartDate)
from #EventLog
)
select
Id,
Code,
StartDate = min(StartDate),
EndDate = nullif(max(isnull(EndDate, #MaxDate)), #MaxDate)
from cte
group by
Id, Code, g;
Try this,
CREATE TABLE #TEMP
(
ID INT,
CODE INT,
STARTDATE DATE,
ENDDATE DATE
)
INSERT INTO #TEMP VALUES
(10001,3,'2014-07-25','2014-07-28'),
(10001,3,'2014-07-29','2014-10-06'),
(10001,3,'2014-10-07','2014-10-10'),
(10001,1,'2014-10-11','2014-10-31'),
(10001,1,'2014-11-01','2014-11-15'),
(10001,3,'2014-11-16','2014-11-25'),
(10001,3,'2014-11-26',NULL),
(20002,3,'2014-07-25','2014-07-28'),
(20002,3,'2014-07-29','2014-10-06'),
(20002,3,'2014-10-07',NULL),
(30003,3,'2014-07-25','2014-11-13'),
(30003,3,'2014-11-14','2014-11-24'),
(30003,2,'2014-11-25',NULL)
SELECT T1.ID,T1.CODE,T1.STARTDATE,A.ENDDATE FROM (SELECT L.ID,L.CODE,MIN(STARTDATE) AS STARTDATE,DIFF FROM (SELECT ID,
CODE,
STARTDATE,
ENDDATE,
IsNull(Lag(CODE, 2)
OVER (
ORDER BY ID, STARTDATE, ENDDATE), CODE) AS T_LAG,
CODE - IsNull(Lag(CODE, 2)
OVER (
ORDER BY ID, STARTDATE, ENDDATE), CODE) AS DIFF
FROM #TEMP ) L
GROUP BY L.ID,L.CODE,DIFF) T1
CROSS APPLY(
SELECT ID,CODE,ENDDATE,DIFF FROM (SELECT ID,CODE,ENDDATE,DIFF,ROW_NUMBER() OVER (PARTITION BY ID,CODE,DIFF ORDER BY ID,CODE,STARTDATE DESC,ENDDATE DESC) AS T_R FROM (SELECT ID,
CODE,
STARTDATE,
ENDDATE,
IsNull(Lag(CODE, 2)
OVER (
ORDER BY ID, STARTDATE, ENDDATE), CODE) AS T_LAG,
CODE - IsNull(Lag(CODE, 2)
OVER (
ORDER BY ID, STARTDATE, ENDDATE), CODE) AS DIFF
FROM #TEMP ) A) A
WHERE T_R=1 AND ID=T1.ID AND CODE=T1.CODE AND DIFF=T1.DIFF)A
ORDER BY T1.ID,T1.STARTDATE
Try this (I'm replicating SQL 2012 Lead And Lag functionality using Row_Number() in 2008):
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE EventLog
(
ID Int,
Code tinyint,
StartDate Date,
EndDate Date Null
)
INSERT INTO EventLog
Values
(10001, 3, '2014-07-25', '2014-07-28'),
(10001, 3, '2014-07-29', '2014-10-06'),
(10001, 3, '2014-10-07', '2014-10-10'),
(10001, 1, '2014-10-11', '2014-10-31'),
(10001, 1, '2014-11-01', '2014-11-15'),
(10001, 3, '2014-11-16', '2014-11-25'),
(10001, 3, '2014-11-26', NULL),
(20002, 3, '2014-07-25', '2014-07-28'),
(20002, 3, '2014-07-29', '2014-10-06'),
(20002, 3, '2014-10-07', NULL),
(30003, 3, '2014-07-25', '2014-11-13'),
(30003, 3, '2014-11-14', '2014-11-24'),
(30003, 2, '2014-11-25', NULL)
Query 1:
WITH CTE
AS
(
SELECT ID,
Code,
StartDate,
EndDate,
ROW_NUMBER() OVER (PARTITION BY Id, Code ORDER BY Id, Code, StartDate) As RN
FROM EventLog
),
CTE2
AS
(
SELECT CTE.Id, CTE.Code, CTE.StartDate, CTE.EndDate,
CASE WHEN DATEDIFF(d, LAG.EndDate, CTE.StartDate) = 1
THEN Lag.EndDate
ELSE NULL
END AS PrevEndDate,
CASE WHEN DateDiff(d, LEAD.StartDate, CTE.EndDate) = -1
THEN Lead.StartDate
ELSE NULL
END As NextStartDate
FROM CTE
LEFT OUTER JOIN CTE AS Lag
ON CTE.ID = Lag.ID AND CTE.Code = Lag.Code AND Lag.Rn = CTE.RN - 1
LEFT OUTER JOIN CTE AS Lead
ON CTE.ID = Lead.ID AND CTE.Code = Lead.Code AND Lead.Rn = CTE.RN + 1
),
StartAndEnd
As
(
SELECT ID,
Code,
StartDate,
EndDate,
PrevEndDate,
NextStartDate,
ROW_NUMBER() OVER (PARTITION BY Id, Code ORDER BY ID, Code, StartDate) As RN
FROM CTE2
WHERE (PrevEndDate IS NULL Or NextStartDate IS NULL)
)
SELECT S.ID, s.Code, S.StartDate, E.EndDate
FROM StartAndEnd as S
LEFT JOIN StartAndEnd E
ON S.ID = E.ID AND S.Code = E.Code AND E.RN = S.Rn + 1
WHERE S.PrevEndDate Is Null
ORDER By s.Id, S.StartDate
Results:
| ID | CODE | STARTDATE | ENDDATE |
|-------|------|------------|------------|
| 10001 | 3 | 2014-07-25 | 2014-10-10 |
| 10001 | 1 | 2014-10-11 | 2014-11-15 |
| 10001 | 3 | 2014-11-16 | (null) |
| 20002 | 3 | 2014-07-25 | (null) |
| 30003 | 3 | 2014-07-25 | 2014-11-24 |
| 30003 | 2 | 2014-11-25 | (null) |
;WITH StartDates
AS(
Select e1.ID, e1.Code, e1.StartDate, ROW_NUMBER() OVER (Order By e1.ID asc) as RowNumber
From #EventLog e1
LEFT JOIN #EventLog e2 ON e1.Code = e2.Code and e2.EndDate = DATEADD(day,-1,e1.StartDate)
WHERE e2.Id is null
),
EndDates as(
Select e1.ID, e1.Code, e1.EndDate, ROW_NUMBER() OVER (Order by e1.ID asc) as RowNumber
FROM #EventLog e1
LEFT JOIN #EventLog e2 ON e1.Code = e2.Code and e2.StartDate = DATEADD(day,1,e1.EndDate)
WHERE e2.Id is null
)
Select s.ID, s.Code, s.StartDate, e.EndDate
FROM StartDates s
JOIN EndDates e ON s.Code = e.Code and s.RowNumber = e.RowNumber

Resources