Oracle SQL - Scanning attribute Changes - sql

I have the following employee table
EMPID RECORD_DATE DEPARTMENT
123456 2020-01-01 HR
123456 2020-02-01 HR
123456 2020-03-01 FINANCE
123456 2020-04-01 FINANCE
987654 2020-01-01 HR
987654 2020-02-01 HR
987654 2020-03-01 HR
987654 2020-04-01 LEGAL
Using Oracle PL/SQL, I need to build an expression to ascertain a list of employee movement, specifically that have moved from HR to any other (non-HR) department.
Expected result:
EMPID MOVEMENT_DATE DEPT_BEFORE DEPT_AFTER
123456 2020-03-01 HR FINANCE
987654 2020-04-01 HR LEGAL
I know you can use the Lead or Lag function, but it's a little off for me:
SELECT
,EMP
,RECORD_DATE
,LAG(DEPARTMENT, 1, 0) OVER (PARTITION BY EMP ORDER BY RECORD_DATE) PREV
FROM EMP
Here are some values to work with:
CREATE TABLE #EMP
(
EMP VARCHAR(30) NOT NULL ,
RECORD_DATE DATE NOT NULL ,
DEPARTMENT VARCHAR(30) NOT NULL
);
INSERT INTO #EMP (EMP, DATE_WORKED, CITY)
VALUES
('123456','2020-01-01','HR'),
('123456','2020-02-01','HR'),
('123456','2020-03-01','FINANCE'),
('123456','2020-04-01','FINANCE'),
('987654','2020-01-01','HR'),
('987654','2020-02-01','HR'),
('987654','2020-03-01','HR'),
('987654','2020-04-01','LEGAL')

You could do it using LAG function:
WITH data AS(
SELECT 123456 EMPID, DATE '2020-01-01' RECORD_DATE, 'HR' DEPARTMENT FROM dual UNION ALL
SELECT 123456 EMPID, DATE '2020-02-01' RECORD_DATE, 'HR' DEPARTMENT FROM dual UNION ALL
SELECT 123456 EMPID, DATE '2020-03-01' RECORD_DATE, 'FINANCE' DEPARTMENT FROM dual UNION ALL
SELECT 123456 EMPID, DATE '2020-04-01' RECORD_DATE, 'FINANCE' DEPARTMENT FROM dual UNION ALL
SELECT 987654 EMPID, DATE '2020-01-01' RECORD_DATE, 'HR' DEPARTMENT FROM dual UNION ALL
SELECT 987654 EMPID, DATE '2020-02-01' RECORD_DATE, 'HR' DEPARTMENT FROM dual UNION ALL
SELECT 987654 EMPID, DATE '2020-03-01' RECORD_DATE, 'HR' DEPARTMENT FROM dual UNION ALL
SELECT 987654 EMPID, DATE '2020-04-01' RECORD_DATE, 'LEGAL' DEPARTMENT FROM dual
)
SELECT * FROM(
SELECT
EMPID,
RECORD_DATE MOVEMENT_DATE,
LAG(DEPARTMENT) OVER (PARTITION BY EMPID ORDER BY RECORD_DATE) DEPARTMENT_BEFORE,
DEPARTMENT DEPARTMENT_AFTER
FROM data
)
WHERE DEPARTMENT_BEFORE <> DEPARTMENT_AFTER;
EMPID MOVEMENT_DATE DEPARTMENT_BEFORE DEPARTMENT_AFTER
---------- --------------- ----------------- -----------------
123456 2020-03-01 HR FINANCE
987654 2020-04-01 HR LEGAL

Related

SQL Joining transactions on Date Range

