select minimum date after maximum gap oracle - oracle

I have data for a member like below
EFF_DT-Term_dt
1/1/13-7/31/14
1/1/15-3/31/15
5/1/15-5/31/15
6/1/15-12/31/15
1/1/16-12/31/16
Here there are 2 gaps - after 7/31/14 and 3/31/15. I want to select the row 5/1/15-5/31/15 as it is the minimum date after maximum gap. I tried using
select ( FIRST_VALUE(EFF_DT) OVER (PARTITION BY MemberID ORDER BY FLAG DESC) AS CUR_EFF_DT)
from
(
select EFF_DT,
CASE WHEN LAG(TERM_DT, 1) OVER (PARTITION BY MemberID ORDER BY TERM_DT) = EFF_DT - 1 THEN 0
ELSE sequence.nextval
END AS FLAG
from effective_dates_table).
This is giving correct result, but i don't want to use sequence Is there any other easiest way to do this?

Here's one way... compute the differences using the analytic LAG() function, then group by member_id and use the aggregate LAST() function.
NOTE: there may be more than one pair of rows with the same, greatest gap between term_dt and the following eff_dt. You must clarify which row should be selected if that happens. The solution below picks the earliest occurrence (if this happens). If you want the latest occurrence, change MIN to MAX. If you want something else, just say what the requirement is.
with
inputs ( member_id, eff_dt, term_dt ) as (
select 101, to_date('1/1/13', 'mm/dd/yy'), to_date('7/31/14' , 'mm/dd/yy') from dual union all
select 101, to_date('1/1/15', 'mm/dd/yy'), to_date('3/31/15' , 'mm/dd/yy') from dual union all
select 101, to_date('5/1/15', 'mm/dd/yy'), to_date('5/31/15' , 'mm/dd/yy') from dual union all
select 101, to_date('6/1/15', 'mm/dd/yy'), to_date('12/31/15', 'mm/dd/yy') from dual union all
select 101, to_date('1/1/16', 'mm/dd/yy'), to_date('12/31/16', 'mm/dd/yy') from dual
)
-- End of simulated inputs (for testing only, not part of the solution).
-- Use your actual table and column names in the SQL query below.
select member_id,
min(eff_dt) keep (dense_rank last order by diff nulls first) as eff_dt,
min(term_dt) keep (dense_rank last order by diff nulls first) as term_dt
from (
select member_id, eff_dt, term_dt,
eff_dt - lag(term_dt) over (partition by member_id order by eff_dt) as diff
from inputs
)
group by member_id
;
MEMBER_ID EFF_DT TERM_DT
--------- ------------------- -------------------
101 2015-01-01 00:00:00 2015-03-31 00:00:00

Related

alternate of intersect set operator in oracle

I have a table emp1 wherein I am interested in only the employees who have joined with salary less than 2000 and whose salary is greater than 2000 now. This is the case with only one person Ward as shown below. I prepared the answer with intersect but wanted to know if there is more efficient way of doing it .Please let me know that will be of great help to me
(select empno,deptno
from emp1
where sal<2000
group by empno,hiredate,deptno
)
intersect
(select empno,deptno
from emp1
where sal>2000
group by empno,hiredate,deptno
)
Thanks
First, here's how you can get the specific employees who satisfy your conditions (as modified in a comment): Earliest salary < 2000, current (most recent) salary > 2500. Note that in my sample data employee 1008 started at 1300 and had salary > 2500 at some point, but his current salary is < 2500 so he is not selected.
The query is as efficient as possible: it performs a standard aggregation and nothing else. The conditions are in the having clause. The first/last aggregate function, even though it is exceptionally useful, is ignored by a vast majority of programmers - for no good reason.
with
sal_hist (empno, sal_date, sal) as (
select 1003, date '2000-01-01', 2300 from dual union all
select 1003, date '2008-01-01', 2600 from dual union all
select 1008, date '2002-03-20', 1300 from dual union all
select 1008, date '2005-01-31', 2600 from dual union all
select 1008, date '2013-11-01', 2400 from dual union all
select 2025, date '2008-03-01', 1900 from dual union all
select 2025, date '2015-04-01', 2550 from dual
)
select empno
from sal_hist
group by empno
having min(sal) keep (dense_rank first order by sal_date) < 2000
and min(sal) keep (dense_rank last order by sal_date) > 2500
;
EMPNO
----------
2025
To get the count of such employees, wrap the above query within an outer query, with select count(*) as my_count from ( <above query> ).
For extra credit, try to understand why the following query also works. It's more compact (and possibly faster, even though not by much), but a bit harder to understand - and especially, to understand why I need min(empno) rather than simply empno or * within the count() call.
select count(min(empno)) as my_count
from sal_hist
group by empno
having min(sal) keep (dense_rank first order by sal_date) < 2000
and min(sal) keep (dense_rank last order by sal_date) > 2500
;

