Need to pull by date, that a value appears from different tables - union-all

I have tried unions, joins, distinct and minimum functions with some success, but still slightly off.
Example of the tables are below
ID date
1 01-02-2020
2 02-02-2020
3 04-06-2020
4 01-03-2019
ID date
1 01-03-2018
2 07-02-2019
3 10-02-2020
4 09-02-2020
What I want is
ID date
1 01-02-2020
2 01-03-2019
3 01-03-2018
4 07-02-2019
5 10-02-2020

It is not pretty
But a Full OUT JOIN which MySQL doesn't have, of both tables did the trick
CREATE TABLE tab1 (
`ID` INTEGER,
`Value_ID` INTEGER,
`date` VARCHAR(10)
);
INSERT INTO tab1
(`ID`, `Value_ID`, `date`)
VALUES
('1', '1', '01-02-2020'),
('2', '2', '02-02-2020'),
('3', '3', '04-06-2020'),
('4', '2', '01-03-2019');
CREATE TABLE tab2 (
`ID` INTEGER,
`Value_ID` INTEGER,
`date` VARCHAR(10)
);
INSERT INTO tab2
(`ID`, `Value_ID`, `date`)
VALUES
('1', '3', '01-03-2018'),
('2', '4', '07-02-2019'),
('3', '5', '10-02-2020'),
('4', '4', '09-02-2020');
SELECT COALESCE(t2.`Value_ID`,t1.`Value_ID`) as `Value_ID`,IF (IFNULL(t1.`date`,'31-12-9999') <= t2.`date`,t1.`date`,t2.`date`)
FROM (SELECT `Value_ID`, MIN(`date`) as `date` FROM tab1 GROUP BY `Value_ID`) t1
right JOIN (SELECT `Value_ID`, MIN(`date`) as `date` FROM tab2 GROUP BY `Value_ID`) t2
ON t1.`Value_ID` = t2.`Value_ID`
UNION
SELECT COALESCE(t1.`Value_ID`,t2.`Value_ID`),IF (t1.`date` <= IFNULL(t2.`date`,'31-12-9999'),t1.`date`,t2.`date`)
FROM (SELECT `Value_ID`, MIN(`date`) as `date` FROM tab1 GROUP BY `Value_ID`) t1
LEFT JOIN (SELECT `Value_ID`, MIN(`date`) as `date` FROM tab2 GROUP BY `Value_ID`) t2
ON t1.`Value_ID` = t2.`Value_ID`
ORDER BY `Value_ID`
Value_ID | IF (IFNULL(t1.`date`,'31-12-9999') <= t2.`date`,t1.`date`,t2.`date`)
-------: | :-------------------------------------------------------------------
1 | 01-02-2020
2 | 01-03-2019
3 | 01-03-2018
4 | 07-02-2019
5 | 10-02-2020
db<>fiddle here
You can use Window functions
Schema (MySQL v8.0)
CREATE TABLE tab1 (
`ID` INTEGER,
`Value_ID` INTEGER,
`date` VARCHAR(10)
);
INSERT INTO tab1
(`ID`, `Value_ID`, `date`)
VALUES
('1', '1', '01-02-2020'),
('2', '2', '02-02-2020'),
('3', '3', '04-06-2020'),
('4', '2', '01-03-2019');
CREATE TABLE tab2 (
`ID` INTEGER,
`Value_ID` INTEGER,
`date` VARCHAR(10)
);
INSERT INTO tab2
(`ID`, `Value_ID`, `date`)
VALUES
('1', '3', '01-03-2018'),
('2', '4', '07-02-2019'),
('3', '5', '10-02-2020'),
('4', '4', '09-02-2020');
Query #1
SELECT
`Value_ID`, `date`
FROM
(SELECT
`Value_ID`, `date`
, ROW_NUMBER() OVER (PARTITION BY `Value_ID` ORDER BY `date` ASC) rn
FROM
(SELECT `Value_ID`, MIN(`date`) as `date` FROM tab1 GROUP BY `Value_ID`
UNION
SELECT `Value_ID`, MIN(`date`) as `date` FROM tab2 GROUP BY `Value_ID`) t1
) t3
WHERE rn = 1;
Value_ID
date
1
01-02-2020
2
01-03-2019
3
01-03-2018
4
07-02-2019
5
10-02-2020
View on DB Fiddle

