If first condition satisfies perform it, otherwise second condition in Oracle - sql

I am trying to take Employee by status from table. I have 2 statuses, If an employee has A condition take that row, otherwise take P status row with maximum oper_day It looks like below:
Table
---------------------------------------------------
id | emp_code | name | status | oper_day |
--------------------------------------------------
1 | 164094 | John | P | 2020-10-02 |
2 | 164094 | John | P | 2020-10-09 |
3 | 164094 | John | A | 2020-10-10 |
4 | 145890 | Mike | P | 2020-10-05 |
My result should look like below
--------------------------------
id | emp_code | name | status | oper_day |
--------------------------------------------------
1 | 164094 | John | A | 2020-10-10 |
2 | 145890 | Mike | P | 2020-10-05 |
Any help is appreciated

Using ROW_NUMBER:
WITH cte AS (
SELECT t.*, ROW_NUMBER() OVER (PARTITION BY emp_code ORDER BY status, oper_day DESC) rn
FROM yourTable t
)
SELECT id, emp_code, name, status, oper_day
FROM cte
WHERE rn = 1;
The logic here is that should an employee have a status A record, it would be assigned the first row number, since A sorts before P. Otherwise, a P status record would be chosen. We choose the more recent record per employee in case of multiple records.

You can use aggregation functions with KEEP( DENSE_RANK FIRST ORDER BY ... ):
SELECT MAX( id ) KEEP ( DENSE_RANK FIRST ORDER BY status ASC, oper_day DESC ) AS id,
emp_code,
MAX( name ),
MIN( status ) AS status,
MAX( oper_day ) KEEP ( DENSE_RANK FIRST ORDER BY status ) AS oper_day
FROM table_name
GROUP BY
emp_code
Which., for your sample data:
CREATE TABLE table_name ( id, emp_code, name, status, oper_day ) AS
SELECT 1, 164094, 'John', 'P', DATE '2020-10-02' FROM DUAL UNION ALL
SELECT 2, 164094, 'John', 'P', DATE '2020-10-09' FROM DUAL UNION ALL
SELECT 3, 164094, 'John', 'A', DATE '2020-10-10' FROM DUAL UNION ALL
SELECT 4, 145890, 'Mike', 'P', DATE '2020-10-05' FROM DUAL;
Outputs:
ID | EMP_CODE | MAX(NAME) | STATUS | OPER_DAY
-: | -------: | :-------- | :----- | :------------------
4 | 145890 | Mike | P | 2020-10-05 00:00:00
3 | 164094 | John | A | 2020-10-10 00:00:00
db<>fiddle here

Related

Filling in missing balance and dates in table to track balance

I hope you can help me with this problem. I just started out on SQL using Bigquery so my problem can seem a bit tedious.
So I have a table that basically records the date and balance whenever the balance changes. It looks somewhat like this:
+------------+-----------+------+---------+
| Date | seller_ID | Name | Balance |
+------------+-----------+------+---------+
| 2020-09-10 | 1 | John | 10 |
| 2020-09-13 | 1 | John | 8 |
| 2020-09-15 | 1 | John | 6 |
+------------+-----------+------+---------+
However, I need to create a new table with the daily balances that looks like this
+------------+-----------+------+---------+
| Date | seller_ID | Name | Balance |
+------------+-----------+------+---------+
| 2020-09-10 | 1 | John | 10 |
| 2020-09-11 | 1 | John | 10 |
| 2020-09-12 | 1 | John | 10 |
| 2020-09-13 | 1 | John | 8 |
| 2020-09-14 | 1 | John | 8 |
| 2020-09-15 | 1 | John | 6 |
+------------+-----------+------+---------+
I tried creating a separate table of all the dates between the first and final date, and then LEFT JOIN the original table with it but the resulting table isn't very helpful to draw from.
Does anyone have an idea of what to do in this case?
To fill null value with previous non-null value in BigQuery you can use LAST_VALUE with IGNORE NULLS:
WITH test_table AS (
SELECT DATE '2020-09-10' AS Date, 1 AS seller_Id, 'John' AS Name, 10 AS Balance UNION ALL
SELECT '2020-09-13', 1, 'John' AS Name, 8 UNION ALL
SELECT '2020-09-15', 1, 'John' AS Name, 6
)
SELECT Date,
LAST_VALUE(seller_Id IGNORE NULLS) OVER (ORDER BY Date) AS seller_Id,
LAST_VALUE(Name IGNORE NULLS) OVER (ORDER BY Date) AS Name,
LAST_VALUE(Balance IGNORE NULLS) OVER (ORDER BY Date) AS purchase_date
FROM UNNEST(GENERATE_DATE_ARRAY('2020-09-10', '2020-09-15')) AS Date
LEFT JOIN test_table USING (Date)
ORDER BY Date
You can do this without window functions for the balance. The key is the window function only for the date:
WITH t AS (
SELECT DATE '2020-09-10' AS Date, 1 AS seller_Id, 'John' AS Name, 10 AS Balance UNION ALL
SELECT '2020-09-13', 1, 'John' AS Name, 8 UNION ALL
SELECT '2020-09-15', 1, 'John' AS Name, 6
),
tt as (
SELECT t.*, LEAD(date) OVER (PARTITION BY name ORDER BY date) as next_date
FROM t
)
SELECT dte, tt.name, tt.balance
FROM tt LEFT JOIN
UNNEST(GENERATE_DATE_ARRAY(tt.date, COALESCE(DATE_ADD(tt.next_date, INTERVAL - 1 DAY), DATE '2020-09-15'))) dte
ON true;
(Note: The ON clause is optional in this case. However, I am not a fan of having joins without ON -- unless it is a CROSS JOIN.)
This has two important advantages over Sergey's solution. The most important is that it will work for multiple names with different time periods.
The second advantage is that it is more efficient, because it is not using window functions to fetch values from previous rows.

