Sort by status and effective date range - sql

Before
After
Is there any way to make 'Before' to 'After'?
if a function or procedure is required, please write one for me.
please help me
drop table test;
create table test (employee_code varchar2(8), status varchar2(1), effective_date date, expiry_date date, rate number);
insert into test values ('1', 'U', '01-JAN-20','15-JAN-20',10);
insert into test values ('1', 'U', '06-JAN-20','01-FEB-20',11);
insert into test values ('1', 'N', '02-FEB-20','15-MAR-20',5);
insert into test values ('1', 'N', '16-MAR-20','15-JUN-20',6);
insert into test values ('2', 'N', '01-JAN-20','11-JAN-20',20);
insert into test values ('2', 'U', '12-JAN-20','12-FEB-20',100);
insert into test values ('2', 'N', '13-FEB-20','19-MAR-20',25);
insert into test values ('2', 'N', '20-MAR-20','21-JUN-20',30);
drop table result;
create table result (employee_code varchar2(8), status varchar2(1), effective_date date, expiry_date date);
insert into result values ('1', 'U', '01-JAN-20','01-FEB-20');
insert into result values ('1', 'N', '02-FEB-20','15-JUN-20');
insert into result values ('2', 'N', '01-JAN-20','11-JAN-20');
insert into result values ('2', 'U', '12-JAN-20','12-FEB-20');
insert into result values ('2', 'N', '13-FEB-20','21-JUN-20');
select * from test;
select * from result;

You just need to use GROUP BY and analytical function as follows:
SQL> Select employee_code,
2 status,
3 min(effective_date) effective_date,
4 max(expiry_date) expiry_date
5 From
6 (Select t.*,
7 Sum(case when lgst is null or lgst <> status then 1 end)
8 over (partition by employee_code order by effective_date) as sm
9 From
10 (Select t.*,
11 Lag(status) over (partition by employee_code order by effective_date) as lgst
12 From test t) t
13 )
14 Group by employee_code, sm, status
15 order by employee_code, effective_date;
EMPLOYEE S EFFECTIVE EXPIRY_DA
-------- - --------- ---------
1 U 01-JAN-20 01-FEB-20
1 N 02-FEB-20 15-JUN-20
2 N 01-JAN-20 11-JAN-20
2 U 12-JAN-20 12-FEB-20
2 N 13-FEB-20 21-JUN-20
SQL>

Related

How to JOIN two rows into one row SQL

Table 1:
ID
Name
Class
Date
Intime
Outtime
INAM
OUTPM
1
Smith
1st
07-12-2022
8:30 AM
Null
P
Null
1
Smith
1st
07-12-2022
Null
4:30 PM
Null
P
How to join these two rows into a single row?
Required output:
ID
Name
Class
Date
Intime
Outtime
INAM
OUTPM
1
Smith
1st
07-12-2022
8:30 AM
4:30 PM
P
P
Can someone please help me to join into a single row? Thank you...
You may aggregate by the first 4 columns and take the max of the final 4 columns:
SELECT ID, Name, Class, Date,
MAX(Intime) AS Intime, MAX(Outtime) AS Outtime, MAX(INAM) AS INAM,
MAX(OUTPM) AS OUTPM
FROM yourTable
GROUP BY ID, Name, Class, Date;
SELECT
FK,
MAX(Field1) AS Field1,
MAX(Field2) AS Field2
FROM
table1
GROUP BY
FK;
I used MAX, but any aggregate which picks one value from among the GROUP BY rows should work.
Test data:
CREATE TABLE table1 (FK int, Field1 varchar(10), Field2 varchar(10));
INSERT INTO table1 VALUES (3, 'ABC', NULL);
INSERT INTO table1 VALUES (3, NULL, 'DEF');
INSERT INTO table1 VALUES (4, 'GHI', NULL);
INSERT INTO table1 VALUES (4, 'JKL', 'MNO');
INSERT INTO table1 VALUES (4, NULL, 'PQR');
Results:
FK Field1 Field2
-- ------ ------
3 ABC DEF
4 JKL PQR

SQL query for date intervals comparing non-adjacent rows?