In SQL Server 2014, I'm working with two tables, an EMPLOYEE and a SALES table:
EMPID EMPNAME HIRE_DATE
---------------------------
1234 JOHN SMITH 2021-05-01
1235 JANE DOE 2021-08-05
1236 JANE SMITH 2021-07-31
EMPID SALE_DATE PRODUCT
-------------------------------------
1234 2021-05-05 VPN
1234 2021-05-10 VPN Basic
1234 2021-07-15 Cloud Storage Bronze
1234 2021-07-05 Cloud Storage Gold
1235 2021-10-01 Antivirus
I need to write a query that will produce all rows/columns from the EMPLOYEE table, with a column showing their (aggregated) sales, but ONLY sales that were triggered within 30 days of the hire date.
This query works, but will pull in ALL sales completed until present:
SELECT EMP.*, SALES_30_DAYS
FROM EMP
LEFT JOIN
(SELECT EMPID, COUNT(*)
FROM SALES_30_DAYS
GROUP BY EMPID) ON EMP.EMPID = SALES.EMPID
In this other attempt, HIRE_DATE is not recognized in the sub-query.
SELECT EMP.*, SALES_30_DAYS
FROM EMP
LEFT JOIN
(SELECT EMPID, COUNT(*) SALES_30_DAYS
FROM SALES
WHERE DATEDIFF(DD, HIRE_DATE, SALE_DATE) < 30
GROUP BY EMPID) ON EMP.EMPID= SALES.EMPID
How can I re-write this query, so that the second table will provide the aggregated sales ONLY if the sale took place up to 30 days after the hire date?
Desired outcome:
EMPID EMPNAME HIRE_DATE SALES_30_DAYS
-----------------------------------------
1234 JOHN SMITH 2021-05-01 2
1235 JANE DOE 2021-08-05 1
1236 JANE SMITH 2021-07-31 NULL
WITH EMPLOYEES(EMPID, EMPNAME, HIRE_DATE)AS
(
SELECT 1234, 'JOHN SMITH', '2021-05-01' UNION ALL
SELECT 1235, 'JANE DOE' , '2021-08-05' UNION ALL
SELECT 1236, 'JANE SMITH' ,'2021-07-31'
),
SALES(EMPID, SALE_DATE, PRODUCT) AS
(
SELECT 1234, '2021-05-05' ,'VPN' UNION ALL
SELECT 1234 , '2021-05-10' ,'VPN Basic' UNION ALL
SELECT 1234 , '2021-07-15' ,'Cloud Storage Bronze' UNION ALL
SELECT 1234 , '2021-07-05' ,'Cloud Storage Gold' UNION ALL
SELECT 1235 , '2021-10-01', 'Antivirus'
)
SELECT E.EMPID,E.EMPNAME,E.HIRE_DATE,SALE_QUERY.CNTT
FROM EMPLOYEES E
OUTER APPLY
(
SELECT COUNT(*)CNTT
FROM SALES AS S WHERE E.EMPID=S.EMPID AND
S.SALE_DATE BETWEEN E.HIRE_DATE AND DATEADD(DD,30,E.HIRE_DATE)
)SALE_QUERY
Could you please try if the above is suitable for you

How to do a query on Oracle SQL to get time intervals, grouping by specific fields

