Getting first and last values from contiguous ranges - sql

I want to get the first enter_date and the last leave_date for contiguous enter_day and leave_day values for each id. Given this example data:
+-----+------------+------------+-----------+-----------+
| id | enter_date | leave_date | enter_day | leave_day |
+-----+------------+------------+-----------+-----------+
| 111 | 2016-07-29 | 2016-12-01 | 1 | 75 |
| 111 | 2016-12-02 | 2017-01-13 | 76 | 95 |
| 111 | 2017-01-17 | 2017-06-02 | 96 | 181 |
| 222 | 2016-07-29 | 2016-12-02 | 1 | 76 |
| 222 | 2017-01-30 | 2017-06-02 | 105 | 181 |
| 333 | 2016-08-01 | 2017-06-02 | 1 | 180 |
+-----+------------+------------+-----------+-----------+
I want the following result:
+-----+------------+------------+
| id | enter_date | leave_date |
+-----+------------+------------+
| 111 | 2016-07-29 | 2017-06-02 |
| 222 | 2016-07-29 | 2016-12-02 |
| 222 | 2017-01-30 | 2017-06-02 |
| 333 | 2016-08-01 | 2017-06-02 |
+-----+------------+------------+
I want one record for ID 111 because there are no gaps between any enter_day and the previous leave_day.
I want both records for ID 222 because there is a gap (days 75 through 104).
EDIT: What I have so far, which isn't giving me the correct leave_date for ID 111:
with cte as (
select a.id, a.enter_date, a.leave_date, b.enter_date next_ed, b.leave_date next_ld
from #tbl a
join #tbl b on b.id = a.id and b.enter_day = a.leave_day + 1
)
select id, min(enter_date) enter_date, max(leave_date) leave_date
from cte
group by id
union
select a.id, a.enter_date, a.leave_date
from #tbl a
left join #tbl b on b.id = a.id and b.enter_day = a.leave_day + 1
left join cte c on c.id = a.id and c.next_ed = a.enter_date and c.next_ld = a.leave_date
where b.id is null and c.id is null
order by 1,3

