oracle track the history on a table with timestamp columns - sql

I have 2 tables in the oracle 12c database with the below structure. Table A has the incoming data from an application with modified date timestamps,
each day we may get around 50,000 rows in table A. the goal is to use the table table A's data and insert into the final target table B(usually has billions of rows)
by using table A's data as the driving data set.
A record needs to be inserted/merged in table B only when there is a change in the incoming dataset attributes.
basically the purpose is to track the history/journey of a given product with valid timestamps only when there are changes in its attributes such as state and zip_cd.
See table structures below
Table A ( PRODUCT_ID, STATE, ZIP_CD, Modified_dt)
'abc', 'MN', '123', '3/5/2020 12:01:00 AM'
'abc', 'MN', '123', '3/5/2020 6:01:13 PM'
'abc', 'IL', '223', '3/5/2020 7:01:15 PM'
'abc', 'OH', '333', '3/5/2020 6:01:16 PM'
'abc', 'NY', '722', '3/5/2020 4:29:00 PM'
'abc', 'KS', '444', '3/5/2020 4:31:41 PM'
'bbc', 'MN', '123', '3/19/2020 2:47:08 PM'
'bbc', 'IL', '223', '3/19/2020 2:50:37 PM'
'ccb', 'MN', '123', '3/21/2020 2:56:24 PM'
'dbd', 'KS', '444', '6/20/2020 12:00:00 AM'
Target Table B (SEQUENCE_KEY,PRODUCT_ID,STATE, ZIP_CD, Valid_From, Valid_To, LATEST_FLAG)
'1', 'abc', 'AR', '999', '3/3/2020 12:00:00 AM', '3/3/2020 6:01:13 PM', 'N'
'2', 'abc', 'AR', '555', '3/3/2020 6:01:14 PM', '3/3/2020 6:01:14 PM', 'N'
'3', 'abc', 'CA', '565', '3/3/2020 6:01:15 PM', '3/4/2020 4:28:59 PM', 'N'
'4', 'abc', 'CA', '777', '3/4/2020 4:29:00 PM', '12/31/2099', 'Y'
'5', 'bbc', 'MN', '123', '3/4/2020 4:31:41 PM', '3/19/2020 2:47:07 PM', 'N'
'6', 'bbc', 'MN', '666', '3/18/2020 2:47:08 PM', '3/19/2020 2:50:36 PM', 'N'
'7', 'bbc', 'MN', '777', '3/18/2020 2:50:37 PM', '12/31/2099', , 'Y'
'8', 'ccb', 'MN', '123', '3/20/2020 2:56:24 PM', '12/31/2099', 'Y'
Rules for populating data into table B:
the primary key on the output table is product_id and valid_from field.
the incoming data from table A will always have modified dt timestamps greather than the existing table.
inorder to insert data, we will have to compare latest_flag = 'Y' record from target table B and the incoming data from table A and only when there is a change
in the attributes state and zip_cd, then a record needs to be inserted in table B from table A. valid_to column is a calcuated field which is always 1 second lower than the
next row's valid from date, and for the latest row its defaulted to '12/31/2099'. Similary, latest_flag column is a calcuated column that indicates the current row of a given product_id
In the incoming dataset if there are multiple rows without any changes compared to the previous row or existing data in table B(latest_flag='Y') then
those should be ignored as well. as an example row 2 and row 9 from Table A are ignored as there are no changes in the attributes state, zip_cd when compared to their previous rows for that product.
Based on the above rules, I need to merge the table A data into table B and the final ouput looks like below
Table B (SEQUENCE_KEY,PRODUCT_ID,STATE, ZIP_CD, Valid_From, Valid_To, LATEST_FLAG)
'1', 'abc', 'AR', '999', '3/3/2020 12:00:00 AM', '3/3/2020 6:01:13 PM', 'N'
'2', 'abc', 'AR', '555', '3/3/2020 6:01:14 PM' '3/3/2020 6:01:14 PM', 'N'
'3', 'abc', 'CA', '565', '3/3/2020 6:01:15 PM' '3/4/2020 4:28:59 PM', 'N'
'4', 'abc', 'CA', '777', '3/4/2020 4:29:00 PM' '3/5/2020 12:00:00 AM', 'N'
'5', 'abc', 'MN', '123', '3/5/2020 12:01:00 AM', '3/5/2020 7:01:14 PM', 'N'
'6', 'abc', 'IL', '223' '3/5/2020 7:01:15 PM', '3/5/2020 6:01:15 PM', 'N'
'7', 'abc', 'OH', '333', '3/5/2020 6:01:16 PM', '3/5/2020 4:28:59 PM', 'N'
'8', 'abc', 'NY', '722', '3/5/2020 4:29:00 PM', '3/5/2020 4:31:40 PM', 'N'
'9', 'abc', 'KS', '444', '3/5/2020 4:31:41 PM', '12/31/2099', 'Y'
'10', 'bbc', 'MN', '123', '3/4/2020 4:31:41 PM' '3/19/2020 2:47:07 PM', 'N'
'11', 'bbc', 'MN', '666', '3/18/2020 2:47:08 PM' '3/19/2020 2:50:36 PM', 'N'
'12', 'bbc', 'MN', '777', '3/18/2020 2:50:37 PM' '3/19/2020 2:47:07 PM', 'N'
'13', 'bbc', 'MN', '123', '3/19/2020 2:47:08 PM' '3/19/2020 2:50:36 PM', 'N'
'14', 'bbc', 'IL', '223', '3/19/2020 2:50:37 PM' '12/31/2099', 'Y'
'15', 'ccb', 'MN', '123', '3/20/2020 2:56:24 PM' '12/31/2099', 'Y'
'16', 'dbd', 'KS', '444', '6/20/2020 12:00:00 AM' '12/31/2099', 'Y'
Looking for suggestions to solve this problem.
LIVE SQL link:
https://livesql.oracle.com/apex/livesql/s/kfbx7dwzr3zz28v6eigv0ars0
Thank you.

