Oracle 12c - sql to find out of order rows - sql

I have a table with following columns:
FILE_NAME VARCHAR2(30);
STATUS VARCHAR2(2);
DEPT_ID NUMBER;
DEPT_SUB_ID NUMBER;
CREATE_DATE DATE;
sample data:
FILE_NAME STATUS DEPT_ID DEPT_SUB_ID CREATE_DATE
--------- ------- -------- ----------- ----------
TEST_20180806222127 C 1 10 07-AUG-18 01.04.47.821795000 AM
TEST_20180806221940 C 1 10 07-AUG-18 04.12.20.957400000 AM
TEST_20180806221733 C 1 10 07-AUG-18 03.35.27.809494000 AM
TEST_20180805202020 C 1 20 06-AUG-18 02.24.47.821795000 AM
TEST_20180805201640 C 1 20 06-AUG-18 00.42.20.957400000 AM
TEST_20180805201530 C 1 20 06-AUG-18 03.55.27.809494000 AM
FILE_NAME consists of: <TYPE>_<DATETIME>
I want to write a query for each DEPT_ID, DEPT_SUB_ID to determine which files with STATUS = 'C' were created out of order based on the <DATETIME> on FILE_NAME and CREATE_DATE field. In this example, for DEPT_SUB_ID = 10, file TEST_20180806222127 was created before the other 2 based on the DATE_TIME on the file name so I would need to return only this file name in result for DEPT_SUB_ID = 10. For DEPT_SUB_ID = 20, result should contain TEST_20180805201640 and TEST_20180805202020 since both were created before TEST_20180805201530, which is considered out of order.
Expected results from query will output all file_name's which were created before it's order of run.

You can assign two rankings to each row, one based on the order of the timestamp embedded int he file name, or other on the order of the creation date:
select yt.*,
row_number() over (partition by dept_id, dept_sub_id
order by to_date(substr(file_name, -14), 'YYYYMMDDHH24MISS')) as rn_file_name,
row_number() over (partition by dept_id, dept_sub_id
order by create_date) as rn_create_date
from your_table yt;
FILE_NAME S DEPT_ID DEPT_SUB_ID CREATE_DATE RN_FILE_NAME RN_CREATE_DATE
------------------- - ---------- ----------- ----------------------------- ------------ --------------
TEST_20180806221733 C 1 10 2018-08-07 03:35:27.809494000 1 2
TEST_20180806221940 C 1 10 2018-08-07 04:12:20.957400000 2 3
TEST_20180806222127 C 1 10 2018-08-07 01:04:47.821795000 3 1
TEST_20180805201530 C 1 20 2018-08-06 03:55:27.809494000 1 3
TEST_20180805201640 C 1 20 2018-08-06 00:42:20.957400000 2 1
TEST_20180805202020 C 1 20 2018-08-06 02:24:47.821795000 3 2
Then filter to see the mismatches:
select file_name, status, dept_id, dept_sub_id, create_date
from (
select yt.*,
row_number() over (partition by dept_id, dept_sub_id
order by to_date(substr(file_name, -14), 'YYYYMMDDHH24MISS')) as rn_file_name,
row_number() over (partition by dept_id, dept_sub_id
order by create_date) as rn_create_date
from your_table yt
)
where rn_file_name > rn_create_date;
FILE_NAME S DEPT_ID DEPT_SUB_ID CREATE_DATE
------------------- - ---------- ----------- -----------------------------
TEST_20180806222127 C 1 10 2018-08-07 01:04:47.821795000
TEST_20180805201640 C 1 20 2018-08-06 00:42:20.957400000
TEST_20180805202020 C 1 20 2018-08-06 02:24:47.821795000
And you can add a filter for a specific ID or sub-ID, either in the inner or outer query, if you don't want to see them all at once.

Related

Oralce sql:I want to select the TOP 3 Records [duplicate]