Below is an example of Gaps-and-Islands for ranges.
I used an ad-hoc tally table, but an actual number/tally would do the trick as well.
If you just run the inner query, you will see very quickly how your sample data of 6 rows explodes into 514 rows. Then is is a small matter of applying the grouped aggregation to get the final results.
Example
Declare #YourTable Table ([id] int,[enter_date] date,[leave_date] date,[enter_day] int,[leave_day] int)
Insert Into #YourTable Values
(111,'2016-07-29','2016-12-01',1,75)
,(111,'2016-12-02','2017-01-13',76,95)
,(111,'2017-01-17','2017-06-02',96,181)
,(222,'2016-07-29','2016-12-02',1,76)
,(222,'2017-01-30','2017-06-02',105,181)
,(333,'2016-08-01','2017-06-02',1,180)
Select ID
,[enter_date] = min([enter_date])
,[leave_date] = max([leave_date])
From (
Select *
,Grp = N - Row_Number() over (Partition By ID Order by N)
From #YourTable A
Join (
Select Top (Select max([leave_day]-[enter_day])+1 From #YourTable)
N=-1+Row_Number() Over (Order By (Select Null))
From master..spt_values n1,master..spt_values n2
) B on B.N between [enter_day] and [leave_day]
) A
Group By [ID],Grp
Order By [ID],min([enter_date])
Returns
ID enter_date leave_date
111 2016-07-29 2017-06-02
222 2016-07-29 2016-12-02
222 2017-01-30 2017-06-02
333 2016-08-01 2017-06-02

Related

Difference between consecutive rows in SQL, inclusive of the first and last row?

I'm trying to get the successive differences of rows of data in SQL, including differences between first and last row and 0.
I have two tables that look like this
+------------+-------+ +------------+-------+
| Date | Name | | Date | Value |
+------------+-------+ +------------+-------+
| 2019-10-10 | AAA | | 2019-10-11 | 100 |
| 2019-10-11 | BBB | | 2019-10-12 | 150 |
| 2019-10-12 | CCC | | 2019-10-14 | 300 |
| 2019-10-13 | DDD | +------------+-------+
| 2019-10-14 | EEE |
| 2019-10-15 | FFF |
+------------+-------+
The end result I'm looking for is
+------------+-------+-------+---------------+------------+
| Date | Name | Value | PreviousValue | Difference |
+------------+-------+-------+---------------+------------+
| 2019-10-11 | BBB | 100 | 0 | 100 |
| 2019-10-12 | CCC | 150 | 100 | 50 |
| 2019-10-14 | EEE | 300 | 150 | 150 |
| 2019-10-15 | FFF | 0 | 300 | -300 |
+------------+-------+-------+---------------+------------+
I can get the first row by using LAG, but I don't quite know how to get the last row at the same time.
SELECT
d.[Date],
d.[Name],
v.[Value],
[PreviousValue] = COALESCE(LAG(v.[Value) OVER (ORDER BY v.[Date]), 0)
[PreviousLossAmount] = v.[Value] - COALESCE(LAG(v.[Value) OVER (ORDER BY v.[Date]), 0)
FROM
[Dates] d
LEFT JOIN
[Values] v
ON
d.[Date] = v.[Date]
Note that in reality, my tables are more complex, and I'd need to group and partition by multiple columns.
In a subquery, you can LEFT JOIN, and use ROW_NUMBER() to identify the last record. Then in the outer query you can filter out rows that did not match in the LEFT JOIN while allowing the last record.
Other considerations:
you just need to order LAG() by d.[Date] instead of v.[Date]. This properly handles the case when the left join comes up empty (ie when v.[Date] is null).
You also want to use COALESCE() on v.[Value], since this may come up null too.
Query:
SELECT
[Date],
[Name],
[Value],
[PreviousValue],
[PreviousLossAmount]
FROM (
SELECT
d.[Date],
d.[Name],
[Value] = COALESCE(v.[Value], 0),
[PreviousValue] = COALESCE(LAG(v.[Value]) OVER (ORDER BY d.[Date]), 0),
[PreviousLossAmount] = COALESCE(v.[Value], 0)
- COALESCE(LAG(v.[Value]) OVER (ORDER BY d.[Date]), 0),
rn = ROW_NUMBER() OVER(ORDER BY d.[Date] DESC),
vDate = v.[Date]
FROM [Dates] d
LEFT JOIN [Values] v ON d.[Date] = v.[Date]
) t
WHERE vDate IS NOT NULL OR RN = 1
ORDER BY [Date]
Demo on DB Fiddle:
Date | Name | Value | PreviousValue | PreviousLossAmount
:------------------ | :--- | ----: | ------------: | -----------------:
11/10/2019 00:00:00 | BBB | 100 | 0 | 100
12/10/2019 00:00:00 | CCC | 150 | 100 | 50
14/10/2019 00:00:00 | EEE | 300 | 0 | 300
15/10/2019 00:00:00 | FFF | 0 | 300 | -300
You seem to want:
select d.[Date], d.[Name], v.[Value],
PreviousValue = COALESCE(LAG(v.[Value) OVER (ORDER BY v.[Date]), 0)
PreviousLossAmount]= v.[Value] - COALESCE(LAG(v.[Value) OVER (ORDER BY v.[Date]), 0)
from dates d join
[Values] v
on d.[Date] = v.[Date]
union all
select top (1) d.date, d.name, 0 as value, v.value, - v.value
from dates d cross join
(select top (1) v.*
from values v
order by v.date desc
) v
order by d.date;

SQL - get default NULL value if data is not available

I got a table data as follows:
ID | TYPE_ID | CREATED_DT | ROW_NUM
=====================================
123 | 485 | 2019-08-31 | 1
123 | 485 | 2019-05-31 | 2
123 | 485 | 2019-02-28 | 3
123 | 485 | 2018-11-30 | 4
123 | 485 | 2018-08-31 | 5
123 | 485 | 2018-05-31 | 6
123 | 487 | 2019-05-31 | 1
123 | 487 | 2018-05-31 | 2
I would like to select 6 ROW_NUMs for each TYPE_ID, if there is missing data I need to return NULL value for CREATED_DT and the final result set should look like:
ID | TYPE_ID | CREATED_DT | ROW_NUM
=====================================
123 | 485 | 2019-08-31 | 1
123 | 485 | 2019-05-31 | 2
123 | 485 | 2019-02-28 | 3
123 | 485 | 2018-11-30 | 4
123 | 485 | 2018-08-31 | 5
123 | 485 | 2018-05-31 | 6
123 | 487 | 2019-05-31 | 1
123 | 487 | 2018-05-31 | 2
123 | 487 | NULL | 3
123 | 487 | NULL | 4
123 | 487 | NULL | 5
123 | 487 | NULL | 6
Query:
SELECT
A.*
FROM TBL AS A
WHERE A.ROW_NUM <= 6
UNION ALL
SELECT
B.*
FROM TBL AS B
WHERE B.ROW_NUM NOT IN (SELECT ROW_NUM FROM TBL)
AND B.ROW_NUM <= 6
I tried using UNION ALL and ISNULL to backfill data that is not available but it is still giving me the existing data but not the expected result. I think this can be done in a easy way by using CTE but not sure how to get this working. Can any help me in this regard.
Assuming Row_Num has at least record has at least all 6 rows... 1,2,3,4,5,6 in tbl and no fractions or 0 or negative numbers...
we get a list of all the distinct type ID's and IDs. (Alias A)
Then we get a distinct list of row numbers less than 7 (giving us 6 records)
we cross join these to ensure each ID & Type_ID has all 6 rows.
we then left join back in the base set (tbl) to get all the needed dates; where such dates exist. As we're using left join the rows w/o a date will still persist.
.
SELECT A.ID, A.Type_ID, C.Created_DT, B.Row_Num
FROM (SELECT DISTINCT ID, Type_ID FROM tbl) A
CROSS JOIN (SELECT distinct row_num from tbl where Row_num < 7) B
LEFT JOIN tbl C
on C.ID = A.ID
and C.Type_ID = A.Type_ID
and C.Row_num = B.Row_num
Giving us:
+----+-----+---------+------------+---------+
| | ID | Type_ID | Created_DT | Row_Num |
+----+-----+---------+------------+---------+
| 1 | 123 | 485 | 2019-08-31 | 1 |
| 2 | 123 | 485 | 2019-05-31 | 2 |
| 3 | 123 | 485 | 2019-02-28 | 3 |
| 4 | 123 | 485 | 2018-11-30 | 4 |
| 5 | 123 | 485 | 2018-08-31 | 5 |
| 6 | 123 | 485 | 2018-05-31 | 6 |
| 7 | 123 | 487 | 2019-05-31 | 1 |
| 8 | 123 | 487 | 2018-05-31 | 2 |
| 9 | 123 | 487 | NULL | 3 |
| 10 | 123 | 487 | NULL | 4 |
| 11 | 123 | 487 | NULL | 5 |
| 12 | 123 | 487 | NULL | 6 |
+----+-----+---------+------------+---------+
Rex Tester: Example
This also assumes that you'd want 1-6 for each combination of type_id and ID. If ID's irrelevant, then simply exclude it from the join criteria. I included it as it's an ID and seems like it's part of a key.
Please reference the other answer for how you can do this using a CROSS JOIN - which is pretty neat. Alternatively, we can utilize the programming logic available in MS-SQL to achieve the desired results. The following approach stores distinct ID and TYPE_ID combinations inside a SQL cursor. Then it iterates through the cursor entries to ensure the appropriate amount of data is stored into a temp table. Finally, the SELECT is performed on the temp table and the cursor is closed. Here is a proof of concept that I validated on https://rextester.com/l/sql_server_online_compiler.
-- Create schema for testing
CREATE TABLE Test (
ID INT,
TYPE_ID INT,
CREATED_DT DATE
)
-- Populate data
INSERT INTO Test(ID, TYPE_ID, CREATED_DT)
VALUES
(123,485,'2019-08-31')
,(123,485,'2019-05-31')
,(123,485,'2019-02-28')
,(123,485,'2018-11-30')
,(123,485,'2018-08-31')
,(123,485,'2018-05-31')
,(123,487,'2019-05-31')
,(123,487,'2018-05-31');
-- Create TempTable for output
CREATE TABLE #OutputTable (
ID INT,
TYPE_ID INT,
CREATED_DT DATE,
ROW_NUM INT
)
-- Declare local variables
DECLARE #tempID INT, #tempType INT;
-- Create cursor to iterate ID and TYPE_ID
DECLARE mycursor CURSOR FOR (
SELECT DISTINCT ID, TYPE_ID FROM Test
);
OPEN mycursor
-- Populate cursor
FETCH NEXT FROM mycursor
INTO #tempID, #tempType;
-- Loop
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #count INT = (SELECT COUNT(*) FROM Test WHERE ID = #tempID AND TYPE_ID = #tempType);
INSERT INTO #OutputTable (ID, TYPE_ID, CREATED_DT, ROW_NUM)
SELECT ID, TYPE_ID, CREATED_DT, ROW_NUMBER() OVER(ORDER BY ID ASC)
FROM Test
WHERE ID = #tempID AND TYPE_ID = #tempType;
WHILE #count < 6
BEGIN
SET #count = #count + 1
INSERT INTO #OutputTable
VALUES (#tempID, #tempType, NULL, #count);
END
FETCH NEXT FROM mycursor
INTO #tempID, #tempType;
END
-- Close cursor
CLOSE mycursor;
-- View results
SELECT * FROM #OutputTable;
Note, if you have an instance where a unique combination of ID and TYPE_ID are grouped more than 6 times, the additional groupings will be included in your final result. If you must only show exactly 6 groupings, you can change that part of the query to SELECT TOP 6 ....
create a cte with a series and cross apply it
CREATE TABLE Test (
ID INT,
TYPE_ID INT,
CREATED_DT DATE
)
INSERT INTO Test(ID, TYPE_ID, CREATED_DT)
VALUES
(123,485,'2019-08-31')
,(123,485,'2019-05-31')
,(123,485,'2019-02-28')
,(123,485,'2018-11-30')
,(123,485,'2018-08-31')
,(123,485,'2018-05-31')
,(123,487,'2019-05-31')
,(123,487,'2018-05-31')
;
WITH n(n) AS
(
SELECT 1
UNION ALL
SELECT n+1 FROM n WHERE n < 6
)
,id_n as (
SELECT
DISTINCT
ID
,TYPE_ID
,n
FROM
Test
cross apply n
)
SELECT
id_n.ID
,id_n.TYPE_ID
,test.CREATED_DT
,id_n.n row_num
FROM
id_n
left join
(
select
ID
,TYPE_ID
,CREATED_DT
,ROW_NUMBER() over(partition by id, type_id order by created_dt) rn
from
Test
) Test on Test.ID = id_n.ID and Test.TYPE_ID = id_n.TYPE_ID and id_n.n = test.rn
drop table Test

SQL server 2008: join 3 tables and select last entered record from child table against each parent record

I have following 3 tables and last entered reasoncode from Reasons table against each claimno in claims table.
Reasons:
Rid |chargeid| enterydate user reasoncode
-----|--------|-------------|--------|----------
1 | 210 | 04/03/2018 | john | 99
2 | 212 | 05/03/2018 | juliet | 24
5 | 212 | 26/12/2018 | umar | 55
3 | 212 | 07/03/2018 | borat | 30
4 | 211 | 03/03/2018 | Juliet | 20
6 | 213 | 03/03/2018 | borat | 50
7 | 213 | 24/12/2018 | umer | 60
8 | 214 | 01/01/2019 | john | 70
Charges:
chargeid |claim# | amount
---------|-------|---------
210 | 1 | 10
211 | 1 | 24.2
212 | 2 | 5.45
213 | 2 | 76.30
214 | 1 | 2.10
Claims:
claimno | Code | Code
--------|-------|------
1 | AH22 | AH22
2 | BB32 | BB32
Expected result would be like this:
claimno | enterydate | user | reasoncode
--------|-------------|--------|-----------
1 | 01/01/2019 | john | 70
2 | 26/12/2018 | umer | 55
I have applied many solutions but no luck. Following is the latest solution I was trying using SQL Server 2008 but still got incorrect result.
With x As
(
select r.chargeid,r.enterydate,ch.claimno from charges ch
join (select chargeid,max(enterydate) enterydate,user from Reasons group by chargeid) r on r.chargeid = ch.chargeid
)
select x.*,r1.user, r1.reasoncode from x
left outer join Reasons r1 on r1.chargeid = x.chargeid and r1.enterydate = x.enterydate
--group by x.claimno
Is this what you want?
select claimno, enterydate, user, reasoncode
from (select c.claimno, r.*,
row_number() over (partition by c.claimno order by r.entrydate desc) as seqnum
from charges c join
reasons r
on c.chargeid = r.chargeid
) cr
where seqnum = 1;
You can try using row_number()
select * from
(
select r.chargeid,r.enterydate,ch.claimno,user,reasoncode,
row_number() over(partition by ch.claimno order by r1.enterydate desc) as rn
from charges ch left outer join Reasons r1 on r1.chargeid = ch.chargeid
)A where rn=1

Adding Missing Days in Data -SQL

I have a table that's only showing weekday values. It's grabbing these from a file that's imported only on the weekdays. I'm needing to also add in the weekend (or holidays) with the previously known day's value. I have asked this question when I was needing it to be used in MS Access. I'm now moving this database to SQL Server.
If you're wanting to see what worked for me in Access, you're more than welcome to check out the link.
I have attempted to adapt the MS Access SQL to SQL Server with:
SELECT a1.IDNbr, a1.Balance, CONVERT(int, DAY(a1.BalDate)) + 3
FROM tblID a1 INNER JOIN tblID a2 ON (CONVERT(int, DAY(a1.BalDate)) + 4 = a2.BalDate) AND (a1.IDNbr = a2.IDNbr)
WHERE NOT EXISTS (
SELECT *
FROM tblID a3
WHERE a3.IDNbr = a1.IDNbr AND a3.BalDate = CONVERT(int, DAY(a1.BalDate)) + 3) AND (DATEPART(W, a1.BalDate) = 6
);
However, I'm getting the Error:
Msg 206, Level 16, State 2, Line 4
Operand type clash: date is incompatible with int
Question: How can I get this query (which I will be turning into an INSERT statement) to show all the missing days within my data and to assign the value of the last known day to the missing days?
Data that I have(starting on Friday):
+-------------------------------------+
|ID | IDNbr | Balance | BalDate |
+-------------------------------------+
|001| 91 | 529 | 1/5/2018 |
|002| 87 | 654 | 1/5/2018 |
|003| 45 | 258 | 1/5/2018 |
|004| 91 | 611 | 1/8/2018 |
|005| 87 | 753 | 1/8/2018 |
|006| 45 | 357 | 1/8/2018 |
|...| .. | ... | ........ |
+-------------------------------------+
'BalDate then skips past 1/6/2018 and 1/7/2018 to 1/8/2018
Data that I'm needing:
+-------------------------------------+
|ID | IDNbr | Balance | BalDate |
+-------------------------------------+
|001| 91 | 529 | 1/5/2018 |
|002| 87 | 654 | 1/5/2018 |
|003| 45 | 258 | 1/5/2018 |
|004| 91 | 529 | 1/6/2018 |
|005| 87 | 654 | 1/6/2018 |
|006| 45 | 258 | 1/6/2018 |
|007| 91 | 529 | 1/7/2018 |
|008| 87 | 654 | 1/7/2018 |
|009| 45 | 258 | 1/7/2018 |
|010| 91 | 611 | 1/8/2018 |
|011| 87 | 753 | 1/8/2018 |
|012| 45 | 357 | 1/8/2018 |
|...| .. | ... | ........ |
+-------------------------------------+
'I'm needing it to add the Saturday(1/6/2018) and Sunday(1/7/2018) before continuing on to 1/8/2018
Any help would be appreciated. Thank you in advance!
If there are downvotes, I ask that you please explain why you are downvoting so I may correct it!
Ok, you're going to need the CalTable() function from Bernd's answer. We're going to use it to create a list of all calendar dates between the MIN(BalDate) and the MAX(BalDate) in tblID. We're also going to CROSS JOIN that with the list of DISTINCT IDNbr values, which I assume is the PK of tblID.
Let's create some sample data.
CREATE TABLE #tblID (ID VARCHAR(3), IDNbr INT, Balance INT, BalDate DATE)
INSERT INTO #tblID
(
ID
,IDNbr
,Balance
,BalDate
)
VALUES
('001',91,529,'1/5/2018'),
('002',87,654,'1/5/2018'),
('003',45,258,'1/5/2018'),
('004',91,611,'1/8/2018'),
('005',87,753,'1/8/2018'),
('006',45,357,'1/8/2018')
Next, we're going to INSERT new records into #tblID for the missing days. The magic here is in the LAG() function, which can looks at a previous row's data. We give it an expression for the offset value, based on the difference between missing date and the last date with data.
;WITH IDs AS
(
SELECT DISTINCT
IDNbr
FROM #tblID
)
,IDDates AS
(
SELECT
BalDate = c.[Date]
,i.IDNbr
FROM [CalTable]((SELECT MIN(BalDate) FROM #tblID), (SELECT MAX(BalDate) FROM #tblID)) c
CROSS APPLY IDs i
)
,FullResults AS
(
SELECT
i.BalDate
,i.IDNbr
,Balance = CASE WHEN t.Balance IS NOT NULL THEN t.Balance
ELSE LAG(t.Balance,
DATEDIFF(
DAY
,(SELECT MAX(t1.BalDate) FROM #tblID t1 WHERE t1.IDNbr = i.IDNbr AND t1.BalDate <= i.BalDate GROUP BY t1.IDNbr)
,i.BalDate
)
) OVER (PARTITION BY i.IDNbr ORDER BY i.BalDate ASC)
END
FROM IDDates i
LEFT JOIN #tblID t ON t.BalDate = i.BalDate AND t.IDNbr = i.IDNbr
)
INSERT INTO #tblID
(
IDNbr
,Balance
,BalDate
)
SELECT
f.IDNbr
,f.Balance
,f.BalDate
FROM FullResults f
LEFT JOIN #tblID t ON t.IDNbr = f.IDNbr AND t.BalDate = f.BalDate
WHERE t.IDNbr IS NULL
At this point, if we didn't care about the ID field, which appears to be a 3-character string representation of the row number, we'd be good. However, while I don't think it's a good practice to use a string in this manner, I'm also not one to comment on someone else's business requirements that I am not privy to.
So let's assume we have to update the ID field to match the expected output. We can do that like this:
;WITH IDUpdate AS
(
SELECT
ID = RIGHT('000' + CAST(ROW_NUMBER() OVER (ORDER BY BalDate ASC, IDNbr DESC) AS VARCHAR), 3)
,t.IDNbr
,t.Balance
,t.BalDate
FROM #tblID t
)
UPDATE t
SET t.ID = i.ID
FROM #tblID t
INNER JOIN IDUpdate i ON i.IDNbr = t.IDNbr AND i.BalDate = t.BalDate
Now if you query your updated table, you'll get the following:
SELECT
ID
,IDNbr
,Balance
,BalDate
FROM #tblID
ORDER BY BalDate ASC, IDNbr DESC
Output:
ID | IDNbr | Balance | BalDate
------------------------------
001 | 91 | 529 | 2018-01-05
002 | 87 | 654 | 2018-01-05
003 | 45 | 258 | 2018-01-05
004 | 91 | 529 | 2018-01-06
005 | 87 | 654 | 2018-01-06
006 | 45 | 258 | 2018-01-06
007 | 91 | 529 | 2018-01-07
008 | 87 | 654 | 2018-01-07
009 | 45 | 258 | 2018-01-07
010 | 91 | 611 | 2018-01-08
011 | 87 | 753 | 2018-01-08
012 | 45 | 357 | 2018-01-08
Here is a samples for the linked function:
create FUNCTION [dbo].[CalTable]
(
#startDate date,
#endDate date
)
RETURNS
#calender TABLE
(
[Date] date not null primary key CLUSTERED,
isMondayToFriday bit not null
)
AS
BEGIN
declare #currentday date = #startDate;
declare #isMondayToFriday bit;
while (#currentday<=#endDate)
begin
-- respect DATEFIRST depending on language settings
if (DATEPART(dw, #currentday)+##DATEFIRST-2)%7+1>5
set #isMondayToFriday = 0
else
set #isMondayToFriday = 1
insert into #calender values (#currentday, #isMondayToFriday);
set #currentday = DATEADD(D, 1, #currentday);
end
RETURN
END
GO
select * from [CalTable]({d'2018-01-01'}, {d'2018-02-03'});
use this for find the gaps.

SQL JOIN with multiple date condition

We have the first valuetable table and the query should check if there is
a next younger datetime in the correctiontable table and should add the corrvalue with the corrdatetime.
My problem query:
SELECT * FROM valuetable vt
LEFT JOIN correctiontable corr ON corr.value_id = vt.id WHERE vt.datetime <= corr.corrdatetime
is just delivering the last corrdatetime...
To clarify te results:
Row1 id1 should be NULL as the valuetable datetime is younger than the correction datetime
Row2 id2 should be 01/08/2017 00:00:00 as the datetime in valuetable is older but younger than the 01/12/2017 10:00:00 corrdatetime
Row3 id2 got its correction on 01/12/2017 10:00:00
Row4 id3 is NULL, there is no corrdatetime in correctiontable for it
Thank you all ++
+----------------------------------+
| valuetable |
+----------------------------------+
| id | datetime | value |
+----+---------------------+-------+
| 1 | 22/07/2017 13:00:00 | 123 |
+----+---------------------+-------+
| 2 | 10/08/2017 09:00:00 | 456 |
+----+---------------------+-------+
| 2 | 05/12/2017 20:00:00 | 789 |
+----+---------------------+-------+
| 3 | 11/11/2017 11:11:11 | 012 |
+----+---------------------+-------+
+-------------------------------------------------+
| correctiontable |
+-------------------------------------------------+
| id | value_id | corrdatetime | corrvalue |
+----+----------+---------------------+-----------+
| 1 | 2 | 01/08/2017 00:00:00 | 888 |
+----+----------+---------------------+-----------+
| 2 | 2 | 01/12/2017 10:00:00 | 999 |
+----+----------+---------------------+-----------+
| 3 | 1 | 01/08/2017 20:00:00 | 111 |
+----+----------+---------------------+-----------+
+--------------------------------------------------------------------+
| Result (as it should be) |
+--------------------------------------------------------------------+
| id | datetime | corrdatetime | value | corrvalue |
+----+---------------------+---------------------+-------+-----------+
| 1 | 22/07/2017 13:00:00 | NULL | 123 | NULL |
+----+---------------------+---------------------+-------+-----------+
| 2 | 10/08/2017 09:00:00 | 01/08/2017 00:00:00 | 456 | 888 |
+----+---------------------+---------------------+-------+-----------+
| 2 | 05/12/2017 20:00:00 | 01/12/2017 10:00:00 | 789 | 999 |
+----+---------------------+---------------------+-------+-----------+
| 3 | 11/11/2017 11:11:11 | NULL | 012 | NULL |
+----+---------------------+---------------------+-------+-----------+
Assuming "younger" means "logically less than", this should work for you.
select *
from valuetable a
outer apply (
select top 1 *
from correctiontable y
where y.value_id = a.id
and y.datetime < a.datetime
order by y.datetime desc
) b
Many Thanks to #KindaTechy for delivering me the right path!
I've created two querys, one for MySQL and one for >= Oracle 12.1
For MySQL:
SELECT *
FROM valuetable vt
LEFT JOIN correctiontable ON correctiontable.id
=
(SELECT corr.id
FROM correctiontable corr
WHERE vt.id = corr.value_id
AND vt.datetime <= corr.corrdatetime
ORDER BY datetime DESC
LIMIT 1)
For Oracle:
select *
from valuetable vt
outer apply (
select *
from correctiontable corr
where corr.value_id = vt.id
and corr.corrdatetime < vt.datetime
order by corr.corrdatetime desc
FETCH FIRST 1 ROWS ONLY
) b;
I found a working query, but your id column of valuetable should be unique, because otherwise you get a cross product.
SELECT vt.id, vt.datetime, corr.corrdatetime, vt.value, corr.corrvalue
FROM valuetable vt
LEFT JOIN correctiontable corr
ON corr.value_id = vt.id
AND vt.datetime >= corr.corrdatetime
By changing the date constraint from WHERE-CLAUSE to ON-CLAUSE it will impact only the join and not the result.
A made a sample for you http://sqlfiddle.com/#!9/301a6/4/0
When you need that non-unique id, the query must be improved. And also the test data set.