I love a good challenge, but this one has been breaking my head for too long. :)
I'm trying to build a query to get dates intervals, grouping the information by one field.
Let me try to explain it in a simple way.
We have this table:
I need to get the intervals a soldier spent on each ranking, so the end result I need to get should be something like this:
As you can see the soldier can be promoted/demoted along the time.
Any suggestion on how to build a query to do this?
THANK YOU!
From Oracle 12, you can use MATCH_RECOGNIZE:
SELECT *
FROM table_name
MATCH_RECOGNIZE (
PARTITION BY id
ORDER BY start_date, end_date
MEASURES
FIRST( name ) AS name,
FIRST( ranking ) AS ranking,
FIRST( start_date ) AS start_date,
LAST( end_Date ) AS end_Date
PATTERN ( same_rank+ )
DEFINE same_rank AS FIRST( ranking ) = ranking
)
Which, for the sample data:
CREATE TABLE table_name ( id, name, ranking, start_date, end_date ) AS
SELECT 1001, 'Jones', 'Lieutenant', DATE '2000-03-20', DATE '2002-08-15' FROM DUAL UNION ALL
SELECT 1001, 'Jones', 'Lieutenant', DATE '2002-08-16', DATE '2003-03-18' FROM DUAL UNION ALL
SELECT 1001, 'Jones', 'Lieutenant', DATE '2003-03-19', DATE '2004-06-01' FROM DUAL UNION ALL
SELECT 1001, 'Jones', 'Lieutenant', DATE '2004-06-02', DATE '2004-10-01' FROM DUAL UNION ALL
SELECT 1001, 'Jones', 'Captain', DATE '2004-10-02', DATE '2005-04-20' FROM DUAL UNION ALL
SELECT 1001, 'Jones', 'Captain', DATE '2005-04-21', DATE '2007-02-20' FROM DUAL UNION ALL
SELECT 1001, 'Jones', 'Major', DATE '2007-02-21', DATE '2008-10-22' FROM DUAL UNION ALL
SELECT 1001, 'Jones', 'Major', DATE '2008-10-23', DATE '2010-01-26' FROM DUAL UNION ALL
SELECT 1001, 'Jones', 'Captain', DATE '2010-01-27', DATE '2013-11-25' FROM DUAL UNION ALL
SELECT 1001, 'Jones', 'Captain', DATE '2013-11-26', DATE '2014-05-11' FROM DUAL UNION ALL
SELECT 1001, 'Jones', 'Major', DATE '2014-05-12', DATE '2016-04-22' FROM DUAL UNION ALL
SELECT 1001, 'Jones', 'General', DATE '2016-04-23', DATE '2020-10-10' FROM DUAL UNION ALL
SELECT 1001, 'Jones', 'General', DATE '2020-10-11', DATE '2020-11-30' FROM DUAL;
Outputs:
ID | NAME | RANKING | START_DATE | END_DATE
---: | :---- | :--------- | :------------------ | :------------------
1001 | Jones | Lieutenant | 2000-03-20 00:00:00 | 2004-10-01 00:00:00
1001 | Jones | Captain | 2004-10-02 00:00:00 | 2007-02-20 00:00:00
1001 | Jones | Major | 2007-02-21 00:00:00 | 2010-01-26 00:00:00
1001 | Jones | Captain | 2010-01-27 00:00:00 | 2014-05-11 00:00:00
1001 | Jones | Major | 2014-05-12 00:00:00 | 2016-04-22 00:00:00
1001 | Jones | General | 2016-04-23 00:00:00 | 2020-11-30 00:00:00
db<>fiddle here
This is a type of gaps and islands problem. You want to find groups of rows that are the same, which you can do using lag() to compare the ranking and then a cumulative sum to keep track of the changes:
select soldier_id, soldier_name, ranking,
min(start_date), max(end_date)
from (select t.*,
sum(case when prev_end_date = start_date - interval '1' day then 0 else 1 end)
(partition by soldier_id order by start_date) as island
from (select t.*,
lag(end_date) over (partition by soldier_id, ranking order by start_date) as prev_end_date
from t
) t
) t
group by soldier_id, soldier_name, ranking, island;
Note: This assumes that the soldier_name does not change over time for a given soldier. If that is something you need to deal with, then ask a new question with appropriate sample data and desired results.

Oracle 18c - Complex sql