This question already has answers here:
How do I limit the number of rows returned by an Oracle query after ordering?
(14 answers)
Closed 8 months ago.
I want to select the TOP 3 Records ordered desc by 'cnt'
this is top 4
a b c cnt
99 YC 市購件異常 3
99 LY 漏油 2
99 QT16 其他異常 2
99 JGSH 機構損壞 1
then
select * from ()where rownum<= 3 order by cnt desc
get data
99 YC 市購件異常 3
99 LY 漏油 2
99 JGSH 機構損壞 1
i want to get
99 YC 市購件異常 3
99 LY 漏油 2
99 QT16 其他異常 2
Try this:
SELECT T.a, T.b, T.c, T.cnt
FROM
(
SELECT *, RANK() OVER(PARTITION BY a ORDER BY cnt DESC) RNK
FROM TEST_TBL
) T
WHERE T.RNK <= 3
It looks like you want to keep "duplicates" (in the cnt column) in the result.
In that case, I'd say that it is row_number analytic function that helps:
Sample data:
SQL> with test (a, b, cnt) as
2 (select 99, 'yc' , 3 from dual union all
3 select 99, 'ly' , 2 from dual union all
4 select 99, 'qt16', 2 from dual union all
5 select 99, 'jgsh', 1 from dual union all
6 --
7 select 99, 'abc' , 2 from dual --> yet another row with CNT = 2
8 ),
Query begins here: first rank rows (line #11), and then return the top 3 (line #15):
9 temp as
10 (select a, b, cnt,
11 row_number() over (partition by a order by cnt desc) rnk
12 from test
13 )
14 select * from temp
15 where rnk <= 3;
A B CNT RNK
---------- ---- ---------- ----------
99 yc 3 1
99 ly 2 2
99 abc 2 3
SQL>
Because, if you use rank analytic function (as Hana suggested), you might get more than desired 3 rows (see the rnk column's values) (depending on data you work with, of course; rank works with data you posted, but - if there are more rows that share the same cnt value, it won't work any more):
<snip>
9 temp as
10 (select a, b, cnt,
11 rank() over (partition by a order by cnt desc) rnk
12 from test
13 )
14 select * from temp
15 where rnk <= 3;
A B CNT RNK
---------- ---- ---------- ----------
99 yc 3 1
99 ly 2 2
99 abc 2 2
99 qt16 2 2
SQL>

How to evaluate rows and get the max value based on multiple columns

I have a SQL question where I want to evaluate and get the latest work order for a location based on multiple criteria
The table looks something like this
location work order create dt. status result
1. 123 3/1/22 complete positive
1. 124 3/2/22 incomplete. null
2. 231 2/1/22 cancelled. null
2. 232 2/3/22 incomplete. null
The requirement is as follows
For each location, find the latest work order based on the following criteria
If there are multiple work orders with results, pick the one with the latest date
If there are multiple work orders but one with result and one with no result, pick the one with the result - even if it is not latest
If there are multiple work orders, but none have result, pick the latest one that is not cancelled
If there are multiple work orders, but all are cancelled, pick the latest one
The result would be something like this
location work order
1. 123
2. 232
Since for location 1, we pick the earlier one, since it has the result
And for location 2, we pick the earlier one, since it is not cancelled.
Thanks
Here's the source data
SQL> select * from t;
LOCATION WORKORDER CREATED STATUS RESULT
---------- ---------- ---------- -------------------- --------------------
1 123 03/01/2022 complete positive
1 124 03/02/2022 incomplete
2 231 02/01/2022 cancelled
2 232 02/03/2022 incomplete
We can pick up some additional data on a per location basis
SQL> select
2 t.*,
3 max(created) over ( partition by location) as last_date,
4 count(result) over ( partition by location) result_count,
5 max(case when result is not null then created end) over ( partition by location) result_date,
6 max(case when status != 'cancelled' then created end) over ( partition by location) non_cancelled_date
7 from t
8 /
LOCATION WORKORDER CREATED STATUS RESULT LAST_DATE RESULT_COUNT RESULT_DAT NON_CANCEL
---------- ---------- ---------- -------------------- -------------------- ---------- ------------ ---------- ----------
1 123 03/01/2022 complete positive 03/02/2022 1 03/01/2022 03/02/2022
1 124 03/02/2022 incomplete 03/02/2022 1 03/01/2022 03/02/2022
2 231 02/01/2022 cancelled 02/03/2022 0 02/03/2022
2 232 02/03/2022 incomplete 02/03/2022 0 02/03/2022
and use that to apply our rules
SQL> select *
2 from
3 (
4 select
5 t.*,
6 max(created) over ( partition by location) as last_date,
7 count(result) over ( partition by location) result_count,
8 max(case when result is not null then created end) over ( partition by location) result_date,
9 max(case when status != 'cancelled' then created end) over ( partition by location) non_cancelled_date
10 from t
11 )
12 where ( result_count > 1 and created = result_date ) -- rule1
13 or ( result_count = 1 and created = result_date ) -- rule2
14 or ( result_count = 0 and non_cancelled_date = created ) -- rule3
15 or ( result_count = 0 and non_cancelled_date is null and created = last_date ) -- rule4
16 /
LOCATION WORKORDER CREATED STATUS RESULT LAST_DATE RESULT_COUNT RESULT_DAT NON_CANCEL
---------- ---------- ---------- -------------------- -------------------- ---------- ------------ ---------- ----------
1 123 03/01/2022 complete positive 03/02/2022 1 03/01/2022 03/02/2022
2 232 02/03/2022 incomplete 02/03/2022 0 02/03/2022
If you're unfamiliar with these "OVER" functions, here's my tutorial series on them https://www.youtube.com/watch?v=0cjxYMxa1e4&list=PLJMaoEWvHwFIUwMrF4HLnRksF0H8DHGtt
You can use the ROW_NUMBER analytic function:
SELECT *
FROM (
SELECT t.*,
ROW_NUMBER() OVER (
PARTITION BY location
ORDER BY
CASE
WHEN result IS NOT NULL THEN 0
WHEN status = 'cancelled' THEN 2
ELSE 1
END ASC,
create_dt DESC
) AS rn
FROM table_name t
)
WHERE rn = 1;
Which, for the sample data:
CREATE TABLE table_name (location, work_order, create_dt, status, result) AS
SELECT 1, 123, DATE '2022-01-03', 'complete', 'positive' FROM DUAL UNION ALL
SELECT 1, 124, DATE '2022-02-03', 'incomplete', null FROM DUAL UNION ALL
SELECT 2, 231, DATE '2022-01-02', 'cancelled', null FROM DUAL UNION ALL
SELECT 2, 232, DATE '2022-03-02', 'incomplete', null FROM DUAL;
Outputs:
LOCATION
WORK_ORDER
CREATE_DT
STATUS
RESULT
RN
1
123
03-JAN-22
complete
positive
1
2
232
02-MAR-22
incomplete
null
1
db<>fiddle here

DB2 Toad SQL - Group by Certain Columns using Max Command

I am having some trouble with the below query. I do understand I need to group by ID and Category, but I only want to group by ID while keeping the rest of the columns based on Rank being max. Is there a way to only group by certain columns?
select ID, Category, max(rank)
from schema.table1
group by ID
Input:
ID Category Rank
111 3 4
111 1 5
123 5 3
124 7 2
Current Output
ID Category Rank
111 3 4
111 9 1
123 5 3
124 7 2
Desired Output
ID Category Rank
111 1 5
123 5 3
124 7 2
You can use:
select *
from table1
where (id, rank) in (select id, max(rank) from table1 group by id)
Result:
ID CATEGORY RANK
---- --------- ----
111 1 5
123 5 3
124 7 2
Or you can use the ROW_NUMBER() window function. For example:
select *
from (
select *,
row_number() over(partition by id order by rank desc) as rn
from table1
) x
where rn = 1
See running example at db<>fiddle.
You can try using - row_number()
select * from
(
select ID, Category,rank, row_number() over(partition by id order by rank desc) as rn
from schema.table1
)A where rn=1

How to select ranges in a range of record in oracle

If I have a table like this
Number Status
------ ------
1 A
2 A
3 A
4 U
5 U
6 A
7 U
8 U
9 A
10 A
What query can I use to group the range into ranges where Status = A?
Range Count Status
----- ----- ------
1-3 3 A
6-6 1 A
9-10 2 A
My query is
select min(number) || '--' || max(number), count(*), Status
from table
where Status = 'A'
group by Status
Range Count Status
----- ----- ------
1-10 6 A
This is a nice way, fancy name "Tabibitosan method" given by Aketi Jyuuzou.
SQL> WITH data AS
2 (SELECT num - DENSE_RANK() OVER(PARTITION BY status ORDER BY num) grp,
3 status,
4 num
5 FROM t
6 )
7 SELECT MIN(num)
8 ||' - '
9 || MAX(num) range,
10 COUNT(*) cnt
11 FROM data
12 WHERE status='A'
13 GROUP BY grp
14 ORDER BY grp
15 /
RANGE CNT
------ ----------
1 - 3 3
6 - 6 1
9 - 10 2
SQL>
Note It is better to use DENSE_RANK to avoid duplicates.
Table
SQL> SELECT * FROM t ORDER BY num;
NUM S
---------- -
1 A
1 A
2 A
2 A
3 A
4 U
5 U
6 A
7 U
8 U
9 A
NUM S
---------- -
10 A
12 rows selected.
There are duplicates for num = 1.
Using DENSE_RANK:
SQL> WITH data AS
2 (SELECT num - DENSE_RANK() OVER(PARTITION BY status ORDER BY num) grp,
3 status,
4 num
5 FROM t
6 )
7 SELECT MIN(num)
8 ||' - '
9 || MAX(num) range,
10 COUNT(*) cnt
11 FROM data
12 WHERE status='A'
13 GROUP BY grp
14 ORDER BY grp
15 /
RANGE CNT
------ ----------
1 - 3 5
6 - 6 1
9 - 10 2
SQL>
Using ROW_NUMBER:
SQL> WITH DATA AS
2 (SELECT num - ROW_NUMBER() OVER(PARTITION BY status ORDER BY num) grp,
3 status,
4 num
5 FROM t
6 )
7 SELECT MIN(num)
8 ||' - '
9 || MAX(num) range,
10 COUNT(*) cnt
11 FROM data
12 WHERE status='A'
13 GROUP BY grp
14 ORDER BY grp
15 /
RANGE CNT
------ ----------
2 - 3 2
1 - 2 2
1 - 6 2
9 - 10 2
SQL>
So, in case of duplicates, the ROW_NUMBER query would give incorrect results. You should use DENSE_RANK.
SQL Fiddle
Oracle 11g R2 Schema Setup:
create table x(
num_ number,
status_ varchar2(1)
);
insert into x values(1,'A');
insert into x values(2,'A');
insert into x values(3,'A');
insert into x values(4,'U');
insert into x values(5,'U');
insert into x values(6,'A');
insert into x values(7,'U');
insert into x values(8,'U');
insert into x values(9,'A');
insert into x values(10,'A');
Query 1:
select min(num_) || '-' || max(num_) range_, status_,
count(1) count_
from
(
select num_, status_,
num_ - row_number() over (order by status_, num_) y --gives a group number to each groups, which have same status over consecutive records.
from x
)
where status_ = 'A'
group by y, status_
order by range_
Results:
| RANGE_ | STATUS_ | COUNT_ |
|--------|---------|--------|
| 1-3 | A | 3 |
| 6-6 | A | 1 |
| 9-10 | A | 2 |

PIVOT multiple rows into columns

I have table like below. Consider the query
select invoice_mth, inv_amt from table xdetails
where mobile_number=9080808080
data in the table
mobile_number invoice_mth inv_amt
9080808080 2010-10 20
9080808080 2010-11 30
9080808080 2010-12 40
I have to display the data from table like below.
I want invoice months to separate each month and amt separately.
MOBILE_NUMBER inv_m1 inv_m2 inv_m3 amt1 amt2 amt3
------- ----------------------------------------------------------
9080808080 2010-10 2010-11 2010-12 20 30 40
to display the data like above what I have to do?
You could play around with the standard PIVOT query:
SQL> SELECT * FROM t;
MOBILE_NUMBER INVOICE INV_AMT
------------- ------- ----------
9080808080 2010-10 20
9080808080 2010-11 30
9080808080 2010-12 40
SQL>
SQL> SELECT *
2 FROM
3 (SELECT mobile_number, invoice_mth, inv_amt FROM t
4 ) PIVOT (MIN(invoice_mth) AS inv_mth,
5 SUM(inv_amt) AS inv_amt
6 FOR (invoice_mth) IN ('2010-10' AS m1, '2010-11' AS m2, '2010-12' AS m3))
7 ORDER BY mobile_number;
MOBILE_NUMBER M1_INV_ M1_INV_AMT M2_INV_ M2_INV_AMT M3_INV_ M3_INV_AMT
------------- ------- ---------- ------- ---------- ------- ----------
9080808080 2010-10 20 2010-11 30 2010-12 40
SQL>
If you want a fixed number of columns in the output:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE TABLE_NAME ( mobile_number, invoice_mth, inv_amt ) AS
SELECT 9080808080, '2010-10', 20 FROM DUAL
UNION ALL SELECT 9080808080, '2010-11', 30 FROM DUAL
UNION ALL SELECT 9080808080, '2010-12', 40 FROM DUAL;
Query 1:
SELECT mobile_number,
MAX( CASE RN WHEN 1 THEN invoice_mth END ) AS inv_m1,
MAX( CASE RN WHEN 2 THEN invoice_mth END ) AS inv_m2,
MAX( CASE RN WHEN 3 THEN invoice_mth END ) AS inv_m3,
MAX( CASE RN WHEN 1 THEN inv_amt END ) AS amt1,
MAX( CASE RN WHEN 2 THEN inv_amt END ) AS amt2,
MAX( CASE RN WHEN 3 THEN inv_amt END ) AS amt3
FROM (
SELECT t.*,
ROW_NUMBER() OVER ( PARTITION BY mobile_number ORDER BY invoice_mth ASC ) AS rn
FROM TABLE_NAME t
)
GROUP BY mobile_number
Results:
| MOBILE_NUMBER | INV_M1 | INV_M2 | INV_M3 | AMT1 | AMT2 | AMT3 |
|---------------|---------|---------|---------|------|------|------|
| 9080808080 | 2010-10 | 2010-11 | 2010-12 | 20 | 30 | 40 |