SQL blank rows between rows

I am trying to output a blank row after each row.
For example:
SELECT id,job,amount FROM table
+----+-----+--------+
| id | job | amount |
+----+-----+--------+
| 1 | 100 | 123 |
| 2 | 200 | 321 |
| 3 | 300 | 421 |
+----+-----+--------+
To the following:
+----+-----+--------+
| id | job | amount |
+----+-----+--------+
| 1 | 100 | 123 |
| | | |
| 2 | 200 | 321 |
| | | |
| 3 | 300 | 421 |
+----+-----+--------+
I know I can do similar things with a UNION like:
SELECT null AS id, null AS job, null AS amount
UNION
SELECT id,job,amount FROM table
Which would give me a blank row at the beginning, but for the life of me I can't figure out how to do it every second row. A nested SELECT/UNION? - Have tried but nothing seemed to work.
The DBMS is SQL Server 2016
This is an akward requirement, that would most probably better handled on application side. Here is, however, one way to do it:
select id, job, amount
from (
select id, job, amount, id order_by from mytable
union all
select null, null, null, id from mytable
) t
order by order_by, id desc
The trick is to add an additional column to the unioned query, that keeps track of the original id, and can be used to sort the records in the outer query. You can then use id desc as second sorting criteria, which will put null values in second position.
Demo on DB Fiddle:
with mytable as (
select 1 id, 100 job, 123 amount
union all select 2, 200, 321
union all select 3, 300, 421
)
select id, job, amount
from (
select id, job, amount, id order_by from mytable
union all
select null, null, null, id from mytable
) t
order by order_by, id desc;
id | job | amount
---: | ---: | -----:
1 | 100 | 123
null | null | null
2 | 200 | 321
null | null | null
3 | 300 | 421
null | null | null
In SQL Server, you can just use apply:
select v.id, v.job, v.amount
from t cross apply
(values (id, job, amount, id, 1),
(null, null, null, id, 2)
) v(id, job, amount, ord1, ord2)
order by ord1, ord2;

How collpase all subgroups into one line and keep the same order