Related

Oracle PLSQL generating absences

have some PLSQL code that generates a list of dates from a range, which seems to be working fine.
As part of a larger project I want to generate a procedure that will create a list of absences for each employee.
My first step is to use the MINUS command to remove all the holidays, which fall into the range of dates. Is there an easy way of doing this instead of comparing each holiday one at a time (there maybe several in the table) against the GENERATED dates.
If possible, I would prefer breaking all these tasks into small procedures or functions for easy debugging and legibility.
If there is an easier way to do this I am open to all suggestions. Thanks in advance for your help, expertise and patience.
ALTER SESSION SET
NLS_DATE_FORMAT = 'MMDDYYYY HH24:MI:SS';
create table holidays(
holiday_date DATE,
holiday_name VARCHAR2(20)
);
INSERT into holidays
(holiday_date,
holiday_name)
VALUES
(
TO_DATE('2021/07/21 00:00:00', 'yyyy/mm/dd hh24:mi:ss'), 'July 21 2021');
CREATE OR REPLACE PROCEDURE generate_dates
(
p_start_date IN DATE,
p_end_date IN DATE
)
AS
l_day DATE := p_start_date;
BEGIN
WHILE l_day <= p_end_date
LOOP
DBMS_OUTPUT.PUT_LINE(l_day);
l_day := l_day + 1;
END LOOP;
END generate_dates;
EXEC generate_dates(TRUNC(SYSDATE),TRUNC(SYSDATE+10));
Create table employees(
employee_id NUMBER(6),
first_name VARCHAR2(20),
last_name VARCHAR2(20),
card_num VARCHAR2(10),
work_days VARCHAR2(7)
);
ALTER TABLE employees
ADD ( CONSTRAINT employees_pk
PRIMARY KEY (employee_id));
INSERT INTO employees
(
EMPLOYEE_ID,
first_name,
last_name,
card_num,
work_days
)
WITH names AS (
SELECT 1, 'Jane', 'Doe', 'F123456', 'NYYYYYN' FROM dual UNION ALL
SELECT 2, 'Madison', 'Smith', 'R33432','NYYYYYN'
FROM dual UNION ALL
SELECT 3, 'Justin', 'Case', 'C765341','NYYYYYN'
FROM dual UNION ALL
SELECT 4, 'Mike', 'Jones', 'D564311','NYYYYYN' FROM dual
) SELECT * FROM names
-- check to see if working for that day. Byte=Y for Yes
SELECT SUBSTR( work_days, to_char(TRUNC(SYSDATE), 'D'),1) Work_Day
FROM employees
create table timeoff(
seq_num integer GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
employee_id NUMBER(6),
timeoff_date DATE,
timeoff_type VARCHAR2(1),
constraint timeoff_chk check (timeoff_date=trunc(timeoff_date, 'dd')),
constraint timeoff_pk primary key (employee_id, timeoff_date)
);
INSERT INTO timeoff (EMPLOYEE_ID,TIMEOFF_DATE,TIMEOFF_TYPE
)
WITH dts AS (
SELECT 1, to_date('20210726 00:00:00','YYYYMMDD HH24:MI:SS'),'V' FROM dual UNION ALL
SELECT 2, to_date('20210726 00:00:00','YYYYMMDD HH24:MI:SS'),'V' FROM dual UNION ALL
SELECT 2, to_date('20210727 00:00:00','YYYYMMDD HH24:MI:SS'),'V' FROM dual )
SELECT * FROM dts
CREATE TABLE emp_attendance(
seq_num integer GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
employee_id NUMBER(6),
start_date DATE,
end_date DATE,
week_number NUMBER(2),
create_date DATE DEFAULT SYSDATE
);
create table absences(
seq_num integer GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
employee_id NUMBER(6),
absent_date DATE,
constraint absence_chk check (absent_date=trunc(absent_date, 'dd')),
constraint absence_pk primary key (employee_id, absent_date)
);
INSERT INTO emp_attendance ( EMPLOYEE_ID, START_DATE,END_DATE,WEEK_NUMBER)
WITH dts AS (
SELECT 1, to_date('20210728 13:10:00','YYYYMMDD HH24:MI:SS'),
to_date('20210728 23:15:00','YYYYMMDD HH24:MI:SS'), 30 FROM dual UNION ALL
SELECT 2, to_date('20210728 12:10:10','YYYYMMDD HH24:MI:SS'),
to_date('20210728 20:15:01','YYYYMMDD HH24:MI:SS'), 30 FROM dual)
SELECT * FROM dts
CREATE OR REPLACE TYPE obj_date IS OBJECT (
date_val DATE
);
CREATE OR REPLACE TYPE nt_date IS TABLE OF obj_date;
CREATE OR REPLACE FUNCTION generate_dates(
p_from IN DATE
,p_to IN DATE)
RETURN nt_date PIPELINED
IS
-- normalize inputs to be as-of midnight
v_from DATE :=
TRUNC(NVL(p_from, SYSDATE));
v_to DATE := TRUNC(NVL(p_to, SYSDATE));
BEGIN
LOOP
EXIT WHEN v_from > v_to;
PIPE ROW (obj_date(v_from));
v_from := v_from + 1; -- next. calendar day
END LOOP;
RETURN;
END generate_dates;
INSERT INTO absences
(employee_id, absent_date)
SELECT e.employee_id,
c.date_val
FROM employees e
INNER JOIN table(generate_dates(date '2021-07-20', DATE '2021-07-31')) c
PARTITION BY ( e.employee_id )
ON (SUBSTR(e.work_days,
TRUNC(c.date_val) -
TRUNC(c.date_val, 'IW') + 1, 1) = 'Y')
WHERE NOT EXISTS (
SELECT 1
FROM holidays h
WHERE c.date_val = h.holiday_date
)
AND NOT EXISTS(
SELECT 1
FROM timeoff t
WHERE e.employee_id = t.employee_id
AND t.timeoff_date = c.date_val
)
AND NOT EXISTS(
SELECT 1
FROM emp_attendance ea
WHERE e.employee_id = ea.employee_id
AND TRUNC(ea.start_date) = c.date_val
)
ORDER BY
e.employee_id,
c.date_val
;
Don't use lots of procedures and/or a functions; just use a single query:
SELECT e.employee_id,
c.day
FROM employees e
INNER JOIN (
WITH calendar ( start_date, end_date ) AS (
SELECT DATE '2021-07-01', DATE '2021-07-30' FROM DUAL
UNION ALL
SELECT start_date + 1, end_date
FROM calendar
WHERE start_date + 1 <= end_date
)
SELECT start_date AS day
FROM calendar
) c
PARTITION BY ( e.employee_id )
ON (SUBSTR(e.work_days, TRUNC(c.day) - TRUNC(c.day, 'IW') + 1, 1) = 'Y')
WHERE NOT EXISTS (
SELECT 1
FROM holidays h
WHERE c.day = h.holiday_date
)
AND NOT EXISTS(
SELECT 1
FROM timeoff t
WHERE e.employee_id = t.employee_id
AND t.timeoff_date = c.day
)
ORDER BY
e.employee_id,
c.day
Notes:
This assumes that your work_days column aligns with the ISO week; if it does not then you will need to adjust the substring.
Do not use TO_CHAR(date_value, 'D') as users will get different results depending on their NLS_TERRITORY session setting.
db<>fiddle here