I tried to see how to do this in SQL but it was impossible to me because of the logic and also the sequence_key reset that you have in your desired ouput.
So, here my suggestion in PL/SQL
SQL> select * from table_a ;
PRODUCT_ID STATE ZIP_CD MODIFIED_
------------------------------ ------------------------------ ------------------------------ ---------
abc MN 123 05-MAR-20
abc MN 123 05-MAR-20
abc IL 223 05-MAR-20
abc OH 333 05-MAR-20
abc NY 722 05-MAR-20
abc KS 444 05-MAR-20
bbc MN 123 19-MAR-20
bbc IL 223 19-MAR-20
ccb MN 123 19-MAR-20
dbd KS 444 19-MAR-20
10 rows selected.
SQL> select * from table_b ;
SEQUENCE_KEY PRODUCT_ID STATE ZIP_CD VALID_FRO VALID_TO L
------------ ------------------------------ ------------------------------ ------------------------------ --------- --------- -
1 abc AR 999 05-MAR-20 05-MAR-20 N
2 abc AR 555 05-MAR-20 05-MAR-20 N
3 abc CA 565 05-MAR-20 05-MAR-20 N
4 abc CA 777 05-MAR-20 31-DEC-99 Y
5 bbc MN 123 05-MAR-20 05-MAR-20 N
6 bbc MN 666 05-MAR-20 05-MAR-20 N
7 bbc MN 777 19-MAR-20 31-DEC-99 Y
8 ccb MN 123 19-MAR-20 31-DEC-99 Y
8 rows selected.
Now, I used this piece of PL_SQL code
declare
type typ_rec_set IS RECORD
(
PRODUCT_ID VARCHAR2(30 CHAR),
STATE VARCHAR2(30 CHAR),
ZIP_CD VARCHAR2(30 CHAR),
VALID_FROM DATE ,
VALID_TO DATE ,
LATEST_FLAG VARCHAR2(1 CHAR)
);
type typ_rec_tab is TABLE OF typ_rec_set;
l_hdr_tab typ_rec_tab;
begin
SELECT product_id
,state
,zip_cd
,valid_from
,valid_to
,CASE WHEN valid_to = DATE '2099-12-31' THEN 'Y' ELSE 'N' END latest_flag
BULK COLLECT INTO l_hdr_tab
FROM
(
SELECT a.product_id
,a.state
,a.zip_cd
,a.modified_dt valid_from
,NVL(((LEAD (a.modified_dt,1) OVER (PARTITION BY a.product_id ORDER BY a.modified_dt)) - INTERVAL '1' SECOND),DATE '2099-12-31' )valid_to
,CASE
WHEN ( ( b.product_id IS NOT NULL
AND a.state != b.state
AND a.zip_cd != b.zip_cd)
OR b.product_id IS NULL
) THEN
1
ELSE
0
END insert_flag
FROM table_a a
LEFT OUTER JOIN table_b b
ON a.product_id = b.product_id
AND b.latest_flag = 'Y'
WHERE (a.modified_dt >= b.valid_from OR b.product_id IS NULL)
ORDER BY a.product_id,a.modified_dt
)
WHERE insert_flag != 0 ;
--loop
FOR i IN l_hdr_tab.first .. l_hdr_tab.last
LOOP
-- begin block
begin
insert into table_b
(
sequence_key ,
PRODUCT_ID ,
STATE ,
ZIP_CD ,
VALID_FROM ,
VALID_TO ,
LATEST_FLAG
)
values
(
( select max(sequence_key)+1 from table_b ),
l_hdr_tab(i).product_id ,
l_hdr_tab(i).state ,
l_hdr_tab(i).zip_cd ,
l_hdr_tab(i).valid_from ,
l_hdr_tab(i).valid_to ,
l_hdr_tab(i).latest_flag
);
end;
end loop;-- reset sequence base of row_number over product_id valid_from
commit;
-- reset sequence
merge into table_b t
using ( select sequence_key ,
PRODUCT_ID ,
STATE ,
ZIP_CD ,
VALID_FROM ,
VALID_TO ,
LATEST_FLAG ,
row_number() over ( order by product_id,valid_from ) as new_seq
from table_b ) s
on ( s.rowid = t.rowid )
when matched then
update set t.sequence_key = s.new_seq where t.sequence_key != s.new_seq ;
commit;
exception when others then raise;
end;
/
Then I run it
SQL> host cat proc.sql
declare
type typ_rec_set IS RECORD
(
PRODUCT_ID VARCHAR2(30 CHAR),
STATE VARCHAR2(30 CHAR),
ZIP_CD VARCHAR2(30 CHAR),
VALID_FROM DATE ,
VALID_TO DATE ,
LATEST_FLAG VARCHAR2(1 CHAR)
);
type typ_rec_tab is TABLE OF typ_rec_set;
l_hdr_tab typ_rec_tab;
begin
SELECT product_id
,state
,zip_cd
,valid_from
,valid_to
,CASE WHEN valid_to = DATE '2099-12-31' THEN 'Y' ELSE 'N' END latest_flag
BULK COLLECT INTO l_hdr_tab
FROM
(
SELECT a.product_id
,a.state
,a.zip_cd
,a.modified_dt valid_from
,NVL(((LEAD (a.modified_dt,1) OVER (PARTITION BY a.product_id ORDER BY a.modified_dt)) - INTERVAL '1' SECOND),DATE '2099-12-31' )valid_to
,CASE
WHEN ( ( b.product_id IS NOT NULL
AND a.state != b.state
AND a.zip_cd != b.zip_cd)
OR b.product_id IS NULL
) THEN
1
ELSE
0
END insert_flag
FROM table_a a
LEFT OUTER JOIN table_b b
ON a.product_id = b.product_id
AND b.latest_flag = 'Y'
WHERE (a.modified_dt >= b.valid_from OR b.product_id IS NULL)
ORDER BY a.product_id,a.modified_dt
)
WHERE insert_flag != 0 ;
--loop
FOR i IN l_hdr_tab.first .. l_hdr_tab.last
LOOP
-- begin block
begin
insert into table_b
(
sequence_key ,
PRODUCT_ID ,
STATE ,
ZIP_CD ,
VALID_FROM ,
VALID_TO ,
LATEST_FLAG
)
values
(
( select max(sequence_key)+1 from table_b ),
l_hdr_tab(i).product_id ,
l_hdr_tab(i).state ,
l_hdr_tab(i).zip_cd ,
l_hdr_tab(i).valid_from ,
l_hdr_tab(i).valid_to ,
l_hdr_tab(i).latest_flag
);
end;
end loop;-- reset sequence base of row_number over product_id valid_from
commit;
-- reset sequence
merge into table_b t
using ( select sequence_key ,
PRODUCT_ID ,
STATE ,
ZIP_CD ,
VALID_FROM ,
VALID_TO ,
LATEST_FLAG ,
row_number() over ( order by product_id,valid_from ) as new_seq
from table_b ) s
on ( s.rowid = t.rowid )
when matched then
update set t.sequence_key = s.new_seq where t.sequence_key != s.new_seq ;
commit;
exception when others then raise;
end;
/
SQL> #proc.sql
PL/SQL procedure successfully completed.
SQL> select * from table_b order by sequence_key ;
SEQUENCE_KEY PRODUCT_ID STATE ZIP_CD VALID_FRO VALID_TO L
------------ ------------------------------ ------------------------------ ------------------------------ --------- --------- -
1 abc AR 999 05-MAR-20 05-MAR-20 N
2 abc NY 722 05-MAR-20 05-MAR-20 N
3 abc CA 777 05-MAR-20 31-DEC-99 Y
4 abc KS 444 05-MAR-20 05-MAR-20 N
5 abc MN 123 05-MAR-20 05-MAR-20 N
6 abc AR 555 05-MAR-20 05-MAR-20 N
7 abc CA 565 05-MAR-20 05-MAR-20 N
8 abc OH 333 05-MAR-20 05-MAR-20 N
9 abc IL 223 05-MAR-20 31-DEC-99 Y
10 bbc MN 666 05-MAR-20 05-MAR-20 N
11 bbc MN 123 05-MAR-20 05-MAR-20 N
SEQUENCE_KEY PRODUCT_ID STATE ZIP_CD VALID_FRO VALID_TO L
------------ ------------------------------ ------------------------------ ------------------------------ --------- --------- -
12 bbc MN 777 19-MAR-20 31-DEC-99 Y
13 bbc IL 223 19-MAR-20 31-DEC-99 Y
14 ccb MN 123 19-MAR-20 31-DEC-99 Y
15 dbd KS 444 19-MAR-20 31-DEC-99 Y
15 rows selected.
SQL>
Just let me know any doubts you might have. I know that for sure I miss something ;)
UPDATE
I realized that I have an useless operation in the loop, the calculation of the maxvalue for the field SEQUENCE_KEY. I have a better version of the procedure here:
declare
type typ_rec_set IS RECORD
(
PRODUCT_ID VARCHAR2(30 CHAR),
STATE VARCHAR2(30 CHAR),
ZIP_CD VARCHAR2(30 CHAR),
VALID_FROM DATE ,
VALID_TO DATE ,
LATEST_FLAG VARCHAR2(1 CHAR)
);
type typ_rec_tab is TABLE OF typ_rec_set;
l_hdr_tab typ_rec_tab;
r pls_integer := 1;
vseq pls_integer;
begin
-- calculate value sequence
select max(sequence_key) into vseq from table_b ;
SELECT product_id
,state
,zip_cd
,valid_from
,valid_to
,CASE WHEN valid_to = DATE '2099-12-31' THEN 'Y' ELSE 'N' END latest_flag
BULK COLLECT INTO l_hdr_tab
FROM
(
SELECT a.product_id
,a.state
,a.zip_cd
,a.modified_dt valid_from
,NVL(((LEAD (a.modified_dt,1) OVER (PARTITION BY a.product_id ORDER BY a.modified_dt)) - INTERVAL '1' SECOND),DATE '2099-12-31' )valid_to
,CASE
WHEN ( ( b.product_id IS NOT NULL
AND a.state != b.state
AND a.zip_cd != b.zip_cd)
OR b.product_id IS NULL
) THEN
1
ELSE
0
END insert_flag
FROM table_a a
LEFT OUTER JOIN table_b b
ON a.product_id = b.product_id
AND b.latest_flag = 'Y'
WHERE (a.modified_dt >= b.valid_from OR b.product_id IS NULL)
ORDER BY a.product_id,a.modified_dt
)
WHERE insert_flag != 0 ;
--loop
FOR i IN l_hdr_tab.first .. l_hdr_tab.last
LOOP
-- begin block
vseq := vseq + r ;
begin
insert into table_b
(
sequence_key ,
PRODUCT_ID ,
STATE ,
ZIP_CD ,
VALID_FROM ,
VALID_TO ,
LATEST_FLAG
)
values
(
vseq ,
l_hdr_tab(i).product_id ,
l_hdr_tab(i).state ,
l_hdr_tab(i).zip_cd ,
l_hdr_tab(i).valid_from ,
l_hdr_tab(i).valid_to ,
l_hdr_tab(i).latest_flag
);
end;
r := r + 1;
end loop;-- reset sequence base of row_number over product_id valid_from
commit;
-- reset sequence
merge into table_b t
using ( select sequence_key ,
PRODUCT_ID ,
STATE ,
ZIP_CD ,
VALID_FROM ,
VALID_TO ,
LATEST_FLAG ,
row_number() over ( order by product_id,valid_from ) as new_seq
from table_b ) s
on ( s.rowid = t.rowid )
when matched then
update set t.sequence_key = s.new_seq where t.sequence_key != s.new_seq ;
commit;
exception when others then raise;
end;
/