This is a simplified version of my table
+----+----------+------------+------------+
| ID | Category | Start Date | End Date |
+----+----------+------------+------------+
| 1 | 'Alpha' | 2018/04/12 | 2018/04/15 |
| 2 | null | 2018/04/17 | 2018/04/21 |
| 3 | 'Gamma' | 2018/05/02 | 2018/05/07 |
| 4 | 'Gamma' | 2018/05/09 | 2018/05/11 |
| 5 | 'Gamma' | 2018/05/11 | 2018/05/17 |
| 6 | 'Alpha' | 2018/05/17 | 2018/05/23 |
| 7 | 'Alpha' | 2018/05/23 | 2018/05/24 |
| 8 | null | 2018/05/24 | 2018/06/02 |
| 9 | 'Beta' | 2018/06/12 | 2018/06/16 |
| 10 | 'Beta' | 2018/06/16 | 2018/06/20 |
+----+----------+------------+------------+
All Start Date are unique, not nullable and they have the same order as the IDs (if a and b are IDs and a < b then StartDate[a] < StartDate[b]). The Start Date is not always equal to the End Date of the previous row for the same Category (look at id 3 and 4).
I'm looking for a query that will give me the following result
+----------+------------+------------+
| Category | Start Date | End Date |
+----------+------------+------------+
| 'Alpha' | 2018/04/12 | 2018/04/15 |
| null | 2018/04/17 | 2018/04/21 |
| 'Gamma' | 2018/05/02 | 2018/05/17 |
| 'Alpha' | 2018/05/17 | 2018/05/24 |
| null | 2018/05/24 | 2018/06/02 |
| 'Beta' | 2018/06/12 | 2018/06/20 |
+----------+------------+------------+
Note: The End Date will be equal to End Date of the last row in the subgroup (same continuous Category).
This is a gaps-and-islands problem. I think you can use the difference of row numbers:
select category, min(startdate), max(enddate)
from (select t.*,
row_number() over (order by id) as seqnum,
row_number() over (partition by category order by id) as seqnum_c
from t
) t
group by category, (seqnum - seqnum_c)
order by min(startdate);
This is a gaps and islands question, you can use such a logic below
select category, min(start_date) as start_date, max(end_date) as end_date
from
(
select tt.*, sum(grp) over (order by id, start_date) sm
from
(
with t( ID, Category, Start_Date, End_Date) as
(
select 1 , 'Alpha' , date'2018-04-12',date'2018-04-15' from dual union all
select 2 , null , date'2018-04-17',date'2018-04-21' from dual union all
select 3 , 'Gamma' , date'2018-05-02',date'2018-05-07' from dual union all
select 4 , 'Gamma' , date'2018-05-09',date'2018-05-11' from dual union all
select 5 , 'Gamma' , date'2018-05-11',date'2018-05-17' from dual union all
select 6 , 'Alpha' , date'2018-05-17',date'2018-05-23' from dual union all
select 7 , 'Alpha' , date'2018-05-23',date'2018-05-24' from dual union all
select 8 , null , date'2018-05-24',date'2018-06-02' from dual union all
select 9 , 'Beta' , date'2018-06-12',date'2018-06-16' from dual union all
select 10 , 'Beta' , date'2018-06-16',date'2018-06-20' from dual
)
select id, Category,
decode(nvl(lag(end_date) over
(order by end_date),start_date),start_date,0,1)
as grp, --> means prev. value equals or not
row_number() over (order by id, end_date) as rn, start_date, end_date
from t
) tt
order by rn
)
group by Category, sm
order by end_date;
CATEGORY START_DATE END_DATE
Alpha 12.04.2018 15.04.2018
NULL 17.04.2018 21.04.2018
Gamma 02.05.2018 07.05.2018
Gamma 09.05.2018 17.05.2018
Alpha 17.05.2018 24.05.2018
NULL 24.05.2018 02.06.2018
Beta 12.06.2018 20.06.2018

Joining a record from a single column date range