I have a table with following columns:
Emp_ID Number
Emp_flag Varchar2(1)
Date_1 Date
Date_2 Date
create_date Date
No PK on this table , there are many records with duplicates of Emp_id..
What I need to know, is when a new Date_1 is entered (so Null to a date, or from Date 1 to Date 2) on what date that happened.
I can’t just look at a single record to compare Date_1 with create_date because there are many times in the many records for a given Emp_ID when the Date_1 is simply “copied” to the new record. A Date_1 may have been originally entered on 02/15/2019 with a value of 02/01/2019. Now let’s say Date_2 gets added on 02/12/2020. So the table looks like this:
Emp_ID Emp_flag Date_1 Date_2 Create_Date
123 Y Null Null 1/18/2018
123 Y 02/1/2019 Null 02/15/2019
123 Y 02/1/2019 02/12/2021 02/12/2020
I need a SQL query that would tell me that Emp_ID 123 had a Date_1 of 02/1/2019 entered on 02/15/2019 and NOT pick up any other record.
Expected output:
Emp_ID Emp_flag Date_1 Date_2 Create_Date
123 Y 02/1/2019 Null 02/15/2019
Example 2 (notice date_1 is different):
Emp_ID Emp_flag Date_1 Date_2 Create_Date
456 Y Null Null 1/18/2018
456 Y 10/1/2019 Null 02/15/2019
456 Y 11/2/2019 02/12/2021 02/12/2020
Expected output:
Emp_ID Emp_flag Date_1 Date_2 Create_Date
456 Y 10/1/2019 Null 02/15/2019
456 Y 11/2/2019 02/12/2021 02/12/2020
Example 3:
Emp_ID Emp_flag Date_1 Date_2 Create_Date
456 Y Null Null 1/18/2018
456 Y 10/1/2019 Null 02/15/2019
456 Y 10/1/2019 Null 02/15/2019
456 Y 11/2/2019 02/12/2021 02/12/2020
Expected output:
Emp_ID Emp_flag Date_1 Date_2 Create_Date
456 Y 10/1/2019 Null 02/15/2019
456 Y 11/2/2019 02/12/2021 02/12/2020
Example 4:
Emp_ID Emp_flag Date_1 Date_2 Create_Date
456 Y 10/1/2019 Null 02/15/2019
456 Y 10/1/2019 Null 02/16/2019
Expected output: No records.
You can use the Lag function to check whether the previous value of date_1 existed or not.
SELECT x.emp_id,
x.date_1,
x.create_date AS first_date_with_date_1
FROM (
SELECT t.emp_id,
t.create_date,
t.date_1,
LAG(t.date_1) OVER (PARTITION BY t.emp_id ORDER BY t.create_date) AS last_date_1
FROM your_table t
) x
WHERE x.date_1 IS NOT NULL
AND x.last_date_1 IS NULL
Test for all cases:
with t(emp_id, emp_flag, date_1, date_2, create_date) as (
select 101, 'Y', null, null, date '2018-01-18' from dual union all
select 101, 'Y', date '2019-02-01', null, date '2019-02-15' from dual union all
select 101, 'Y', date '2019-02-01', date '2021-02-12', date '2019-02-16' from dual union all
select 102, 'Y', null, null, date '2018-01-18' from dual union all
select 102, 'Y', date '2019-02-10', null, date '2019-02-15' from dual union all
select 102, 'Y', date '2019-02-11', date '2021-02-12', date '2019-02-16' from dual union all
select 103, 'Y', null, null, date '2018-01-18' from dual union all
select 103, 'Y', date '2019-02-10', null, date '2019-02-15' from dual union all
select 103, 'Y', date '2019-02-10', null, date '2019-02-15' from dual union all
select 103, 'Y', date '2019-02-11', date '2021-02-21', date '2020-12-02' from dual )
select emp_id, emp_flag, date_1, date_2, create_date
from (
select emp_ID, emp_flag, date_1, date_2, create_date,
lag(date_1) over (partition by emp_id order by create_date) prev_dt1
from t )
where date_1 <> nvl(prev_dt1, date_1 - 1);
Result:
EMP_ID EMP_FLAG DATE_1 DATE_2 CREATE_DATE
---------- -------- ----------- ----------- -----------
101 Y 2019-02-01 2019-02-15
102 Y 2019-02-10 2019-02-15
102 Y 2019-02-11 2021-02-12 2019-02-16
103 Y 2019-02-10 2019-02-15
103 Y 2019-02-11 2021-02-21 2020-12-02
Edit:
when there are more than one records with no change in Date_1. It
should not return a record for that Emp_id
In this case date_1 is set in first row (id 104). If you want hide rows in such case use:
with t(emp_id, emp_flag, date_1, date_2, create_date) as (
select 104, 'Y', date '2019-02-10', null, date '2019-02-15' from dual union all
select 104, 'Y', date '2019-02-10', null, date '2019-02-16' from dual union all
select 105, 'Y', date '2019-02-10', null, date '2019-02-15' from dual union all
select 105, 'Y', null, null, date '2019-02-16' from dual )
select emp_id, emp_flag, date_1, date_2, create_date
from (
select emp_ID, emp_flag, date_1, date_2, create_date,
lag(date_1) over (partition by emp_id order by create_date) prev_dt1,
row_number() over (partition by emp_id order by create_date) rn
from t )
where (date_1 is not null and prev_dt1 is null and rn > 1)
or date_1 <> prev_dt1
or date_1 is null and prev_dt1 is not null;
I also added case when previous date was set and now it is null (id 105). If it is not possible or you don't want it then remove last row.
You can use the lag function instead of lead here:
with tableA as
(
select 456 as Emp_ID,'Y' as Emp_flag,CAST(NUll as date) as Date_1,CAST(NULL as date) as Date_2,CAST('18Jan2018' as date) as Create_date from dual union
select 456,'Y',CAST('01Oct2019' as date),Null,CAST('15Feb2019' as date) from dual union
select 456,'Y',CAST('02Nov2019' as date),CAST('12Feb2021' as date),CAST('12Feb2020' as date) from dual)
select x.Emp_ID,x.Emp_flag,x.Date_1,x.Date_2,x.Create_date
from
(select a.*
,lag(a.date_1) Over (partition by a.Emp_ID order by a.create_date) as lag_date
from tableA a) x
where x.date_1 is not null and x.date_1<>COALESCE(x.lag_date,CAST('01Jan2100' as date))
This will give out the values only when there is a change in date_1. Since NULL comparisons won't work, I have replace them with 1/1/2100. Hope this helps.
Edit:
I checked for a sample like you mentioned and it does seem to be working. If it's not working, kindly share the expected and the result you are getting:
with tableA as
(
select 456 as Emp_ID,'Y' as Emp_flag,CAST(NUll as date) as Date_1,CAST(NULL as date) as Date_2,CAST('18Jan2018' as date) as Create_date from dual union
select 456,'Y',CAST('01Oct2019' as date),Null,CAST('15Feb2019' as date) from dual union
select 456,'Y',CAST('01Oct2019' as date),CAST('12Feb2021' as date),CAST('12Feb2020' as date) from dual)
select x.Emp_ID,x.Emp_flag,x.Date_1,x.Date_2,x.Create_date
from
(select a.*
,lag(a.date_1) Over (partition by a.Emp_ID order by a.create_date) as lag_date
from tableA a) x
where x.date_1 is not null and x.date_1<>COALESCE(x.lag_date,CAST('01Jan2100' as date))