I would give my first try with the understanding I have. The cursor as source for inserting to TableB would look like,
SELECT product_id
,state
,zip_cd
,valid_from
,valid_to
,CASE WHEN valid_to = DATE '2099-12-31' THEN 'Y' ELSE 'N' END latest_flag
FROM
(
SELECT a.product_id
,a.state
,a.zip_cd
,a.modified_dt valid_from
,NVL(((LEAD (a.modified_dt,1) OVER (PARTITION BY a.product_id ORDER BY a.modified_dt)) - INTERVAL '1' SECOND),DATE '2099-12-31' )valid_to
,CASE
WHEN ( ( b.product_id IS NOT NULL
AND a.state != b.state
AND a.zip_cd != b.zip_cd)
OR b.product_id IS NULL
) THEN
1
ELSE
0
END insert_flag
FROM table_a a
LEFT OUTER JOIN table_b b
ON a.product_id = b.product_id
AND b.latest_flag = 'Y'
WHERE (a.modified_dt >= b.valid_from OR b.product_id IS NULL)
ORDER BY a.product_id,a.modified_dt
)
WHERE insert_flag != 0;
LEFT OUTER JOIN to check if the record exists in TableB and the WHERE clause checks for modified_date greater than the valid_from for the latest_flag = 'Y'
Inner Case statement will tell us whether the attributes are changed or not and in case the product_id is not present it also consider it as first entry and the insert_flag will be 1
Outer case statement provides the valid_to in case of last record as per modified date column to 31-12-2099
Not completely clear with respect to point 3 but I believe the case statement is what we need for.
At the end I didn't consider the performance problem here. you can think of converting it to PL/SQL block and other collection methods to process data in chunk.
Also I have here one question , what happens to the record with product id "dbd" (which is a new entry and doesn't exists in TableB) if present multiple times in tableA ?

This is Slowly Changing Dimensions (SCD) Type 2 problem in data warehousing (Kimball approach). You can see a short definitions here
https://www.oracle.com/webfolder/technetwork/tutorials/obe/db/10g/r2/owb/owb10gr2_gs/owb/lesson3/slowlychangingdimensions.htm
Support for SCD Type 2 is available in Enterprise ETL option of OWB 10gR2 only as described in the above link. If that's not available and you have to use PL/SQL, you can check out the following approach. Unfortunately, Oracle PL/SQL does not offer a straight forward solution unlike MS SQL.
Implementing Type 2 SCD in Oracle

Related

Denormalize column

I have data in my database like this:
Code
meta
meta_ID
date
A
1,2
1
01/01/2022 08:08:08
B
1,2
2
01/01/2022 02:00:00
B
null
2
01/01/1900 02:00:00
C
null
3
01/01/2022 02:00:00
D
8
8
01/01/2022 02:00:00
E
5,6,7
5
01/01/2022 02:00:00
F
1,2
2
01/01/2022 02:00:00
I want to have this with the last date (comparing with day, month year)
Code
meta
meta_ID
list_Code
date
A
2,3
1
A,B,F
01/01/2022 08:08:08
B
1,3
2
A,B,F
01/01/2022 02:00:00
C
null
3
C
01/01/2022 02:00:00
D
8
8
D
01/01/2022 02:00:00
E
5,6,7
5
E
01/01/2022 02:00:00
F
1,2
3
A,B,F
01/01/2022 02:00:00
I want to have the list of code having the same meta group, do you know how to do it with SQL Server?
The code below inputs the 1st table and outputs the 2nd table exactly. The Meta and Date columns had duplicate values, so in the CTE I took the MAX for both fields. Different logic can be applied if needed.
It uses XML Path to merge all rows into one column to create the List_Code column. The Stuff function removes the leading comma (,) delimiter.
CREATE TABLE MetaTable
(
Code VARCHAR(5),
Meta VARCHAR(100),
Meta_ID INT,
Date DATETIME
)
GO
INSERT INTO MetaTable
VALUES
('A', '1,2', '1', '01/01/2022 08:08:08'),
('B', '1,2','2', '01/01/2022 02:00:00'),
('B', NULL,'2', '01/01/1900 02:00:00'),
('C', NULL,'3', '01/01/2022 02:00:00'),
('D', '8','8', '01/01/2022 02:00:00'),
('E', '5,6,7', '5', '01/01/2022 02:00:00'),
('F', '1,2','2', '01/01/2022 02:00:00')
GO
WITH CTE_Meta
AS
(
SELECT
Code,
MAX(Meta) AS 'Meta',
Meta_ID,
MAX(Date) AS 'Date'
FROM MetaTable
GROUP BY
Code,
Meta_ID
)
SELECT
T1.Code,
T1.Meta,
T1.Meta_ID,
STUFF
(
(
SELECT ',' + Code
FROM CTE_Meta T2
WHERE ISNULL(T1.Meta, '') = ISNULL(T2.Meta, '')
FOR XML PATH('')
), 1, 1, ''
) AS 'List_Code',
T1.Date
FROM CTE_Meta T1
ORDER BY 1
I like the first answer using XML. It's very concise. This is more verbose, but might be more flexible if the data can have different meta values spread about in different records. The CAST to varchar(12) in various places is just for the display. I use STRING_AGG and STRING_SPLIT instead of XML.
WITH TestData as (
SELECT t.*
FROM (
Values
('A', '1,2', '1', '01/01/2022 08:08:08'),
('B', '1,2', '2', '01/01/2022 02:00:00'),
('B', null, '2', '01/01/1900 02:00:00'),
('C', null, '3', '01/01/2022 02:00:00'),
('D', '8', '8', '01/01/2022 02:00:00'),
('E', '5,6,7', '5', '01/01/2022 02:00:00'),
('F', '1,2', '2', '01/01/2022 02:00:00'),
('G', '16', '17', '01/01/2022 02:00:00'),
('G', null, '17', '01/02/2022 03:00:00'),
('G', '19', '18', '01/03/2022 04:00:00'),
('G', '19', '18', '01/03/2022 04:00:00'),
('G', '20', '19', '01/04/2022 05:00:00'),
('G', '20', '20', '01/05/2022 06:00:00')
) t (Code, meta, meta_ID, date)
), CodeLookup as ( -- used to find the Code from the meta_ID
SELECT DISTINCT meta_ID, Code
FROM TestData
), Normalized as ( -- split out the meta values, one per row
SELECT t.Code, s.Value as [meta], meta_ID, [date]
FROM TestData t
OUTER APPLY STRING_SPLIT(t.meta, ',') s
), MetaLookup as ( -- used to find the distinct list of meta values for a Code
SELECT n.Code, CAST(STRING_AGG(n.meta, ',') WITHIN GROUP ( ORDER BY n.meta ASC ) as varchar(12)) as [meta]
FROM (
SELECT DISTINCT Code, meta
FROM Normalized
WHERE meta is not NULL
) n
GROUP BY n.Code
), MetaIdLookup as ( -- used to find the distinct list of meta_ID values for a Code
SELECT n.Code, CAST(STRING_AGG(n.meta_ID, ',') WITHIN GROUP ( ORDER BY n.meta_ID ASC ) as varchar(12)) as [meta_ID]
FROM (
SELECT DISTINCT Code, meta_ID
FROM Normalized
) n
GROUP BY n.Code
), ListCodeLookup as ( -- for every code, get all codes for the meta values
SELECT l.Code, CAST(STRING_AGG(l.lookupCode, ',') WITHIN GROUP ( ORDER BY l.lookupCode ASC ) as varchar(12)) as [list_Code]
FROM (
SELECT DISTINCT n.Code, c.Code as [lookupCode]
FROM Normalized n
INNER JOIN CodeLookup c
ON c.meta_ID = n.meta
UNION -- every record needs it's own code in the list_code?
SELECT DISTINCT n.Code, n.Code as [lookupCode]
FROM Normalized n
) l
GROUP BY l.Code
)
SELECT t.Code, m.meta, mi.meta_ID, lc.list_Code, t.[date]
FROM (
SELECT Code, MAX([date]) as [date]
FROM TestData
GROUP BY Code
) t
LEFT JOIN MetaLookup m
ON m.Code = t.Code
LEFT JOIN MetaIdLookup mi
ON mi.Code = t.Code
LEFT JOIN ListCodeLookup lc
ON lc.Code = t.Code
Code meta meta_ID list_Code date
---- ------------ ------------ ------------ -------------------
A 1,2 1 A,B,F 01/01/2022 08:08:08
B 1,2 2 A,B,F 01/01/2022 02:00:00
C NULL 3 C 01/01/2022 02:00:00
D 8 8 D 01/01/2022 02:00:00
E 5,6,7 5 E 01/01/2022 02:00:00
F 1,2 2 A,B,F 01/01/2022 02:00:00
G 16,19,20 17,18,19,20 G 01/05/2022 06:00:00

need to get a subsequent record with a specific value

i have the following table :
Dt Status
05.23.2019 10:00:00 A
05.23.2019 11:00:00 B
05.23.2019 12:00:00 B
05.23.2019 13:00:00 D
05.23.2019 14:00:00 A
05.23.2019 15:00:00 B
05.23.2019 16:00:00 C
05.23.2019 17:00:00 D
05.23.2019 18:00:00 A
For each status A i need to get the next status D. The result should be like this :
Status1 Status2 Dt1 Dt2
A D 05.23.2019 10:00:00 05.23.2019 13:00:00
A D 05.23.2019 14:00:00 05.23.2019 17:00:00
A null 05.23.2019 18:00:00 null
I have my own solution based on cross/outer apply , In terms of performance i need solution without cross/outer apply.
We can try using ROW_NUMBER here along with some pivot logic:
WITH cte AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY Status ORDER BY Dt) rn
FROM yourTable
WHERE Status IN ('A', 'D')
)
SELECT
MAX(CASE WHEN Status = 'A' THEN Status END) AS Status1,
MAX(CASE WHEN Status = 'D' THEN Status END) AS Status2,
MAX(CASE WHEN Status = 'A' THEN Dt END) AS Dt1,
MAX(CASE WHEN Status = 'D' THEN Dt END) AS Dt2
FROM cte
GROUP BY rn
ORDER BY rn;
Demo
The idea here is to generate a row number sequence along your entire table, for each separate Status value (A or D). Then, aggregate by that row number sequence to bring the A and D records together.
As result columns Status1 and Status2 always seem to be "A" and "D" respectively, I omitted them in my result.
DECLARE #Data TABLE
(
[Dt] SMALLDATETIME,
[Status] CHAR(1)
);
INSERT INTO #Data ([Dt], [Status]) VALUES
('2019-05-23 10:00:00', 'A'),
('2019-05-23 11:00:00', 'B'),
('2019-05-23 12:00:00', 'B'),
('2019-05-23 13:00:00', 'D'),
('2019-05-23 14:00:00', 'A'),
('2019-05-23 15:00:00', 'B'),
('2019-05-23 16:00:00', 'C'),
('2019-05-23 17:00:00', 'D'),
('2019-05-23 18:00:00', 'A'),
('2019-05-23 19:00:00', 'D'),
('2019-05-23 20:00:00', 'D'),
('2019-05-23 21:00:00', 'A'),
('2019-05-23 22:00:00', 'A'),
('2019-05-23 23:00:00', 'A');
SELECT
D.[Dt] AS [Dt1],
[LastDBeforeNextA].[Dt] AS [Dt2]
FROM
#Data AS D
OUTER APPLY (SELECT TOP (1) [Dt]
FROM #Data
WHERE [Status] = 'A' AND [Dt] > D.[Dt]
ORDER BY [Dt]) AS [NextA]
OUTER APPLY (SELECT TOP (1) [Dt]
FROM #Data
WHERE [Status] = 'D' AND [Dt] < [NextA].[Dt] AND [Dt] > D.[Dt]
ORDER BY [Dt] DESC) AS [LastDBeforeNextA]
WHERE
D.[Status] = 'A' AND
([NextA].[Dt] > [LastDBeforeNextA].[Dt] OR ([LastDBeforeNextA].[Dt] IS NULL AND [NextA].[Dt] IS NULL))
It initially gets all records from the table where status is 'A' (using expression D.[Status] = 'A' in the WHERE-clause).
For each record found, it joins the date of the next record with status A (table expression with alias NextA) and the date of the last record with status D that comes right before the next A-record but after the current A-record (table expression with alias LastDBeforeNextA).
Results are valid when a D-record is found (expression [NextA].[Dt] > [LastDBeforeNextA].[Dt] in the WHERE-clause) or when there is no D-record yet (expression [LastDBeforeNextA].[Dt] IS NULL in the WHERE-clause). In the latter case, you need to get the latest A-record, however (expression [NextA].[Dt] IS NULL in the WHERE-clause), since there can be multiple A-records after the last D-record.