Sql query to filter out the overlapping dates

Version
start_date
end_date
1
2005-11-23
2005-11-23
2
2005-11-23
2005-11-23
3
2005-11-23
2008-10-23
4
2008-10-23
2010-05-18
5
2011-05-13
2012-05-19
In the above table instead of keeping version 1,2,3,4 we can keep version 1 starting from '2005-11-23' to '2010-05-18' since all these verions are overlapping and keep version 5 as it is.
Ouput needed
..............
Version
start_date
end_date
1
2005-11-23
2010-05-18
5
2011-05-13
2012-05-19
How we can frame sql query for thi scenario?
Hive or Postgresql
CREATE TABLE my_dates (
"Version" INTEGER,
start_date date,
end_date date
);
INSERT INTO my_dates
("Version",start_date, end_date)
VALUES
('1', '2005-11-23', '2005-11-23'),
('2', '2005-11-23', '2005-11-23'),
('3', '2005-11-23', '2008-10-23'),
('4', '2008-10-23', '2010-05-18'),
('5', '2011-05-13', '2012-05-19');
Query #1
with my_overlaps AS (
select
*,
LAG(end_date) OVER (ORDER BY "Version") >= start_date as overlap
from my_dates
),
selected AS (
SELECT
"Version",
start_date,
end_date ,
LEAD("Version") OVER (ORDER BY "Version") AS next_version
FROM
my_overlaps
where overlap=false or
overlap is null
)
select
s."Version",
s.start_date,
CASE
WHEN md.end_date IS NULL THEN s.end_date
ELSE md.end_date
END as end_date
FROM
selected s
LEFT JOIN
my_dates md on s.next_version -1 = md."Version";
Version
start_date
end_date
1
2005-11-23T00:00:00.000Z
2010-05-18T00:00:00.000Z
5
2011-05-13T00:00:00.000Z
2012-05-19T00:00:00.000Z
View on DB Fiddle
Schema (PostgreSQL v13)
CREATE TABLE my_dates (
"Version" INTEGER,
start_date date,
end_date date
);
INSERT INTO my_dates
("Version",start_date, end_date)
VALUES
('1', '2005-11-23', '2005-11-23'),
('2', '2005-11-23', '2005-11-23'),
('3', '2005-11-23', '2008-10-23'),
('4', '2008-10-23', '2010-05-18'),
('5', '2011-05-13', '2012-05-19');
Query #1
with my_overlaps AS (
select
*,
LAG(end_date) OVER (ORDER BY "Version") >= start_date as overlap
from my_dates
),
selected AS (
SELECT
"Version",
start_date,
end_date ,
LEAD("Version") OVER (ORDER BY "Version") AS next_version
FROM
my_overlaps
where overlap=false or
overlap is null
)
select
s."Version",
s.start_date::text,
CASE
WHEN md.end_date IS NULL THEN s.end_date::text
ELSE md.end_date::text
END as end_date
FROM
selected s
LEFT JOIN
my_dates md on s.next_version -1 = md."Version";
Version
start_date
end_date
1
2005-11-23
2010-05-18
5
2011-05-13
2012-05-19
View on DB Fiddle
Update 1
Lag/Lead functions now assigned default values
Schema (PostgreSQL v13)
CREATE TABLE my_dates (
"Version" INTEGER,
start_date date,
end_date date
);
INSERT INTO my_dates
("Version",start_date, end_date)
VALUES
('1', '2005-11-23', '2005-11-23'),
('2', '2005-11-23', '2012-05-19');
Query #1
with my_overlaps AS (
select
*,
LAG(end_date,1,null) OVER (ORDER BY "Version") >= start_date as overlap
from my_dates
),
selected AS (
SELECT
"Version",
start_date,
end_date ,
LEAD("Version",1,3) OVER (ORDER BY "Version") AS next_version
FROM
my_overlaps
where overlap=false or
overlap is null
)
select
s."Version",
s.start_date::text,
CASE
WHEN md.end_date IS NULL THEN s.end_date::text
ELSE md.end_date::text
END as end_date
FROM
selected s
LEFT JOIN
my_dates md on s.next_version -1 = md."Version";
ORDER BY
s."Version";
Version
start_date
end_date
1
2005-11-23
2012-05-19
View on DB Fiddle
With original dataset
Schema (PostgreSQL v13)
CREATE TABLE my_dates (
"Version" INTEGER,
start_date date,
end_date date
);
INSERT INTO my_dates
("Version",start_date, end_date)
VALUES
('1', '2005-11-23', '2005-11-23'),
('2', '2005-11-23', '2005-11-23'),
('3', '2005-11-23', '2008-10-23'),
('4', '2008-10-23', '2010-05-18'),
('5', '2011-05-13', '2012-05-19');
Query #1
with my_overlaps AS (
select
*,
LAG(end_date,1,null) OVER (ORDER BY "Version") >= start_date as overlap
from my_dates
),
selected AS (
SELECT
"Version",
start_date,
end_date ,
LEAD("Version",1,3) OVER (ORDER BY "Version") AS next_version
FROM
my_overlaps
where overlap=false or
overlap is null
)
select
s."Version",
s.start_date::text,
CASE
WHEN md.end_date IS NULL THEN s.end_date::text
ELSE md.end_date::text
END as end_date
FROM
selected s
LEFT JOIN
my_dates md on s.next_version -1 = md."Version"
ORDER BY
s."Version";
Version
start_date
end_date
1
2005-11-23
2010-05-18
5
2011-05-13
2005-11-23
View on DB Fiddle
The safest way to handle this -- assuming that you can create stable sort on the rows (which version provides) -- uses a cumulative maximum instead of lag().
select min(version), min(start_date), min(end_date)
from (select t.*,
sum(case when prev_max_end_date >= start_date then 0 else 1 end) over
(order by start_date, version) as grp
from (select t.*,
max(end_date) over (order by start_date, version
rows between unbounded preceding and 1 preceding
) as prev_max_end_date
from t
) t
) t
group by grp;
This should work in any (reasonable) database. Here is a db<>fiddle that happens to use Postgres.
The issue with lag()/lead() approaches is that the overlap with earlier rows may not be on the "previous" row. For instance, consider this diagram (where lower case means start and upper case means end):
---a----b--B----c--C----d--D--e---A--E--
E overlaps with A. However, by any reasonable definition of "previous", A is not the previous row for E.