I have two tables:
Person
+---------+-----------+
| Name | Added |
+---------+-----------+
| Roger | 2/1/2001 |
| Natalie | 5/5/2001 |
| George | 6/6/2001 |
| Paul | 12/5/1999 |
+---------+-----------+
Stage
+-------------+----------+
| Description | Start |
+-------------+----------+
| 1 | 1/1/1980 |
| 2 | 4/1/2001 |
| 3 | 6/1/2001 |
+-------------+----------+
I want to join Person with stage such that I get the following result.
Result
+---------+-----------+--------+
| Name | Added | Stage |
+---------+-----------+--------+
| Roger | 2/1/2001 | 1 |
| Natalie | 5/5/2001 | 2 |
| George | 6/6/2001 | 3 |
| Paul | 12/5/1999 | 1 |
+---------+-----------+--------+
So, the stage 1 matches (added >= 1/1/1980 AND added < 4/1/2001), stage 2 matches (added >= 4/1/2001 AND added < 6/1/2001), stage 3 (added >= 6/1/2001) etc... This works, but I think it's kind of ugly (and only happens to work because the description is sequential as well).
SELECT person.name,
person.added,
(SELECT MAX(description) FROM stage d2 WHERE person.added >= d2.start) description
FROM person
Is there a way to do this in a regular join, and if description were a string rather than a sequential number? Thanks.
Instead of a subquery, you could use row_number():
select name, added, description
from (
select p.name, p.added, s.description
, row_number() over (
partition by p.name
order by s.start desc
) as rn
from person p
inner join stage s
on s.start <= p.added
) t
where rn = 1
test setup: http://rextester.com/SIAUAZ29747
with Person (Name,Added_date) as (
select 'Roger' , to_date('2001-02-01','yyyy-mm-dd') from dual union all
select 'Natalie' , to_date('2001-05-05','yyyy-mm-dd') from dual union all
select 'George' , to_date('2001-06-06','yyyy-mm-dd') from dual union all
select 'Paul' , to_date('1999-12-05','yyyy-mm-dd') from dual
),
Stage ( Description , Start_date ) as (
select 1, to_date('1980-01-01','yyyy-mm-dd') from dual union all
select 2, to_date('2001-04-01','yyyy-mm-dd') from dual union all
select 3, to_date('2001-06-01','yyyy-mm-dd') from dual
)
select name, to_char(added_date,'yyyy-mm-dd') added, description
from (
select p.name, p.added_date, s.description
, row_number() over (
partition by p.name
order by s.start_date desc
) as rn
from person p
inner join stage s
on s.start_date <= p.added_date
) t
where rn = 1
order by added_date
returns:
+---------+------------+-------------+
| NAME | ADDED | DESCRIPTION |
+---------+------------+-------------+
| Paul | 1999-12-05 | 1 |
| Roger | 2001-02-01 | 1 |
| Natalie | 2001-05-05 | 2 |
| George | 2001-06-06 | 3 |
+---------+------------+-------------+
Problems of this type can often be solved with no joins at all. Instead, combine the two tables (as illustrated below) with UNION ALL and use the LAST_VALUE() function:
select name, added, description
from (
select name, added,
last_value(description ignore nulls)
over (order by added, description) as description
from ( select name, null as description, added
from person
union all
select null, description, start_date
from stage
)
)
where name is not null
order by added, name -- if needed
;
NAME ADDED DESCRIPTION
------- ---------- -----------
Paul 12/05/1999 1
Roger 02/01/2001 1
Natalie 05/05/2001 2
George 06/06/2001 3
Big THANK YOU to #MT0 for providing the setup (CREATE TABLE statements).
Here is a version that joins the rows in Person to Stage with a 1:1 correspondence (unlike the accepted solution which will join Person to multiple rows in Stage and then have to filter out the unwanted rows):
Oracle Setup:
CREATE TABLE Person (Name,Added) AS
SELECT 'Roger' , DATE '2001-02-01' FROM DUAL UNION ALL
SELECT 'Natalie' , DATE '2001-05-05' FROM DUAL UNION ALL
SELECT 'George' , DATE '2001-06-06' FROM DUAL UNION ALL
SELECT 'Paul' , DATE '1999-12-05' FROM DUAL;
CREATE TABLE Stage ( Description , Start_date ) AS
SELECT 1, DATE '1980-01-01' FROM DUAL UNION ALL
SELECT 2, DATE '2001-04-01' FROM DUAL UNION ALL
SELECT 3, DATE '2001-06-01' FROM DUAL;
Query:
SELECT name, added, description
FROM person p
INNER JOIN
(
SELECT description,
start_date,
LEAD( start_date ) OVER ( ORDER BY start_date ) AS end_date
FROM stage
) s
ON ( s.start_date <= p.added AND ( s.end_date IS NULL OR p.added < s.end_date ) );
Output:
NAME ADDED DESCRIPTION
------- ------------------- -----------
Paul 1999-12-05 00:00:00 1
Roger 2001-02-01 00:00:00 1
Natalie 2001-05-05 00:00:00 2
George 2001-06-06 00:00:00 3

Querying latest date for a particluar attribute where it is not in date format