How to convert this code from oracle to redshift?

I am trying to implement the same in redshift and i am finding it little difficult to do that. Since redshift is in top of postgresql engine, if any one can do it in postgresql it would be really helpfull. Basically the code gets the count for previous two month at column level. If there is no count for exact previous month then it gives 0.
This is my code:
with abc(dateval,cnt) as(
select 201908, 100 from dual union
select 201907, 200 from dual union
select 201906, 300 from dual union
select 201904, 600 from dual)
select dateval, cnt,
last_value(cnt) over (order by dateval
range between interval '1' month preceding
and interval '1' month preceding ) m1,
last_value(cnt) over (order by dateval
range between interval '2' month preceding
and interval '2' month preceding ) m2
from (select to_date(dateval, 'yyyymm') dateval, cnt from abc)
I get error in over by clause. I tried to give cast('1 month' as interval) but still its failing. Can someone please help me with this windows function.
expected output:
Regards
This is how I would do it. In Redshift there's no easy way to generate sequences, do I select row_number() from an arbitrary table to create a sequence:
with abc(dateval,cnt) as(
select 201908, 100 union
select 201907, 200 union
select 201906, 300 union
select 201904, 600),
cal(date) as (
select
add_months(
'20190101'::date,
row_number() over () - 1
) as date
from <an arbitrary table to generate a sequence of rows> limit 10
),
with_lag as (
select
dateval,
cnt,
lag(cnt, 1) over (order by date) as m1,
lag(cnt, 2) over (order by date) as m2
from abc right join cal on to_date(dateval, 'YYYYMM') = date
)
select * from with_lag
where dateval is not null
order by dateval

How to get the earliest date of the lastmodifier?

I choose to pickup the earliest date and also the same row of another column which is lastmodifier. Is this possible to get the same row data just like vlookup. when I MIN(DATE) and will lookup the other column same row 's value.
I have try to get the min(date) and group by lastmodifier. However, I don't know which lastmodifier does the script pick.
with CASECODESTATS (ORDBATCH,PO,EARLIEST,OPERATOR,count1,count2) as
(
select substr(code,1,15),substr(code,1,14),MIN(LSTUPDTIME),LSTMODIFIER,COUNT(c.code) ,0
from casecode c
where c.state='REVIEWED'
group by substr(code,1,15),LSTMODIFIER,substr(code,1,14)
union all
select substr(code,1,15),substr(code,1,14),MIN(LSTUPDTIME),LSTMODIFIER,0,COUNT(c.code)
from casecode c
where c.state ='WAREHOUSE RECEIVE'
group by substr(code,1,15),LSTMODIFIER,substr(code,1,14)
)
select ORDBATCH ORBATCH,sum(count1) REVIEWED ,sum(count2) WAREHOUSERECIEVE,MIN(EARLIEST) EARLIESTDATE,OPERATOR LSTMODIFIER,PO ORDERNUM from CASECODESTATS
where 1=1
group by ORDBATCH,OPERATOR,PO
order by ORDBATCH
I want to select the 1st lastmodifier in every ordernumber.
I am not sure if I understood correctly, sample data and expected output would be very helpful in such question. But I think you need this:
select substr(code, 1, 2) ordbatch, substr(code, 1, 1) ordernum, min(lstupdtime) earliest,
min(lstmodifier) keep (dense_rank first order by lstupdtime) lstmodifier,
count(case state when 'REVIEWED' then 1 end) reviewed,
count(case state when 'WAREHOUSE RECEIVE' then 1 end) warehouse
from casecode
where state in ('REVIEWED', 'WAREHOUSE RECEIVE')
group by substr(code,1,2), substr(code,1,1)
First: you do not need union, use conditional count with case. But the answer to your main question is min ... keep ... first. It finds value of some column (lstmodifier) where value of the other column (lstupdtime) is the lowest. min means that if two operators meets criteria we have to pick someone so we take first alphabetically. I don't know what you want to do in such situation, depending on that different solutions are posibble, for instance with rank and listagg.
Here is the example:
with casecode(code, state, lstupdtime, lstmodifier) as (
select 'A1', 'REVIEWED', date '2018-12-25', 'Clark' from dual union all
select 'A1', 'OTHER', date '2018-12-25', 'Clark' from dual union all
select 'A1', 'WAREHOUSE RECEIVE', date '2018-12-25', 'Clark' from dual union all
select 'A1', 'WAREHOUSE RECEIVE', date '2018-12-24', 'Jones' from dual union all
select 'B1', 'WAREHOUSE RECEIVE', date '2018-12-25', 'Clark' from dual union all
select 'B2', 'WAREHOUSE RECEIVE', date '2018-12-25', 'Clark' from dual
)
select substr(code, 1, 2) ordbatch, substr(code, 1, 1) ordernum, min(lstupdtime) earliest,
min(lstmodifier) keep (dense_rank first order by lstupdtime) lstmodifier,
count(case state when 'REVIEWED' then 1 end) reviewed,
count(case state when 'WAREHOUSE RECEIVE' then 1 end) warehouse
from casecode
where state in ('REVIEWED', 'WAREHOUSE RECEIVE')
group by substr(code,1,2), substr(code,1,1)
I used shorter substr for code manipulation to make things clear, use 14 and 15 instead. Jones is picked, because his lstupdtime is the lowest:
ORDBATCH ORDERNUM EARLIEST LSTMODIFIER REVIEWED WAREHOUSE
-------- -------- ----------- ----------- ---------- ----------
A1 A 2018-12-24 Jones 1 2
B1 B 2018-12-25 Clark 0 1
B2 B 2018-12-25 Clark 0 1