group orders based on crossing date ranges

I need to group order together with crossing their date ranges only
scenario A.
order 1, 1.3.2020-30.6.2020
order 2, 1.5.2020-31.8.2020
order 3, 31.7.2020-31.10.2020
order 4, 31.7.2020-31.12.2020
so the output should be
order 1, order 2
order 2, order 3, order 4
order1,3,4 are not grouped because their ranges don't cross at all
scenario B.
same as above plus another order
order 5, 1.1.2020-31.12.2020
so output will be
order 1, order 2, order 5
order 2, order 3, order 4, order 5
I tried Self Join to check which start date falls in that range.
so in the range of order 1 falls only the start date of order 2 -> we have one group
then in the range of order 2 fall both start dates of order 3 and 4 -> we have second group
but then for order 3 falls start date of order 4 and opposite -> that will give another 2 groups but they are invalid because order 2 is crossing their date ranges as well and shoul be included as well and becuase there will be 3 douplicates we should display it just once as in the desired output but this approach will fail.
Thanks
the result of MATCH_RECOGNIZE solution is incorrent because order 5 should be in both groups
I use some analitycal functions to solve this:
-- create table
Create table cross_dates (order_id number, start_date date , end_date date);
-- insert dates
insert into cross_dates values( 1, to_date('01.03.2020', 'dd.mm.yyyy'), to_date('30.06.2020', 'dd.mm.yyyy'));
insert into cross_dates values( 2, to_date('01.05.2020', 'dd.mm.yyyy'), to_date( '31.08.2020', 'dd.mm.yyyy'));
insert into cross_dates values( 3, to_date('31.07.2020', 'dd.mm.yyyy'), to_date( '31.08.2020', 'dd.mm.yyyy'));
insert into cross_dates values( 4, to_date('31.07.2020', 'dd.mm.yyyy'), to_date( '31.10.2020', 'dd.mm.yyyy'));
insert into cross_dates values( 5, to_date('01.01.2020', 'dd.mm.yyyy'), to_date( '31.12.2020', 'dd.mm.yyyy'));
-- SQL
select 'Order '|| min_order_id ||': ', listagg( order_id, ',') within group (order by order_id) list
from (
select distinct min_order_id, order_id from (
with dates (cur_date, end_date, order_id, start_date) as (
select start_date, end_date, order_id, start_date
from cross_Dates
union all
select cur_date + 1, end_date, order_id,start_date
from dates
where cur_date < end_date )
select d.order_id,
min(d.order_id) over(partition by greatest(d.start_date, cd.start_date)) min_order_id
from dates d, cross_Dates cd
where d.cur_date between cd.start_date and cd.end_date ))
group by min_order_id
having count(*) > 1;
Result:
Order 1: 1,2,5
Order 2: 2,3,4,5
-- add new column and update old records
alter table cross_dates add (item varchar2(1));
update cross_dates set item = 'A';
-- insert new records B
insert into cross_dates values( 1, to_date('01.01.2020', 'dd.mm.yyyy'), to_date( '30.06.2020', 'dd.mm.yyyy'), 'B');
insert into cross_dates values( 1, to_date('01.07.2020', 'dd.mm.yyyy'), to_date( '31.12.2020', 'dd.mm.yyyy'), 'B');
My assumption:
A and B are separate orders, not going in same groups even when crossing
order 1 B - has two records as a continuations - in my understanding counts like one order : order 1 B 01.01.2020 - 21.12.2020
If my assumption are correct the SQL could look like this:
select distinct min_order_id, order_id, item from (
with dates (cur_date, end_date, order_id, start_date, item) as (
select start_date, end_date, order_id, start_date, item
from cross_Dates
union all
select cur_date + 1, end_date, order_id,start_date, item
from dates
where cur_date < end_date )
select d.order_id, d.item,
min(d.order_id) over(partition by greatest(d.start_date, cd.start_date),d.item) min_order_id
from dates d, cross_Dates cd
where d.cur_date between cd.start_date and cd.end_date and d.item = cd.item )
order by item, min_order_id;
Result:
MIN_ORDER_ID ORDER_ID I
1 1 A
1 2 A
1 5 A
2 2 A
2 3 A
2 4 A
2 5 A
5 5 A
1 1 B
If my assumption are not ok please provide me what result should look like i this case.
:)
You can use MATCH_RECOGNIZE to find groups where the next value's start date is before, or equal to, the end date of all the previous values in the group. Then you can aggregate and exclude groups that would be entirely contained in another group:
WITH groups ( id, ids, start_date, end_date ) AS (
SELECT id,
LISTAGG( grp_id, ',' ) WITHIN GROUP ( ORDER BY start_date ),
MIN( start_date ),
MIN( end_date )
FROM (
SELECT t.id,
x.id AS grp_id,
x.start_date,
x.end_date
FROM table_name t
INNER JOIN table_name x
ON (
x.start_date >= t.start_date
AND x.start_date <= t.end_date
)
)
MATCH_RECOGNIZE (
PARTITION BY id
ORDER BY start_date
MEASURES
MATCH_NUMBER() AS mno
ALL ROWS PER MATCH
PATTERN ( FIRST_ROW GROUPED_ROWS* )
DEFINE GROUPED_ROWS AS (
GROUPED_ROWS.start_date <= MIN( end_date )
)
)
WHERE mno = 1
GROUP BY id
)
SELECT id,
ids
FROM groups g
WHERE NOT EXISTS (
SELECT 1
FROM groups x
WHERE g.ID <> x.ID
AND x.start_date <= g.start_date
AND g.end_date <= x.end_date
)
Which for the sample data:
CREATE TABLE table_name ( id, start_date, end_date ) AS
SELECT 'order 1', DATE '2020-03-01', DATE '2020-06-30' FROM DUAL UNION ALL
SELECT 'order 2', DATE '2020-05-01', DATE '2020-08-31' FROM DUAL UNION ALL
SELECT 'order 3', DATE '2020-07-31', DATE '2020-10-31' FROM DUAL UNION ALL
SELECT 'order 4', DATE '2020-07-31', DATE '2020-12-31' FROM DUAL;
Outputs:
ID | IDS
:------ | :----------------------
order 2 | order 2,order 3,order 4
order 1 | order 1,order 2
I you then:
INSERT INTO table_name ( id, start_date, end_date )
VALUES ( 'order 5', DATE '2020-01-01', DATE '2020-12-31' );
The output would be:
ID | IDS
:------ | :----------------------
order 2 | order 2,order 3,order 4
order 5 | order 5,order 1,order 2
db<>fiddle here