Query to compare dates patients are missing from hospital census

I have a hospital bed census that is triggered and creates a date/time stamped row in a table. when the bed check portion is done it labels the event census. i have found that some patients on days they were in the hospital have not been timestamped with the event census. I am trying to write a query to capture all patients that may have had this issue.
i need to capture the patients between their admit and discharge dates, and then any day they do not have a time stamp event of census. for example, this patient does not have a census on the 12th or 13th but does on the 14th. i want to be able to pull this pat_id and dates they are not stamped with census.
11-APR-2019 11:59:00 PM CENSUS
12-APR-2019 03:12:00 PM TRANSFER OUT
12-APR-2019 03:12:00 PM TRANSFER IN
14-APR-2019 07:06:00 AM PATIENT UPDATE
14-APR-2019 11:40:00 AM TRANSFER OUT
14-APR-2019 11:40:00 AM TRANSFER IN
14-APR-2019 11:59:00 PM CENSUS
I created a calendar portion to my query. then i created a query to capture patients in a time frame. from there i am a bit stuck.
DATE1
AS
(select
to_char(dates,'MM/DD/YYYY') AS WEEK_DATE,
dates,
to_char(dates,'D') weekday,
to_char(dates,'mm') m_onth,
to_char(dates,'ww') week_of_year,
to_char(dates,'dd') month_day,
to_char(dates,'ddd') Year_day,
SUBSTR(dates,1,2) AS WEEKDATE
from (SELECT TRUNC(to_date(v.yyyy,'YYYY'),'YY') +LEVEL - 1 DATES
FROM ( SELECT 2019 yyyy FROM dual ) v
CONNECT BY LEVEL < 366
)
)
,
ADT
AS (select distinct
adt.pat_id,
peh.y_mrn,
adt.DEPARTMENT_ID,
adp.department_name,
--peh.HOSP_ADMSN_TIME,
to_char(peh.HOSP_ADMSN_TIME,'MM/DD/YYYY') AS HOSP_ADMSN_TIME2,
--peh.HOSP_DISCH_TIME,
to_char(peh.HOSP_DISCH_TIME,'MM/DD/YYYY') AS HOSP_DISCH_TIME2,
adt.effective_time,
to_char(aDT.effective_time,'MM/DD/YYYY') AS EFFECT_DATE,
--LEAD(adt.effective_time) over (partition by ADT.pat_id order by ADT.pat_id, adt.effective_time) AS NEXT_EFF_DATE,
--CASE WHEN adt.event_type_c =6 THEN adt.effective_time END AS CENSUS_DATE,
et.title as event_type,
adt.event_type_c,
peh.ADT_PAT_CLASS_C,
Adt.event_subtype_c--,
--LAG(adt.effective_time) over (partition by ADT.pat_id order by ADT.pat_id, adt.effective_time) AS PREV_EFF_DATE
from
clarity_adt adt
left OUTER join
pat_enc_hsp peh
on
peh.pat_enc_csn_id = adt.pat_enc_csn_id
left outer join
clarity_dep adp
on adt.department_id = adp.department_id
left OUTER join
zc_event_type et
on adt.event_type_c = et.event_type_c
where
adt.effective_time between '08-apr-2019' and '15-apr-2019'
order by adt.effective_time
)
,
ADT2
AS
(
SELECT-- DISTINCT
D.WEEK_DATE,
A.HOSP_ADMSN_TIME2,
A.EFFECT_DATE,
A.PAT_ID,
CASE WHEN D.WEEK_DATE IS NOT NULL AND A.EFFECT_DATE IS NULL AND A.event_type <> 'CENSUS' THEN 1
WHEN D.WEEK_DATE IS NOT NULL AND A.EFFECT_DATE IS NULL AND A.event_type IS NULL THEN 1
WHEN D.WEEK_DATE IS NOT NULL AND A.EFFECT_DATE IS NOT NULL AND A.event_type <> 'CENSUS' THEN 1 ELSE 0
END AS NO_ADT_INFO,
A.event_type,
A.HOSP_DISCH_TIME2
FROM
DATE2 D
LEFT OUTER JOIN
ADT A
ON
D.WEEK_DATE = A.EFFECT_DATE
ORDER BY
D.WEEK_DATE)
i would like to end up with the patient id, the day of the week they have no census, the hosp admission & discharge dates
PAT_ID WEEK_DATE EVENT_TYPE HOSP_ADMSN_TIME HOSP_DISCH_TIME
ABCDEF 4/12/2019 NO CENSUS 4/10/2019 4/19/2019
ABCDEF 4/13/2019 NO CENSUS 4/10/2019 4/19/2019
GHIJK 4/8/2019 NO CENSUS 4/2/2019 4/12/2019
GHIJK 4/11/2019 NO CENSUS 4/2/2019 4/12/2019
Here is sample data for two patients:
events(pat_id, event_date, event_type) as (
select 'ABCD', to_date('2019-04-11 23:59', 'yyyy-mm-dd hh24:mi'), 'CENSUS' from dual union all
select 'ABCD', to_date('2019-04-12 15:12', 'yyyy-mm-dd hh24:mi'), 'TRANSFER OUT' from dual union all
select 'ABCD', to_date('2019-04-12 15:12', 'yyyy-mm-dd hh24:mi'), 'TRANSFER IN' from dual union all
select 'ABCD', to_date('2019-04-14 07:06', 'yyyy-mm-dd hh24:mi'), 'PATIENT UPDATE' from dual union all
select 'ABCD', to_date('2019-04-14 11:40', 'yyyy-mm-dd hh24:mi'), 'TRANSFER OUT' from dual union all
select 'ABCD', to_date('2019-04-14 11:40', 'yyyy-mm-dd hh24:mi'), 'TRANSFER IN' from dual union all
select 'ABCD', to_date('2019-04-14 23:59', 'yyyy-mm-dd hh24:mi'), 'CENSUS' from dual union all
select 'GHIJ', to_date('2019-05-17 23:59', 'yyyy-mm-dd hh24:mi'), 'CENSUS' from dual union all
select 'GHIJ', to_date('2019-05-19 23:59', 'yyyy-mm-dd hh24:mi'), 'CENSUS' from dual ),
peh(pat_id, hosp_admsn_time, hosp_disch_time) as (
select 'ABCD', date '2019-04-11', date '2019-04-14' from dual union all
select 'GHIJ', date '2019-05-17', date '2019-05-20' from dual ),
You can create recursive query generating days for each patient and check if there is CENSUS event for each of these days:
with cte(pat_id, num, adm, dis) as (
select pat_id, 0, hosp_admsn_time, hosp_disch_time from peh
union all
select pat_id, num + 1, adm, dis from cte where num < dis - adm)
select pat_id, day, 'NO CENSUS' info, adm, dis
from (select pat_id, adm + num day, adm, dis from cte) d
where not exists (
select 1
from events
where pat_id = d.pat_id and trunc(event_date) = d.day and event_type = 'CENSUS')
order by pat_id, day;
Result:
PAT_ID DAY INFO ADM DIS
------ ----------- --------- ----------- -----------
ABCD 2019-04-12 NO CENSUS 2019-04-11 2019-04-14
ABCD 2019-04-13 NO CENSUS 2019-04-11 2019-04-14
GHIJ 2019-05-18 NO CENSUS 2019-05-17 2019-05-20
GHIJ 2019-05-20 NO CENSUS 2019-05-17 2019-05-20
dbfiddle demo