I want to flag the first date in every window of at least 31 days for each ID unit in my data.
ROW ID INDEX_DATE
1 ABC 1/1/2019
2 ABC 1/7/2019
3 ABC 1/21/2019
4 ABC 2/2/2019
5 ABC 2/9/2019
6 ABC 3/6/2019
7 DEF 1/5/2019
8 DEF 2/1/2019
9 DEF 2/8/2019
The desired rows are 1, 4, 6, 7 and 9; these are either the first INDEX_DATE for the given ID, or they occur at least 31 days after the previously flagged INDEX_DATE. Every suggestion I have found uses LAG() or LEAD with window functions, but I could only get these to compare adjacent rows. Row 4, for example, needs to be compared to Row 1 in order to be identified as the first after a 31-day window has completed.
I tried the following:
Data
DROP TABLE tTest IF EXISTS;
CREATE TEMP TABLE tTest
(
ROWN INT,
ID VARCHAR(3),
INDEX_DATE DATE
) ;
GO
INSERT INTO tTEST VALUES (1, 'ABC', '1/1/2019');
INSERT INTO tTEST VALUES (2, 'ABC', '1/7/2019');
INSERT INTO tTEST VALUES (3, 'ABC', '1/21/2019');
INSERT INTO tTEST VALUES (4, 'ABC', '2/2/2019');
INSERT INTO tTEST VALUES (5, 'ABC', '2/9/2019');
INSERT INTO tTEST VALUES (6, 'ABC', '3/6/2019');
INSERT INTO tTEST VALUES (7, 'DEF', '1/5/2019');
INSERT INTO tTEST VALUES (8, 'DEF', '2/1/2019');
INSERT INTO tTEST VALUES (9, 'DEF', '2/8/2019');
GO
Query:
DROP TABLE TTEST2 IF EXISTS;
CREATE TEMP TABLE TTEST2 AS (
WITH
RN_CTE(ROWN, ID, INDEX_DATE, RN) AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY INDEX_DATE)
FROM tTEST),
MIN_CTE(ROWN, ID, INDEX_DATE, RN) AS (SELECT * FROM RN_CTE WHERE RN=1),
DIFF_CTE(ROWN,ID, INDEX_DATE, RN, DAY_DIFF) AS (
SELECT RN.*, DATE(RN.INDEX_DATE + INTERVAL '30 DAYS')
FROM RN_CTE AS RN
JOIN MIN_CTE AS MC ON RN.ID=MC.ID
WHERE RN.RN=1
OR RN.INDEX_DATE > MC.INDEX_DATE + INTERVAL '30 DAYS' ),
MIN_DIFF_CTE AS (
SELECT ID, DAY_DIFF, MIN(ROWN) AS MIN_ROW
FROM DIFF_CTE
GROUP BY ID, DAY_DIFF)
SELECT T.*
FROM MIN_DIFF_CTE AS MDC
JOIN tTEST AS T ON MDC.MIN_ROW = T.ROWN
ORDER BY ID, INDEX_DATE
);
Result:
SELECT * FROM TTEST2 ORDER BY ID, INDEX_DATE;
ROWN ID INDEX_DATE
1 ABC 2019-01-01
4 ABC 2019-02-02
5 ABC 2019-02-09
6 ABC 2019-03-06
7 DEF 2019-01-05
9 DEF 2019-02-08
Row 5 with INDEX_DATE = 2019-02-09 should not be in the output because it is less than 31 days after Row 4's INDEX_DATE.
Something like this. The CTE's locate the unique window of the minimum ROW value for each ID transition and 31 days rolling too.
Data
drop table if exists #tTEST;
go
select * INTO #tTEST from (values
(1, 'abc', '1/1/2019'),
(2, 'abc', '1/7/2019'),
(3, 'abc', '1/21/2019'),
(4, 'abc', '2/2/2019'),
(5, 'abc', '2/9/2019'),
(6, 'abc', '3/6/2019'),
(7, 'def', '1/5/2019'),
(8, 'def', '2/1/2019'),
(9, 'def', '2/8/2019')) V([ROW], ID, INDEX_DATE);
Query
;with
rn_cte([ROW], ID, INDEX_DATE, rn) as (
select *, row_number() over (partition by ID order by INDEX_DATE)
from #tTEST),
min_cte([ROW], ID, INDEX_DATE, rn) as (select * from rn_cte where rn=1),
diff_cte([ROW], ID, INDEX_DATE, rn, day_diff) as (
select rn.*, datediff(d, mc.INDEX_DATE, rn.INDEX_DATE)/31
from rn_cte rn
join min_cte mc on rn.ID=mc.ID
where rn.rn=1
or datediff(d, mc.INDEX_DATE, rn.INDEX_DATE)/31>0),
min_diff_cte as (
select ID, day_diff, min([ROW]) min_row
from diff_cte
group by ID, day_diff)
select t.*
from min_diff_cte mdc
join #tTEST t on mdc.min_row=t.ROW
order by 1;
Output
ROW ID INDEX_DATE
1 abc 1/1/2019
4 abc 2/2/2019
6 abc 3/6/2019
7 def 1/5/2019
9 def 2/8/2019

Fill value in rows based on second table

create table a (
name char(10),
addr int,
salary money
)
create table b (
name char(10),
addr int,
salary money
)
insert into a values ('kesh', '2', '10')
insert into a values ('bis', '11', '30')
insert into a values ('kir', '13', '30')
insert into a values ('sss', '14', '30')
insert into b values ('kesh', '3', null)
insert into b values ('bis', '12', null)
insert into b values ('sss', '14', '30')
insert into b values ('maa', '16', '1000')
I need this result from the above table. it would be great if someone help me with this.
name addr newSalary
bis 11 30.00
bis 12 30.00
kesh 2 10.00
kesh 3 10.00
kir 13 30.00
maa 16 1000.00
sss 14 30.00
For this dataset, you could use union all and window functions:
select
name,
addr,
coalesce(salary, max(salary) over(partition by name)) new_salary
from (
select name, addr, salary from a
union all
select name, addr, salary from b
) t
This somehow assumes that names are unique in each table.

Get the top most element for any element in chain (tree) in Oracle Sql

Consider the following structure with some nodes:
1
/\
2 3
| |
4 5
| |
6 7
|
8
or
with mydata as (
select 8 id ,6 id_before, 400 datum from dual union all
select 6,4, 300 from dual union all
select 4, 2, 200 from dual union all
select 2,1, 10 from dual union all
select 3, 1, 60 from dual union all
select 5, 3, 800 from dual union all
select 7, 5, 900 from dual
)
Now given the id of the node I want to get the root node id
e.g. for node 7 root note 1, for 5 root node 1 for 4 root node 1 etc.
I tried something like this:
select id, id_before,datum, SYS_CONNECT_BY_PATH(id_before,'/') as path,
SYS_CONNECT_BY_PATH(datum,'/') as datapath,
level,
CONNECT_BY_ROOT id_before
from mydata
where id=7
connect by id_before = prior id
with disappointing results:
7 5 900 /1/3/5 /60/800/900 3 1
7 5 900 /3/5 /800/900 2 3
7 5 900 /5 /900 1 5
Any ideas on how to fix this?
Thanks.
You can use CONNECT_BY_ISLEAF as a filter to find the root element:
Oracle Setup:
CREATE TABLE mydata ( id, id_before, datum ) as
select 8, 6, 400 from dual union all
select 6, 4, 300 from dual union all
select 4, 2, 200 from dual union all
select 2, 1, 10 from dual union all
select 3, 1, 60 from dual union all
select 5, 3, 800 from dual union all
select 7, 5, 900 from dual;
Query:
SELECT CONNECT_BY_ROOT( id ) AS id,
id_before AS root_id,
SYS_CONNECT_BY_PATH( id, ',' ) || ',' || id_before AS path
FROM mydata
WHERE CONNECT_BY_ISLEAF = 1
CONNECT BY id = PRIOR id_before;
Output:
ID | ROOT_ID | PATH
-: | ------: | :---------
2 | 1 | ,2,1
3 | 1 | ,3,1
4 | 1 | ,4,2,1
5 | 1 | ,5,3,1
6 | 1 | ,6,4,2,1
7 | 1 | ,7,5,3,1
8 | 1 | ,8,6,4,2,1
db<>fiddle here
This topic helped me a lot today.
Needed to find a way to get whole tree by item in the middle.
It's fair to share results here, even after 2 years of accepted answer.
create table ref (id number not null, prvni varchar2(1), druhy varchar2(1));
insert into ref (id, prvni, druhy) values (1, 'B', 'A');
insert into ref (id, prvni, druhy) values (2, 'C', 'A');
insert into ref (id, prvni, druhy) values (3, 'D', 'C');
insert into ref (id, prvni, druhy) values (4, 'E', 'A');
insert into ref (id, prvni, druhy) values (5, 'F', 'C');
insert into ref (id, prvni, druhy) values (6, 'G', 'F');
insert into ref (id, prvni, druhy) values (7, 'x', 'y');
insert into ref (id, prvni, druhy) values (8, 'z', 'v');
insert into ref (id, prvni, druhy) values (9, 'w', 'u');
create table zpr (pismeno varchar2(1), typ varchar2(6));
insert into zpr (pismeno, typ) values ('A', 'ORDERS');
insert into zpr (pismeno, typ) values ('B', 'ORDRSP');
insert into zpr (pismeno, typ) values ('C', 'DESADV');
insert into zpr (pismeno, typ) values ('D', 'RECADV');
insert into zpr (pismeno, typ) values ('E', 'RETANN');
insert into zpr (pismeno, typ) values ('F', 'INVOIC');
insert into zpr (pismeno, typ) values ('G', 'INVOIC');
insert into zpr (pismeno, typ) values ('x', 'APERAK');
insert into zpr (pismeno, typ) values ('y', 'APERAK');
insert into zpr (pismeno, typ) values ('z', 'APERAK');
insert into zpr (pismeno, typ) values ('u', 'APERAK');
insert into zpr (pismeno, typ) values ('v', 'APERAK');
insert into zpr (pismeno, typ) values ('w', 'APERAK');
-- recursion
WITH rcte(prvni,druhy,typ,lvl) AS
(SELECT r1.prvni, r1.druhy, a1.typ, 1 AS lvl
FROM ref r1, zpr a1
WHERE (r1.prvni = 'B' OR r1.druhy = 'B')
AND a1.pismeno = r1.druhy
UNION ALL
SELECT r2.prvni, r2.druhy, a2.typ, lvl + 1 AS lvl
FROM ref r2
INNER JOIN zpr a2
ON a2.pismeno = r2.druhy
INNER JOIN rcte rc
ON r2.prvni = rc.druhy),
orders AS
(SELECT * FROM rcte WHERE typ = 'ORDERS')
SELECT prvni,
z1.typ prvni_typ,
connect_by_root(druhy) AS druhy,
z2.typ druhy_typ,
sys_connect_by_path(druhy, ' => ') || ' => ' || prvni AS path,
sys_connect_by_path(z2.typ, ' => ') AS typy
FROM ref, zpr z1, zpr z2
WHERE ref.prvni = z1.pismeno
AND ref.druhy = z2.pismeno
--and CONNECT_BY_ISLEAF = 1
CONNECT BY PRIOR prvni = druhy
START WITH ref.druhy IN (SELECT druhy FROM orders);
Here's the fiddle: http://sqlfiddle.com/#!4/5a3181/38

find start and end dates over a non-contiguous range

I need to find the start and end dates of range defined as: start date is the first date and the end date is the first date where the subsequent date is two months or more after the end date. There can be multiple possible ranges
I have a table structure like:
ID int identity(1,1),
fk_ID char(9),
dateField datetime
The data looks like:
1 a 2012-01-01
2 a 2012-01-05
3 a 2012-01-12
4 b 2012-02-01
5 a 2012-04-01
6 b 2012-05-01
7 a 2012-05-30
The expected output would look like:
fk_id startdate enddate
a 2012-01-01 2012-01-12
a 2012-04-01 2012-05-30
b 2012-02-01 2012-02-01
b 2012-05-01 null
EDIT:
By doing the following:
CREATE TABLE #temp
(
autonum int identity(1,1),
id char(9),
sd datetime
)
insert into #temp (id, sd) values ('a', '2012-01-01')
insert into #temp (id, sd) values ('a', '2012-01-05')
insert into #temp (id, sd) values ('a', '2012-01-12')
insert into #temp (id, sd) values ('a', '2012-03-01')
insert into #temp (id, sd) values ('a', '2012-04-03')
insert into #temp (id, sd) values ('a', '2012-06-06')
insert into #temp (id, sd) values ('b', '2012-02-12')
insert into #temp (id, sd) values ('b', '2012-02-15')
insert into #temp (id, sd) values ('b', '2012-03-01')
insert into #temp (id, sd) values ('b', '2012-04-03')
insert into #temp (id, sd) values ('b', '2012-06-01')
select t1.id, null as previousend, min(t1.sd) as nextstart
from #temp t1
group by t1.id
union
select t1.id, t1.sd as enddate, (select min(t2.sd) from #temp t2 where t1.id=t2.id and t2.sd>t1.sd) as nextstart
from #temp t1
where (select min(t2.sd) from #temp t2 where t1.id=t2.id and t2.sd>t1.sd) >= dateadd(month, 2, t1.sd)
union
select t1.id, max(t1.sd), null
from #temp t1
group by t1.id
drop table #temp
I can get output like this:
id previousend nextstart
--------- ----------------------- -----------------------
a NULL 2012-01-01 00:00:00.000
a 2012-04-03 00:00:00.000 2012-06-06 00:00:00.000
a 2012-06-06 00:00:00.000 NULL
b NULL 2012-02-12 00:00:00.000
b 2012-06-01 00:00:00.000 NULL
Which is very close, but ideally the start and end date of the range would be on the row.
Here is my best guess given all the changes to the question. I still find the problem very confusing, splintered and that the desired results for the two cases don't seem to match. With this query:
;WITH x AS
(
SELECT a.id, sd = a.sd, ed = b.sd, rn1 = ROW_NUMBER() OVER
(PARTITION BY a.id, a.sd ORDER BY a.sd)
FROM #temp AS a
LEFT OUTER JOIN #temp AS b
ON a.id = b.id
AND b.sd >= a.sd
AND b.sd <= DATEADD(MONTH, 2, a.sd)
),
y AS
(SELECT id, sd,
ed = (SELECT MAX(ed) FROM x AS x2
WHERE x.id = x2.id AND x2.sd <= DATEADD(MONTH, 2, x.sd)
)
FROM x
WHERE rn1 = 1
),
z AS
(
SELECT id, sd = MIN(sd), ed
FROM y GROUP BY id, ed
)
SELECT id, sd, ed /* = CASE
WHEN ed > sd OR (sd = ed AND NOT EXISTS
(SELECT 1 FROM z AS z2 WHERE z2.id = z.id AND z.sd > z2.sd)) THEN ed END
*/
FROM z
ORDER BY id, sd;
The results for your first set of data:
INSERT #temp (id, sd) VALUES
('a','2012-01-01'),
('a','2012-01-05'),
('a','2012-01-12'),
('b','2012-02-01'),
('a','2012-04-01'),
('b','2012-05-01'),
('a','2012-05-30');
Is as follows:
id sd ed
a 2012-01-01 2012-01-12
a 2012-04-01 2012-05-30
b 2012-02-01 2012-02-01
b 2012-05-01 2012-05-01
And for the second set:
insert into #temp (id, sd) values ('a', '2012-01-01')
insert into #temp (id, sd) values ('a', '2012-01-05')
insert into #temp (id, sd) values ('a', '2012-01-12')
insert into #temp (id, sd) values ('a', '2012-03-01')
insert into #temp (id, sd) values ('a', '2012-04-03')
insert into #temp (id, sd) values ('a', '2012-06-06')
insert into #temp (id, sd) values ('b', '2012-02-12')
insert into #temp (id, sd) values ('b', '2012-02-15')
insert into #temp (id, sd) values ('b', '2012-03-01')
insert into #temp (id, sd) values ('b', '2012-04-03')
insert into #temp (id, sd) values ('b', '2012-06-01')
Is as follows:
id sd ed
a 2012-01-01 2012-04-03
a 2012-06-06 2012-06-06
b 2012-02-12 2012-06-01
If you uncomment the CASE block you'll get NULLs for the end date where the start date and end date are the same. As I suggested multiple times, your question is splintered and your desired results don't seem to match, so I'm not sure what the right answer is.
attempt number two which is on Fiddle and is far from elegant but seems to work apart from the final record not being NULL for the end date:
CREATE TABLE temp
(
id char(9),
d datetime
);
insert into temp (id, d) values ('a', '2012-01-01');
insert into temp (id, d) values ('a', '2012-01-05');
insert into temp (id, d) values ('a', '2012-01-12');
insert into temp (id, d) values ('a', '2012-04-01');
insert into temp (id, d) values ('a', '2012-05-30');
insert into temp (id, d) values ('b', '2012-02-01');
insert into temp (id, d) values ('b', '2012-05-01');
SELECT
x.id ,
min(x.sd) sd ,
x.ed
FROM
(SELECT
a.id ,
a.sd ,
max(a.ed) ed
FROM
(
SELECT
j.id ,
j.d sd ,
q.D ed
FROM temp j
JOIN temp q
ON
j.id = q.id
AND j.d <= q.d
GROUP BY j.id ,
j.d ,
q.d
) a
WHERE datediff(m,a.sd,a.ed)<=2
GROUP BY a.id ,
a.sd
)x
GROUP BY x.id ,
x.ed
ORDER BY x.id ,
min(x.sd) ,
x.ed