I need to set up a query that allows me to pick the most recent updated record within a group. If two records have the latest update, then the one with the longest update history should be picked. If both are null, or both have the same length of history, then neither should be chosen. The fields are varchar2 format. The last two digits in first record and last record correspond to the years those records were taken. The letters in the history length correspond to codes for what type of data was taken. Below is a sample table, with the expected results:
| group_id | id | First Record | Last Record | History Length |
---------------------------------------------------------------------------------
| a | 1 | record98 | record16 | SNDAWEDSPSEDSYSEAOE |
| a | 2 | record97 | record14 | AVNDAWEDSPSEDSYS |
| b | 3 | record96 | record15 | BVNDAWEDSPSEDSYSEAOE |
| b | 4 | record98 | record16 | UNDAWEDSPSEDSYSEAOP |
| b | 5 | record95 | record16 | UNDAWEDSPSEDSYSEAOPHYE|
| c | 6 | record96 | record12 | BVNDAWEDSPSEDSYSE |
| c | 7 | record10 | record15 | HUSIKD |
| d | 8 | null | null | null |
| d | 9 | null | null | null |
| e | 10 | record11 | record16 | ASIKSO |
| e | 11 | record11 | record16 | SIXLLO |
-------------------------------------------------------------------------------------------------------------------
Output
| group_id | id | First Record | Last Record | History Length |
---------------------------------------------------------------------------------
| a | 1 | record98 | record16 | SNDAWEDSPSEDSYSEAOE |
| b | 5 | record95 | record16 | UNDAWEDSPSEDSYSEAOPHYE|
| c | 7 | record10 | record15 | HUSIKD |
The history isn't as important as the latest record, so if that is too difficult to implement, I just need the one row with the latest record. Thank you.
Let me know if the below query works for your requirement.
SELECT group_id,ID,first_record,last_record,history_length
FROM (
SELECT group_id,ID,first_record,last_record,history_length,diff,
MAX(LENGTH(history_length)) OVER (PARTITION BY group_id) max_len,
count(1) OVER (PARTITION BY group_id,LENGTH(history_length)) cnt
FROM (
SELECT group_id,ID,first_record,last_record,history_length,
count(1) OVER (PARTITION BY group_id,LENGTH(history_length)) cnt,
MAX(to_date(to_number(substr(last_record, 7,2)),'RR')-to_date(to_number(substr(first_record, 7,2)),'RR')) OVER (PARTITION BY group_id) diff
FROM (
SELECT group_id,ID,first_record,last_record,history_length,
MAX(last_record) OVER (PARTITION BY group_id) max_last_record
FROM t
WHERE nvl(first_record,last_record) IS NOT NULL
)
WHERE last_record=max_last_record
)
WHERE (to_date(to_number(substr(last_record, 7,2)),'RR')-to_date(to_number(substr(first_record, 7,2)),'RR'))=diff
)
WHERE cnt=1
AND LENGTH(history_length)=max_len;
Personally I find hemalp108's answer hard to follow; I prefer to break each step down.
Below is how I did this using CTEs, where each subsequent CTE is the next step with a descriptive name i.e.
Add Max LastRecord
then Search by Max LastRecord
then Add HistoryTally
then Add Max HistoryTally
then Search by Max HistoryTally
then Add HistoryTally Frequency
then Search by HistoryTally Frequency
then return the result
P.S. SQLFiddle wasn't working so I had to so this in local SQL Server (don't have local Oracle) and tried to translate it back!
WITH YourTable AS
( SELECT *
FROM ( VALUES ( 'a',1,'record98','record16','SNDAWEDSPSEDSYSEAOE' ),
( 'a',2,'record97','record14','AVNDAWEDSPSEDSYS' ),
( 'b',3,'record96','record15','BVNDAWEDSPSEDSYSEAOE' ),
( 'b',4,'record98','record16','UNDAWEDSPSEDSYSEAOP' ),
( 'b',5,'record95','record16','UNDAWEDSPSEDSYSEAOPHYE' ),
( 'c',6,'record96','record12','BVNDAWEDSPSEDSYSE' ),
( 'c',7,'record10','record15','HUSIKD' ),
( 'd',8,null,null,null),
( 'd',9,null,null,null),
( 'e',10,'record11','record16','ASIKSO' ),
( 'e',11,'record11','record16','SIXLLO' )
) AS T ( group_id, id, FirstRecord, LastRecord, HistoryLength ) ),
AddMaxLastRecord AS
( SELECT *, MAX( LastRecord ) OVER ( PARTITION BY group_id ) MaxLastRecord
FROM YourTable ),
SearchByMaxLastRecord AS
( SELECT group_id, id, FirstRecord, LastRecord, HistoryLength
FROM AddMaxLastRecord
WHERE LastRecord = MaxLastRecord ),
AddHistoryTally AS
( SELECT *, LEN( HistoryLength ) AS HistoryTally
FROM SearchByMaxLastRecord ),
AddMaxHistoryTally AS
( SELECT *, MAX( HistoryTally ) OVER ( PARTITION BY group_id ) MaxHistoryTally
FROM AddHistoryTally ),
SearchByMaxHistoryTally AS
( SELECT group_id, id, FirstRecord, LastRecord, HistoryLength, HistoryTally
FROM AddMaxHistoryTally
WHERE HistoryTally = MaxHistoryTally ),
AddHistoryTallyFrequency AS
( SELECT *, COUNT( HistoryTally ) OVER ( PARTITION BY group_id ) AS HistoryTallyFreq
FROM SearchByMaxHistoryTally ),
SearchByHistoryTallyFrequency AS
( SELECT group_id, id, FirstRecord, LastRecord, HistoryLength
FROM AddHistoryTallyFrequency
WHERE HistoryTallyFreq = 1 )
SELECT *
FROM SearchByHistoryTallyFrequency;