Correlated SQL Query using With Clause

I am trying to modify below query :
SELECT START_END.*, START_END.LOAD_TIME_END - START_END.LOAD_TIME_START
FROM
(
SELECT START.JOB_ID, START.LOAD_TIME_START, END.LOAD_TIME_END
FROM
(
SELECT JOB_ID,LOAD_TIME AS LOAD_TIME_START
FROM JOB_CTRL_RECON JCR
INNER JOIN
(
SELECT JOB_ID AS JOB_ID_S, MAX(RECON_ID) AS S_MAX_RECON_ID
FROM job_ctrl_recon
where job_id in ('1','2')
and count_type = 'Source'
GROUP BY JOB_ID
) SMAX
ON JCR.JOB_ID = SMAX.S_MAX_RECON_ID
AND JCR.RECON_ID = SMAX.RECON_ID
) START
INNER JOIN
(
SELECT JOB_ID,LOAD_TIME AS LOAD_TIME_END FROM JOB_CTRL_RECON JCR INNER JOIN
(
SELECT JOB_ID AS JOB_ID_T, MAX(RECON_ID) AS T_MAX_RECON_ID
FROM job_ctrl_recon
where job_id in ('1','2')
and count_type = 'Target'
GROUP BY JOB_ID
) TMAX
ON JCR.JOB_ID = TMAX.T_MAX_RECON_ID
AND JCR.RECON_ID = TMAX.RECON_ID
) END
ON START.JOB_ID = END.JOB_ID
) START_END
to a query using with clause as below :
How can i eliminate the multiple use of where job_id in ('1','2') using with clause something like below :
WITH A AS
(
SELECT JOB_ID FROM JOB_CTRL_RECON WHERE JOB_ID IN ('60','67')
)
SELECT START_END.*, START_END.LOAD_TIME_END - START_END.LOAD_TIME_START
FROM
(
SELECT START.JOB_ID, START.LOAD_TIME_START, END.LOAD_TIME_END
FROM
(
SELECT JOB_ID,LOAD_TIME AS LOAD_TIME_START
FROM JOB_CTRL_RECON JCR
INNER JOIN
(
SELECT JOB_ID AS JOB_ID_S, MAX(RECON_ID) AS S_MAX_RECON_ID
FROM job_ctrl_recon
where job_id in (????)
and count_type = 'Source'
GROUP BY JOB_ID
) SMAX
ON JCR.JOB_ID = SMAX.S_MAX_RECON_ID
AND JCR.RECON_ID = SMAX.RECON_ID
) START
INNER JOIN
(
SELECT JOB_ID,LOAD_TIME AS LOAD_TIME_END
FROM JOB_CTRL_RECON JCR
INNER JOIN
(
SELECT JOB_ID AS JOB_ID_T, MAX(RECON_ID) AS T_MAX_RECON_ID
FROM job_ctrl_recon
where job_id in (????)
and count_type = 'Target'
GROUP BY JOB_ID
) TMAX
ON JCR.JOB_ID = TMAX.T_MAX_RECON_ID
AND JCR.RECON_ID = TMAX.RECON_ID
) END
ON START.JOB_ID = END.JOB_ID
) START_END, A
WHERE START_END.JOB_ID = A.JOB_ID
Instead of WITH, you may be able to simplify the code using a KEEP with an analytic function. The code is a bit weird at first, but it lets you only specify the JOB_ID once and it only has to read from the table once.
--Load time start and end for each JOB_ID, based on the latest RECON_ID.
select
job_id,
max(load_time_start) load_time_start,
max(load_time_end) load_time_end,
max(load_time_end) - max(load_time_start) diff
from
(
--First and last load time for each set of rows, with some debug data.
select job_id, count_type, recon_id, load_time
,last_value(case when count_type = 'Source' then load_time else null end)
over (
partition by job_id, count_type
order by recon_id
rows between unbounded preceding and unbounded following
) load_time_start
,last_value(case when count_type = 'Target' then load_time else null end)
over (
partition by job_id, count_type
order by recon_id
rows between unbounded preceding and unbounded following
) load_time_end
from job_ctrl_recon
where job_id in (1,2)
)
group by job_id
order by job_id;
For this table and data:
create table job_ctrl_recon(job_id number, recon_id number, count_type varchar2(100), load_time date);
alter session set nls_date_format = 'DD-Mon-RR';
insert into job_ctrl_recon
select 1, 400, 'Source', '24-Feb-18' from dual union all
select 1, 40, 'Source', '23-Feb-18' from dual union all
select 1, 89, 'Target', '25-Feb-18' from dual union all
select 1, 8, 'Target', '25-Feb-18' from dual union all
select 2, 700, 'Source', '24-Feb-18' from dual union all
select 2, 8, 'Source', '23-Feb-18' from dual union all
select 2, 567, 'Target', '25-Feb-18' from dual union all
select 2, 45, 'Target', '25-Feb-18' from dual union all
select 2, 678, 'Target', '25-Feb-18' from dual;
commit;
These are the results:
JOB_ID LOAD_TIME_START LOAD_TIME_END DIFF
------ --------------- ------------- ----
1 2018-02-24 2018-02-25 1
2 2018-02-24 2018-02-25 1
Try this one. Hope it helps. Thanks.
old: where job_id in (????)
new: where job_id in (select JOB_ID from A)