SQL count consecutive rows

I have the following data in a table:
|event_id |starttime |person_id|attended|
|------------|-----------------|---------|--------|
| 11512997-1 | 01-SEP-16 08:00 | 10001 | N |
| 11512997-2 | 01-SEP-16 10:00 | 10001 | N |
| 11512997-3 | 01-SEP-16 12:00 | 10001 | N |
| 11512997-4 | 01-SEP-16 14:00 | 10001 | N |
| 11512997-5 | 01-SEP-16 16:00 | 10001 | N |
| 11512997-6 | 01-SEP-16 18:00 | 10001 | Y |
| 11512997-7 | 02-SEP-16 08:00 | 10001 | N |
| 11512997-1 | 01-SEP-16 08:00 | 10002 | N |
| 11512997-2 | 01-SEP-16 10:00 | 10002 | N |
| 11512997-3 | 01-SEP-16 12:00 | 10002 | N |
| 11512997-4 | 01-SEP-16 14:00 | 10002 | Y |
| 11512997-5 | 01-SEP-16 16:00 | 10002 | N |
| 11512997-6 | 01-SEP-16 18:00 | 10002 | Y |
| 11512997-7 | 02-SEP-16 08:00 | 10002 | Y |
I want to produce the following results, where the maximum number of consecutive occurences where atended = 'N' is returned:
|person_id|consec_missed_max|
| 1001 | 5 |
| 1002 | 3 |
How could this be done in Oracle (or ANSI) SQL? Thanks!
Edit:
So far I have tried:
WITH t1 AS
(SELECT t.person_id,
row_number() over(PARTITION BY t.person_id ORDER BY t.starttime) AS idx
FROM the_table t
WHERE t.attended = 'N'),
t2 AS
(SELECT person_id, MAX(idx) max_idx FROM t1 GROUP BY person_id)
SELECT t1.person_id, COUNT(1) ct
FROM t1
JOIN t2
ON t1.person_id = t2.person_id
GROUP BY t1.person_id;
The main work is in the factored subquery "prep". You seem to be somewhat familiar with analytic function, but that is not enough. This solution uses the so-called "tabibitosan" method to create groups of consecutive rows with the same characteristic in one or more dimensions; in this case, you want to group consecutive N rows with a different group for each sequence. This is done with a difference of two ROW_NUMBER() calls - one partitioned by person only, and the other by person and attended. Google "tabibitosan" to read more about the idea if needed.
with
inputs ( event_id, starttime, person_id, attended ) as (
select '11512997-1', to_date('01-SEP-16 08:00', 'dd-MON-yy hh24:mi'), 10001, 'N' from dual union all
select '11512997-2', to_date('01-SEP-16 10:00', 'dd-MON-yy hh24:mi'), 10001, 'N' from dual union all
select '11512997-3', to_date('01-SEP-16 12:00', 'dd-MON-yy hh24:mi'), 10001, 'N' from dual union all
select '11512997-4', to_date('01-SEP-16 14:00', 'dd-MON-yy hh24:mi'), 10001, 'N' from dual union all
select '11512997-5', to_date('01-SEP-16 16:00', 'dd-MON-yy hh24:mi'), 10001, 'N' from dual union all
select '11512997-6', to_date('01-SEP-16 18:00', 'dd-MON-yy hh24:mi'), 10001, 'Y' from dual union all
select '11512997-7', to_date('02-SEP-16 08:00', 'dd-MON-yy hh24:mi'), 10001, 'N' from dual union all
select '11512997-1', to_date('01-SEP-16 08:00', 'dd-MON-yy hh24:mi'), 10002, 'N' from dual union all
select '11512997-2', to_date('01-SEP-16 10:00', 'dd-MON-yy hh24:mi'), 10002, 'N' from dual union all
select '11512997-3', to_date('01-SEP-16 12:00', 'dd-MON-yy hh24:mi'), 10002, 'N' from dual union all
select '11512997-4', to_date('01-SEP-16 14:00', 'dd-MON-yy hh24:mi'), 10002, 'Y' from dual union all
select '11512997-5', to_date('01-SEP-16 16:00', 'dd-MON-yy hh24:mi'), 10002, 'N' from dual union all
select '11512997-6', to_date('01-SEP-16 18:00', 'dd-MON-yy hh24:mi'), 10002, 'Y' from dual union all
select '11512997-7', to_date('02-SEP-16 08:00', 'dd-MON-yy hh24:mi'), 10002, 'Y' from dual
),
prep ( starttime, person_id, attended, gp ) as (
select starttime, person_id, attended,
row_number() over (partition by person_id order by starttime) -
row_number() over (partition by person_id, attended
order by starttime)
from inputs
),
counts ( person_id, consecutive_absences ) as (
select person_id, count(*)
from prep
where attended = 'N'
group by person_id, gp
)
select person_id, max(consecutive_absences) as max_consecutive_absences
from counts
group by person_id
order by person_id;
OUTPUT:
PERSON_ID MAX_CONSECUTIVE_ABSENCES
---------- ---------------------------------------
10001 5
10002 3
If you are using Oracle 12c you could use MATCH_RECOGNIZE:
Data:
CREATE TABLE data AS
SELECT *
FROM (
with inputs ( event_id, starttime, person_id, attended ) as (
select '11512997-1', to_date('01-SEP-16 08:00', 'dd-MON-yy hh24:mi'), 10001, 'N' from dual union all
select '11512997-2', to_date('01-SEP-16 10:00', 'dd-MON-yy hh24:mi'), 10001, 'N' from dual union all
select '11512997-3', to_date('01-SEP-16 12:00', 'dd-MON-yy hh24:mi'), 10001, 'N' from dual union all
select '11512997-4', to_date('01-SEP-16 14:00', 'dd-MON-yy hh24:mi'), 10001, 'N' from dual union all
select '11512997-5', to_date('01-SEP-16 16:00', 'dd-MON-yy hh24:mi'), 10001, 'N' from dual union all
select '11512997-6', to_date('01-SEP-16 18:00', 'dd-MON-yy hh24:mi'), 10001, 'Y' from dual union all
select '11512997-7', to_date('02-SEP-16 08:00', 'dd-MON-yy hh24:mi'), 10001, 'N' from dual union all
select '11512997-1', to_date('01-SEP-16 08:00', 'dd-MON-yy hh24:mi'), 10002, 'N' from dual union all
select '11512997-2', to_date('01-SEP-16 10:00', 'dd-MON-yy hh24:mi'), 10002, 'N' from dual union all
select '11512997-3', to_date('01-SEP-16 12:00', 'dd-MON-yy hh24:mi'), 10002, 'N' from dual union all
select '11512997-4', to_date('01-SEP-16 14:00', 'dd-MON-yy hh24:mi'), 10002, 'Y' from dual union all
select '11512997-5', to_date('01-SEP-16 16:00', 'dd-MON-yy hh24:mi'), 10002, 'N' from dual union all
select '11512997-6', to_date('01-SEP-16 18:00', 'dd-MON-yy hh24:mi'), 10002, 'Y' from dual union all
select '11512997-7', to_date('02-SEP-16 08:00', 'dd-MON-yy hh24:mi'), 10002, 'Y' from dual
)
SELECT * FROM inputs
);
And query:
SELECT PERSON_ID, MAX(LEN) AS MAX_ABSENCES_IN_ROW
FROM data
MATCH_RECOGNIZE (
PARTITION BY PERSON_ID
ORDER BY STARTTIME
MEASURES FINAL COUNT(*) AS len
ALL ROWS PER MATCH
PATTERN(a b*)
DEFINE b AS attended = a.attended
)
WHERE attended = 'N'
GROUP BY PERSON_ID;
Output:
"PERSON_ID","MAX_ABSENCES_IN_ROW"
10001,5
10002,3
EDIT:
As #mathguy pointed it could be rewritten as:
SELECT PERSON_ID, MAX(LEN) AS MAX_ABSENCES_IN_ROW
FROM data
MATCH_RECOGNIZE (
PARTITION BY PERSON_ID
ORDER BY STARTTIME
MEASURES COUNT(*) AS len
PATTERN(a+)
DEFINE a AS attended = 'N'
)
GROUP BY PERSON_ID;
db<>fiddle demo