Using a single select statment to get the next row from a table or return the first row if the end of table is reached

I have a table say STAFF like below:
STAFF_NAME
============
ALEX
BERNARD
CARL
DOMINIC
EMMA
Now, I want to write a stored function with a single argument. E.g. GET_NEXT_STAFF(CURRENT_STAFF).
The input and output should be like:
Input | Output
=====================
NULL | ALEX
ALEX | BERNARD
BERNARD | CARL
EMMA | ALEX (Start from the beginning of the table again)
I know how to handle this problem using PL/SQL, but is it possible to deal with this problem with a single select statement?
In the solution below, I assume the rows are ordered alphabetically by names. They may be ordered by another column in the same table (for example by hire date, or by salary, etc. - it doesn't matter) - then the name of that column should be used in the ORDER BY clause of the two analytic functions.
The input name is passed in as a bind variable, :input_staff_name. The solution uses pure SQL, with no need for functions (PL/SQL), but if you must make it into a function, you can adapt it easily.
Edit: In my original answer I missed the required behavior when the input is null. The last line of code (excluding the semicolon) takes care of that. As written currently, the query returns ALEX (or in general the first value in the table) when the input is null, and it returns no rows when the input is not null and not in the table. If instead the requirement is to return the first name when the input is null or not found in the table, then it can be accommodated easily by removing and :input_staff_name is null from the last line.
with
tbl ( staff_name ) as (
select 'ALEX' from dual union all
select 'BERNARD' from dual union all
select 'CARL' from dual union all
select 'DOMINIC' from dual union all
select 'EMMA' from dual
),
prep ( staff_name, next_name, first_name ) as (
select staff_name,
lead(staff_name) over (order by staff_name),
first_value (staff_name) over (order by staff_name)
from tbl
)
select nvl(next_name, first_name) as next_staff_name
from prep
where staff_name = :input_staff_name
or (next_name is null and :input_staff_name is null)
;
Based on the answer from #mathguy I have made a few changes that seem to work. I have added the follow
UNION ALL
SELECT NULL
FROM DUAL
and
WHERE NVL (staff_name, 'X') = NVL (NULL, 'X');
The full code
WITH tbl (staff_name) AS
(SELECT 'ALEX' FROM DUAL
UNION ALL
SELECT 'BERNARD' FROM DUAL
UNION ALL
SELECT 'CARL' FROM DUAL
UNION ALL
SELECT 'DOMINIC' FROM DUAL
UNION ALL
SELECT 'EMMA' FROM DUAL
UNION ALL
SELECT NULL
FROM DUAL),
prep (staff_name,
next_name,
first_name,
last_name) AS
(SELECT staff_name,
LEAD (staff_name) OVER (ORDER BY staff_name),
FIRST_VALUE (staff_name) OVER (ORDER BY staff_name),
LAG (staff_name) OVER (ORDER BY staff_name)
FROM tbl)
SELECT NVL (next_name, first_name) AS next_staff_name
FROM prep
WHERE NVL (staff_name, 'X') = NVL (:input_staff_name, 'X');

how to combine multiple rows into one row in table in oracle and store result in other table

sample data: table consists of 96000 rows and i want to combine them into 16000 rows
row 1:1255467861 40825825 IDF+0149502016010615210300000396000026+0000651+ 00000000000000+|
row 2:000000000+0000000+0000000+000000677+02 YY 0444410100000 00 0001000000054+10001EB4200002+00000+0000052+0000000+0000000+|
row 3:00000 00000+00000+0000000+0000000+0000000+00000 00000+00000+0000000+0000000+0000000+00000 00000+00000+0000000+0000000+|
row 4:0000000+00000 00000+00000+0000000+0000000+0000000+00 004 1 000000000000 0000000000 M5|
row 5: 00000000 +00000000000000000000000000000000000000+00000000001011 Y 000000000+|
row 6:0000000+0000000+0000000+AB0002210000000000FIABMM81 15067195 0000000000009403228870|
Assuming that you know what the criteria (column1, column2 in the example) for grouping your rows is, I suggest you to use aggregate functions functions such as SUM(), MAX(), and COUNT() to aggregate data, Group by the criteria, and insert into the target table.
E.G.
insert into my_target_table
Select column1, column2, sum(column3), max(column4), count(1) as column5
from my_source_table
group by column_1, column2
Assuming "row 1, row 2, row 3" has some meaning, you must have another column somewhere that shows the order. In my setup below, I assume you have an "id" (not necessarily consecutive numbers starting from 1; it can be number, or it can be date, etc. - for illustration I use some random numbers, they can also be fractional, or negative etc. - I build "row numbers" form that). If you don't have ANY kind of ordering, in the "order by" clause for the row_number() function you can simply "order by 1". However, I assume you also want to capture the minimum value of the "id" from each group of six (again, the id may be a date or timestamp or whatever).
The strategy is to create "row numbers". It works better if everything starts from 0, so I subtract 1 from the row_number() values. Then group by trunc(rn/6) and order by mod(rn, 6).
To concatenate the strings use listagg. I assume you wanted the strings concatenated with NO separator between them; if you do want them separated, for example, by #, then change the second argument to listagg from '' to '#'.
Query (including setup data, I shortened the input strings and I added a few more to show how everything works):
with test_data(id, input_str) as (
select 1, '12554861 40825825 IDF+0140000000+|' from dual union all
select 2, '0000000+00052+0000000+0000000+|' from dual union all
select 4, '00000 000+00000+0+00000+000+0000000+|' from dual union all
select 9, '00000+0000000+000000000000 M5|' from dual union all
select 14, '000000 +000000000011 Y 000000000+|' from dual union all
select 23, '000000IABMM81 15067195 0000003228870|' from dual union all
select 31, '125508825 IDF+0143333300000+|' from dual union all
select 32, '0000+052+0000AXZC000+0000000+|' from dual union all
select 37, '000MMDOQ000+0+00000+0000000+0000000+|' from dual union all
select 45, '000000 +00000000001011 Y 000000000+|' from dual union all
select 46, '00000+0000FIAB1 15067195 000CCV70|' from dual
),
has_rn (id, input_str, rn) as (
select id, input_str, row_number() over (order by id) - 1
from test_data
)
select min(id) as id,
listagg(input_str, '') within group (order by mod(rn, 6)) as output_str
from has_rn
group by trunc(rn/6);
Output (using the sample inputs in the test_data cte):
ID OUTPUT_STR
-- -------------------------------------------------------------------------------------------------------------------
1 12554861 40825825 IDF+0140000000+|0000000+00052+0000000+0000000+|00000 000+00000+0+00000+000+0000000+|00000+0000000+000000000000 M5|000000 +000000000011 Y 000000000+|000000IABMM81 15067195 0000003228870|
31 125508825 IDF+0143333300000+|0000+052+0000AXZC000+0000000+|000MMDOQ000+0+00000+0000000+0000000+|000000 +00000000001011 Y 000000000+|00000+0000FIAB1 15067195 000CCV70|

Resources