Oracle pivot query results in a "command not properly ended" error

When firing the following query to a 10g Oracle database:
SELECT * FROM (
SELECT T1.ID, T2.ACCT_NO, ROW_NUMBER() OVER (PARTITION BY T1.ID ORDER BY T1.ID DESC) AS RRRRRRR
FROM TABLE1 T1
INNER JOIN TABLE T2 ON T1.ID = T2.ID
WHERE T1.ID = 666
)
PIVOT (MIN(T1.ID) AS ALIAS1 FOR RRRRRRR IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
I get an "command not properly ended" error.
I've searched for Oracle pivot examples and they all pretty much showed the same example.
What am I missing here?
As #APC pointed out there is no PIVOT function in oracle 10g, so you can use an aggregate function and a CASE, similar to this:
SELECT id, acct_no,
min(case when RRRRRRR = 1 then id end) as '1',
min(case when RRRRRRR = 2 then id end) as '2',
min(case when RRRRRRR = 3 then id end) as '3',
min(case when RRRRRRR = 4 then id end) as '4',
min(case when RRRRRRR = 5 then id end) as '5',
min(case when RRRRRRR = 6 then id end) as '6',
min(case when RRRRRRR = 7 then id end) as '7',
min(case when RRRRRRR = 8 then id end) as '8',
min(case when RRRRRRR = 9 then id end) as '9',
min(case when RRRRRRR = 10 then id end) as '10'
FROM
(
SELECT T1.ID, T2.ACCT_NO, ROW_NUMBER() OVER (PARTITION BY T1.ID ORDER BY T1.ID DESC) AS RRRRRRR
FROM TABLE1 T1
INNER JOIN TABLE T2 ON T1.ID = T2.ID
WHERE T1.ID = 666
) x
GROUP BY id, acct_no
Your syntax is impeccable. Unfortunately PIVOT was introduced in Oracle 11g and you're using 10g.

Resources