Update a record in table 1 with a record from table 2 based on a date in table 1

I'm new to the site, and I'm hoping you can help...
I'm trying to stamp the LegNumber from Table 2 into Table 1 based on the datetime of the record in Table1 falling between the datetime of the record(s) from Table 2.
In my example, the records in Table 1 with a datetime that falls between 4/5/16 4:02 AM and 4/7/16 6:53 AM should be stamped with LegNumber 1862410 (from Table 2) based on the datetimes of 4/5/16 8:14 AM thru 4/5/16 4:09 PM. Hopefully, I haven't made this inquiry too confusing.
At first we create to cte's and gather time intervals, then select to show output:
;WITH LegsRowNumbers AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY LegStartDate ASC) as rn
FROM Table2 t2
), GetLegsIntervals AS (
SELECT l1.Tractor,
--Here may be another format, check CAST and CONVERT article on MSDN
CONVERT(datetime,l1.LegStartDate, 120) as LegStartDate,
DATEADD(minute,-1,l2.LegStartDate) as LegEndDate,
l1.LegNumber
FROM LegsRowNumbers l1
LEFT JOIN LegsRowNumbers l2
ON l2.rn = l1.rn+1
)
SELECT t.Tractor,
t.TollExitDateTime,
g.LegNumber
FROM Table1 t
LEFT JOIN GetLegsIntervals g
ON t.TollExitDateTime between g.LegStartDate and g.LegEndDate
Output:
Tractor TollExitDateTime LegNumber
1404 2016-04-03 05:21 AM NULL
1404 2016-04-03 05:34 AM NULL
1404 2016-04-03 06:28 AM NULL
1404 2016-04-03 02:36 PM NULL
1404 2016-04-03 03:13 PM NULL
1404 2016-04-03 03:29 PM NULL
1404 2016-04-05 08:14 AM 1862410
1404 2016-04-05 08:26 AM 1862410
1404 2016-04-05 09:26 AM 1862410
1404 2016-04-05 03:15 PM 1862410
1404 2016-04-05 03:53 PM 1862410
1404 2016-04-05 04:09 PM 1862410
You can change last query to UPDATE:
UPDATE t
SET LegNumber = g.LegNumber
FROM Table1 t
LEFT JOIN GetLegsIntervals g
ON t.TollExitDateTime between g.LegStartDate and g.LegEndDate
If you are one SQL Server 2012+, you can use the LEAD to set the LegEndDate for each:
create table TABLE1(TRACTOR integer, TOLLEXITDATETIME datetime);
create table TABLE2(TRACTOR integer, LEGSTARTDATE datetime, LEGNUMBER integer);
insert into TABLE1
select '1404', '4/3/2016 5:21:00 AM' union all
select '1404', '4/5/2016 5:21:00 AM' union all
select '1404', '4/6/2016 5:21:00 AM'
;
insert into TABLE2
select '1404', '4/4/2016 3:54:00 AM', 1862405 union all
select '1404', '4/5/2016 4:02:00 AM', 1862410 union all
select '1404', '4/7/2016 6:53:00 AM', 1865901
;
with TEMP_TABLE2(TRACTOR, LEGSTARTDATE, LEGENDDATE, LEGNUMBER)
AS(
select
TRACTOR,
LEGSTARTDATE,
lead(LEGSTARTDATE) over (partition by TRACTOR order by LEGSTARTDATE) LEGENDDATE,
LEGNUMBER
from TABLE2
)
select
t1.TRACTOR,
t1.TOLLEXITDATETIME,
t2.LEGNUMBER
from TABLE1 t1
left outer join TEMP_TABLE2 t2
on t1.TRACTOR = t2.TRACTOR
and t1.TOLLEXITDATETIME between t2.LEGSTARTDATE and coalesce(t2.LEGENDDATE, '12/31/9999')
Thank you mo2 for starter table code :)
And thank you gofr1 for the
ROW_NUMBER() OVER (ORDER BY LegStartDate ASC) as rn because even though I use it all the time I completely blanked it.
Table I used for testing
create table Table2(Tractor integer, LegStartDate datetime, LegNumber integer primary key);
create table Table1(Tractor integer, TollExitDateTime datetime, LegNumber integer FOREIGN KEY REFERENCES Table2(Legnumber));
insert into TABLE1
select '1404', '4/3/2016 5:21:00 AM', NULL union all
select '1404', '4/3/2016 5:34:00 AM', NULL union all
select '1404', '4/3/2016 6:28:00 AM', NULL union all
select '1404', '4/3/2016 2:36:00 PM', NULL union all
select '1404', '4/3/2016 3:13:00 PM', NULL union all
select '1404', '4/3/2016 3:29:00 PM', NULL union all
select '1404', '4/5/2016 8:14:00 AM', NULL union all
select '1404', '4/5/2016 8:26:00 AM', NULL union all
select '1404', '4/5/2016 9:26:00 AM', NULL union all
select '1404', '4/5/2016 3:15:00 PM', NULL union all
select '1404', '4/5/2016 3:53:00 PM', NULL union all
select '1404', '4/5/2016 4:09:00 PM', NULL
;
insert into TABLE2
select '5000', '4/4/2016 3:54:00 AM', 5555555 union all --testing purpose
select '1404', '4/3/2016 5:21:00 AM', 8888888 union all --testing purpose
select '1404', '4/4/2016 3:54:00 AM', 1862405 union all
select '1404', '4/5/2016 4:02:00 AM', 1862410 union all
select '1404', '4/7/2016 6:53:00 AM', 1865901
;
This is the code I used to display, update, then display again for testing.
--This will just display what leg number goes with what
--I will probably be yelled at for not using joins. Normally I do but it worked perfectly fine in this situation to not so I did not bother to.
;WITH LegRows AS (SELECT *, ROW_NUMBER() OVER (ORDER BY t2a.LegStartDate ASC) as rn FROM Table2 t2a)
SELECT t1a.Tractor, t1a.TollExitDateTime, t1a.LegNumber, r1.LegStartDate, r1.LegNumber FROM Table1 t1a, LegRows r1, LegRows r2 WHERE t1a.TollExitDateTime BETWEEN r1.LegStartDate AND r2.LegStartDate AND r1.rn+1 = r2.rn
--This updates the leg information
;WITH LegRows AS (SELECT *, ROW_NUMBER() OVER (ORDER BY t2a.LegStartDate ASC) as rn FROM Table2 t2a)
UPDATE Table1 Set LegNumber = r1.LegNumber FROM Table1 t1a, LegRows r1, LegRows r2 WHERE t1a.TollExitDateTime BETWEEN r1.LegStartDate AND r2.LegStartDate AND r1.rn+1 = r2.rn
--This again displays what leg number goes with what to confirm the update
;WITH LegRows AS (SELECT *, ROW_NUMBER() OVER (ORDER BY t2a.LegStartDate ASC) as rn FROM Table2 t2a)
SELECT t1a.Tractor, t1a.TollExitDateTime, t1a.LegNumber, r1.LegStartDate, r1.LegNumber FROM Table1 t1a, LegRows r1, LegRows r2 WHERE t1a.TollExitDateTime BETWEEN r1.LegStartDate AND r2.LegStartDate AND r1.rn+1 = r2.rn