Retrieve single row from a query

I am creating a query to find salary details of an employee with date_to as '31-dec-4712' (Latest).
But, If date_to is 31-dec-4712 for two rows for an employee then the one with status 'Approved' should be picked in other cases when only
single rows comes then that should be returned as is.
I have created the below query for the salary details. need help with teh above scenario
select distinct PAPF.EMPLOYEE_NUMBER ,
TO_CHAR (EMP_DOJ (PAPF.PERSON_ID),'DD-MON-YYYY' ) DOJ ,
TO_CHAR(HR_EMPLOYEE_ORIGINAL_DOJ(PAPF.EMPLOYEE_NUMBER,42) ,'DD- MON-YYYY' ) ORIGINAL_DOJ,
PPP.CHANGE_DATE,
PPP.DATE_TO,
PPP.PROPOSED_SALARY_N TOTAL_REMUN,
HR_GENERAL.DECODE_LOOKUP('PER_SAL_PROPOSAL_STATUS',APPROVED) status
from PER_ALL_ASSIGNMENTS_F PAAF,
PER_ALL_PEOPLE_F PAPF,
PER_PAY_PROPOSALS PPP
where 1 = 1
and PAPF.PERSON_ID = PAAF.PERSON_ID
and PAPF.BUSINESS_GROUP_ID = 21
and PAPF.CURRENT_EMPLOYEE_FLAG = 'Y'
and papf.employee_number = '109575'
and :P_DATE1 between PAAF.EFFECTIVE_START_DATE
and PAAF.EFFECTIVE_END_DATE
and :P_DATE1 between PAPF.EFFECTIVE_START_DATE
and PAPF.EFFECTIVE_END_DATE
and :P_DATE1 between PPP.CHANGE_DATE(+)
and NVL(PPP.DATE_TO, HR_GENERAL.END_OF_TIME)
and PPP.ASSIGNMENT_ID(+) = PAAF.ASSIGNMENT_ID
order by TO_NUMBER(PAPF.EMPLOYEE_NUMBER);
Emp_num DOJ ORIGINAL_DOJ CHANGE_DATE DATE_TO TOTAL_REMUN STATUS
109575 01-DEC-2016 24-JUL-2014 01-MAY-19 31-DEC-12 250000 Proposed
109575 01-DEC-2016 24-JUL-2014 01-APR-19 31-DEC-12 100000 Approved
You can use conditional ordering for each employee separately, like here:
-- sample rows
with salaries (emp_id, name, salary, date_to, status) as (
select 1001, 'Orange', 1400, date '4712-12-31', 'Rejected' from dual union all
select 1001, 'Orange', 1200, date '4712-12-31', 'Approved' from dual union all
select 1002, 'Red', 2500, date '4712-12-31', 'Approved' from dual union all
select 1003, 'Blue', 2700, date '4712-12-31', 'Proposed' from dual union all
select 1004, 'Green', 2200, date '2012-07-31', 'Approved' from dual union all
select 1005, 'White', 1200, date '4712-12-31', 'Approved' from dual union all
select 1005, 'White', 1300, date '4712-12-31', 'Rejected' from dual )
-- end of sample data
select emp_id, name, salary, date_to, status
from (
select s.*,
row_number() over (partition by emp_id
order by case status when 'Approved' then 1 end) rn
from salaries s
where date_to = date '4712-12-31')
where rn = 1
Result:
EMP_ID NAME SALARY DATE_TO STATUS
---------- ------ ---------- ----------- --------
1001 Orange 1200 4712-12-31 Approved
1002 Red 2500 4712-12-31 Approved
1003 Blue 2700 4712-12-31 Proposed
1005 White 1200 4712-12-31 Approved
If the STATUS takes only two values, "Approved" and "Proposed", you can order by STATUS and fetch the first row. If you have (or in the future you'll have) more statuses and you want to define a priority add a column in the select with a "CASE" that assigns to each status the corresponding priority. Then you order by this column and you fetch the first row....

Oracle - Join most recent status at the time of activity

I have two tables - one with employee activity and one with employee_status. The issue is the employee status changes over time, so I need to join the status as it was at the time of the session.
>>> employee_activity
id session_start
emp1 1/1/2019
emp1 2/22/2019
emp1 3/1/2019
emp2 1/4/2019
emp2 2/23/2019
>>> employee_status
id status effective date
emp1 a 1/1/2018
emp1 b 2/1/2019
emp1 c 3/5/2019
emp2 a 6/1/2018
emp2 b 1/1/2019
So I started writing something that will make sure it's ignoring statuses after the activity, but I'm struggling a bit with figuring out how to only select the most recent status. The query needs join only the status with the max
effective date that is less than the session start
SELECT * FROM employee_activity a
LEFT join employee_status s on a.id = s.id WHERE s.effective_date <= a.session_start
-- how do I join only the most recent status?
The desired output from the two tables above would be
>>> my_output
id session_start status
emp1 1/1/2019 a
emp1 2/22/2019 b
emp1 3/1/2019 b
emp2 1/4/2019 b
emp2 2/23/2019 b
Thanks!!
Calculate first the validity interval from the STATUS, i.e. instead of EFFECTIVE_DATE you have starting and ending timestamp.
Note, that I use a default open end date and I subtract one second from the end date to get closed interval which can be queried using BETWEEN.
Than simple join on the key and add the between constraint for the time:
with emp as (
select ID, STATUS, EFFECTIVE_DATE status_valid_from,
lead(EFFECTIVE_DATE - INTERVAL '1' SECOND,1,DATE'2500-01-01')
over (partition by id order by EFFECTIVE_DATE) as status_valid_to
from employee_status)
SELECT a.id, a.SESSION_START, s.STATUS, s.STATUS_VALID_FROM
FROM employee_activity a
LEFT join emp s
on a.id = s.id and session_start between s.status_valid_from and s.status_valid_to
order by 1,2;
ID SESSION_START S STATUS_VALID_FROM
---- ------------------- - -------------------
emp1 01.01.2019 00:00:00 a 01.01.2018 00:00:00
emp1 22.02.2019 00:00:00 b 01.02.2019 00:00:00
emp1 01.03.2019 00:00:00 b 01.02.2019 00:00:00
emp2 04.01.2019 00:00:00 b 01.01.2019 00:00:00
emp2 23.02.2019 00:00:00 b 01.01.2019 00:00:00
Sample Data
create table employee_activity as
select 'emp1' id, to_date('1/1/2019','mm/dd/yyyy') session_start from dual union all
select 'emp1' id, to_date('2/22/2019','mm/dd/yyyy') session_start from dual union all
select 'emp1' id, to_date('3/1/2019','mm/dd/yyyy') session_start from dual union all
select 'emp2' id, to_date('1/4/2019','mm/dd/yyyy') session_start from dual union all
select 'emp2' id, to_date('2/23/2019','mm/dd/yyyy') session_start from dual;
create table employee_status as
select 'emp1' id, 'a'status, to_date('1/1/2018','mm/dd/yyyy') effective_date from dual union all
select 'emp1' id, 'b'status, to_date('2/1/2019','mm/dd/yyyy') effective_date from dual union all
select 'emp1' id, 'c'status, to_date('3/5/2019','mm/dd/yyyy') effective_date from dual union all
select 'emp2' id, 'a'status, to_date('6/1/2018','mm/dd/yyyy') effective_date from dual union all
select 'emp2' id, 'b'status, to_date('1/1/2019','mm/dd/yyyy') effective_date from dual;
You can do this using a correlated subquery:
select ea.*,
(select max(es.status) keep (dense_rank first order by es.effective_date desc)
from employee_status es
where es.id = ea.id and es.effective_date <= ea.session_start
) as status
from employee_activity ea;
In Oracle 12C+, there is the more intuitive:
select ea.*,
(select es.status
from employee_status es
where es.id = ea.id and es.effective_date <= ea.session_start
order by es.effective_date desc
fetch first 1 row only
) as status
from employee_activity ea;