Rank function with complex scenario - sql

I have a TAble TABLE1 and having columns like ID, Status and Code
BAsed on the code priority i want the output , the priority is
SS -> RR - > TT - > AA ( these priority is not stored in any tables)
Query should first look for Approved status then we need to check for Code column
Example1:
ID: 2345 - This record having Approved status for all the codes like SS , AA and RR
and based on the code priority SS should be pulled in the output as 2345, SS
Example2:
ID: 3333- This record having Approved status for all the codes like RR and TT
and based on the code priority RR should be pulled in the output as 3333, RR
ID: 4444- Eventhough this record is having Codes like SS and RR but it is status column is having value as TERMED so we need to populate the next priority in the list and output should display as 4444 TT
ID: 5555- None of the status for this ID is having Approved status all are having status as Termed so based on the priority in the output 5555,SS should be picked as this one is the priority
so output for 2345 and 5555 is same only the difference is if none of the record having approved status then only we should go for Termed - if the record is only having termed then based on priority record should be pulled
Attached the picture for reference

You may use RANK along with a CASE expression for ordering:
WITH cte AS (
SELECT t.*,
RANK() OVER (PARTITION BY ID
ORDER BY CASE Status WHEN 'Approved' THEN 1
WHEN 'Termed' THEN 2
ELSE 3 END,
CASE Code WHEN 'SS' THEN 1
WHEN 'RR' THEN 2
WHEN 'TT' THEN 3
WHEN 'AA' THEN 4 END) rnk
FROM yourTable t
WHERE Status = 'Approved'
)
SELECT ID, Code
FROM cte
WHERE rnk = 1;
Demo

create table table1 (id, status, code) as
select 2345, 'Approved', 'SS' from dual union all
select 2345, 'Approved', 'AA' from dual union all
select 2345, 'Approved', 'RR' from dual union all
select 3333, 'Approved', 'RR' from dual union all
select 3333, 'Approved', 'TT' from dual union all
select 4444, 'TERMED', 'SS' from dual union all
select 4444, 'TERMED', 'RR' from dual union all
select 4444, 'Approved', 'TT' from dual
;
select ID, CODE
from (
select ID, STATUS, CODE
, row_number()over(
partition by ID
order by status
, decode(code, 'SS', 1, 'RR', 2, 'TT', 3, 'AA', 4) ) rank
from table1
)
where rank = 1
;

Related

How do I select rows from table that have one or more than one specific value in a column?

I have a table containing data such as:
BP_NUMBER,CONTRACT_TYPE
0000123, 1
0000123, 2
0000123, 3
0000123, 4
0000124, 4
0000124, 4
0000124, 4
0000125, 4
0000126, 1
0000126, 5
I want to select rows containing one or more occurrences of CONTRACT_TYPE = 4. In other words, I want to know who are the clients with one or more contracts of the same type and type 4.
I tried this query:
SELECT * FROM (
SELECT BP_NUMBER, CONTRACT_TYPE, COUNT(*) OVER (PARTITION BY BP_NUMBER) CT FROM CONTRACTS
WHERE (1=1)
AND DATE = '18/10/2022'
AND CONTRACT_TYPE = 4)
WHERE CT= 1;
But it returns rows with only one occurrence of CONTRACT_TYPE = 4.
Also tried something like:
SELECT BP_NUMBER FROM CONTRACTS
WHERE (1=1)
AND CONTRACT_TYPE = 4
AND CONTRACT_TYPE NOT IN (SELECT CONTRACT_TYPE FROM CONTRACTS WHERE CONTRACT_TYPE != 4 GROUP BY CONTRACT_TYPE);
Trying to avoid any other contract types than 4. I really don't understand why it doesn't work.
The expected result would be:
0000124 --(4 occurrences of type 4)
0000125 --(1 occurrence of type 4)
Any help? Thanks
You can try something like this:
SELECT
BP_NUMBER
FROM CONTRACTS c1
WHERE CONTRACT_TYPE = 4
AND NOT EXISTS
(SELECT 1 FROM CONTRACTS c2 WHERE c2.BP_NUMBER = c1.BP_NUMBER
AND c2.CONTRACT_TYPE <> c1.CONTRACT_TYPE)
Depending on how you actually want to see it (and what other values you might want to include), you could either do a DISTINCT on the BP_NUMBER, or group on that column (and potentially others)
A similar result could also be achieved using an outer join between two instances of the CONTRACTS table. Essentially, you need the second instance of the same table so that you can exclude output rows when there are records with the "unwanted" contract types
You can just do the aggregation like here:
WITH
tbl AS
(
Select '0000123' "BP_NUMBER", '1' "CONTRACT_TYPE" From Dual Union All
Select '0000123', '2' From Dual Union All
Select '0000123', '3' From Dual Union All
Select '0000123', '4' From Dual Union All
Select '0000124', '4' From Dual Union All
Select '0000124', '4' From Dual Union All
Select '0000124', '4' From Dual Union All
Select '0000125', '4' From Dual Union All
Select '0000126', '1' From Dual Union All
Select '0000126', '5' From Dual
)
Select
BP_NUMBER "BP_NUMBER",
Count(*) "OCCURENCIES"
From
tbl
WHERE CONTRACT_TYPE = '4'
GROUP BY BP_NUMBER
ORDER BY BP_NUMBER
--
-- R e s u l t :
--
-- BP_NUMBER OCCURENCIES
-- --------- -----------
-- 0000123 1
-- 0000124 3
-- 0000125 1

Find a row that has passed different steps in the same table

Hi to all here it is my problem:
I have an history table where a record register different steps:
Id
Step
Item Code
1
Created
112345
2
Approved
112345
3
Completed
112345
4
Closed
112345
5
Created
112346
6
Approved
112346
8
Closed
112346
What i want to find inside this table is:
All the item codes that have done one step (for example the Approved one) and where the next one is not the "natural one" (for expample the Completed one). In the example table the time code 112346 item has done the Approved step but has skipped the Completed step).
Is there anyway to do a query like this? I've used the PARTITION BY to make a cluster of Item ,for each step, but i am unable to continue the query.
Thanks in advance for any help or suggestion
You can use the LEAD analytic function to check if the next step is not the expected one:
SELECT id, step, item_code
FROM (
SELECT t.*,
LEAD(step) OVER (PARTITION BY item_code ORDER BY id) AS next_step
FROM table_name t
)
WHERE step = 'Approved'
AND (next_step IS NULL OR next_step != 'Completed')
Or, from Oracle 12, you can use MATCH_RECOGNIZE to perform row-by-row processing:
SELECT id, step, item_code
FROM table_name
MATCH_RECOGNIZE(
PARTITION BY item_code
ORDER BY id
ALL ROWS PER MATCH
PATTERN ( approved {- (not_completed|$) -} )
DEFINE
approved AS step = 'Approved',
not_completed AS step <> 'Completed'
)
Which, for the sample data:
CREATE TABLE table_name (Id, Step, Item_Code) AS
SELECT 1, 'Created', 112345 FROM DUAL UNION ALL
SELECT 2, 'Approved', 112345 FROM DUAL UNION ALL
SELECT 3, 'Completed', 112345 FROM DUAL UNION ALL
SELECT 4, 'Closed', 112345 FROM DUAL UNION ALL
SELECT 5, 'Created', 112346 FROM DUAL UNION ALL
SELECT 6, 'Approved', 112346 FROM DUAL UNION ALL
SELECT 8, 'Closed', 112346 FROM DUAL;
Both output:
ID
STEP
ITEM_CODE
6
Approved
112346
db<>fiddle here
Provided you have a table of step names with a proper ordering int column (seqno)
with
/* Sample data */
steps(seqno, Step) as (
select 1, 'Created' from dual union all
select 2, 'Approved' from dual union all
select 3, 'Completed' from dual union all
select 4, 'Closed' from dual
),
tbl(Id, Step, ItemCode) as (
select 1, 'Created' , 112345 from dual union all
select 2, 'Approved' , 112345 from dual union all
select 3, 'Completed', 112345 from dual union all
select 4, 'Closed' , 112345 from dual union all
select 5, 'Created' , 112346 from dual union all
select 6, 'Approved' , 112346 from dual union all
select 8, 'Closed' , 112346 from dual
),
/* Find steps with the next step being out of order */
m as (
select max(seqno)+1 term
from steps
)
select Id, Step, ItemCode
from (
SELECT t.*, s.seqno, lead(s.seqno, 1, (select term from m)) over(partition by ItemCode order by Id) nextSeqno
FROM tbl t
JOIN steps s on s.Step = t.Step
) q
cross join m
where nextseqno != Seqno + 1 and nextseqno < m.term

Excluding a row that contains a specific value

I want to exclude people who have joined a specific group. For example, if some students signed up for an Orchestra club, and I want to retrieve a list of students who did NOT sign up for orchestra, how do I do so?
I am unable to simply do a Group By clause because some students may have joined multiple clubs, and would bypass the Where condition and still show up in the query,
as shown here.
I am thinking about using a CASE statement in the SELECT clause to flag the person as '1' if they have joined Orchestra, and '0' if they have not, but I'm struggling to write an aggregate CASE function, which would cause issues from the GROUP BY clause.
Any thoughts on how to flag people with a certain row value?
Apparently my table didn't get saved onto SQLFiddle so you can paste the code below on your own screen:
CREATE TABLE activity ( PersonId, Club) as
select 1, 'Soccer' from dual union
select 1, 'Orchestra' from dual union
select 2, 'Soccer' from dual union
select 2, 'Chess' from dual union
select 2, 'Bball' from dual union
select 3, 'Orchestra' from dual union
select 3, 'Chess' from dual union
select 3, 'Bball' from dual union
select 4, 'Soccer' from dual union
select 4, 'Bball' from dual union
select 4, 'Chess' from dual;
Use the HAVING clause instead of using WHERE, with case expression :
HAVING max(case when column = ‘string’ then 1 else 0 end) = 0
Add this after your group by .
How about selecting a list of user ids from the activity table and excluding it:
SELECT * FROM users WHERE id NOT IN
(SELECT PersonId FROM activity WHERE Club = 'Orchestra');
You could use a subquery to return a list of people to exclude.
-- Returns person 2 and 4.
SELECT
PersonId
FROM
activity
WHERE
PersonId NOT IN
(
-- People to exclude.
SELECT
PersonId
FROM
activity
WHERE
Club = 'Orchestra'
)
GROUP BY
PersonId
;
EDIT Removed superfluous distinct in subquery - thanks #mathguy.
select * from
(
select a.*, case when Club ='Orchestra' then 1 else 0 end flag
from activity a
) where flag =1; --> get some students signed up for an Orchestra club
select * from
(
select a.*, case when Club ='Orchestra' then 1 else 0 end flag
from activity a
) where flag =0; --> get students not signed up for an Orchestra club

Select one column value for other columns based on a condition

I have a table like given below structure, I need to write a query to fetch result like achived month comnet retaine for all remaining months from the table based on a condition if the actual value is greater than or equal to target then select that particular month comments value for remaining months too.My table structure is
I am Expecting the result like the below structure.
Here in June actual value is 100 and comment 'Closed' after thet user will not enter anything(Actual or comments) since actual meets target. so i need to display the commment 'Closed' in all remaining months(July-Dec)
Your expected output is not clear, Please add clarity.
How about this? -
SELECT *
FROM YOUR_TABLE
WHERE MONTHS_ID IN (SELECT MONTHS_ID FROM YOUR_TABLE WHERE ACTUAL_VALUE >= TARGET)
Do you want to aggregate comments by Months_ID?
SELECT MONTHS_ID,
LISTAGG(COMMENTS, ',') WITHIN GROUP(ORDER BY COMMENTS) AS COMMENTS
FROM YOUR_TABLE
WHERE MONTHS_ID IN (SELECT MONTHS_ID FROM YOUR_TABLE WHERE ACTUAL_VALUE >= TARGET)
GROUP BY MONTHS_ID
you can use a where based on you filter condition
select a.comment
from your_table_with_commen a
where a.comment is not null
and a.target is not null
and a.target <= a.actual
Your expected output isn't very clear. But if I got it correctly, then you can achieve the desired result using below query(may not be best performing):
with commentValue as (
select month_id, comments from your_table where actual_value = ( select max(target)
from your_table)
)
select yt.target,yt.month_id,
case when yt.month_id >= cv.month_id then cv.comments else yt.comments end as
comments,
yt.actual_value
from your_table yt
join commentValue cv on 1 = 1
From the explanation, seems you need nothing more than this :
Select month, nvl(comment,'Closed') as comment, target, actual
From tableDemands;
I need to write a query to fetch result like achived month comnet retaine for all remaining months from the table based on a condition if the actual value is greater than or equal to target then select that particular month comments value for remaining months too.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_name ( month, "COMMENT", target, actual ) AS
SELECT 1, 'initiated', NULL, 5 FROM DUAL UNION ALL
SELECT 2, 'feb', NULL, 6 FROM DUAL UNION ALL
SELECT 3, 'Mar- On going', NULL, 10 FROM DUAL UNION ALL
SELECT 4, 'Apr- work On going', NULL, 20 FROM DUAL UNION ALL
SELECT 5, 'May- Ongoing', NULL, 50 FROM DUAL UNION ALL
SELECT 6, 'closed', NULL, 100 FROM DUAL UNION ALL
SELECT 7, NULL, NULL, NULL FROM DUAL UNION ALL
SELECT 8, NULL, NULL, NULL FROM DUAL UNION ALL
SELECT 9, NULL, 100, NULL FROM DUAL UNION ALL
SELECT 10, NULL, NULL, NULL FROM DUAL UNION ALL
SELECT 11, NULL, NULL, NULL FROM DUAL UNION ALL
SELECT 12, NULL, NULL, NULL FROM DUAL;
Query 1:
SELECT month,
"COMMENT",
max_target As target,
actual
FROM (
SELECT t.*,
MAX( target ) OVER () AS max_target
FROM table_name t
)
WHERE actual >= max_target
Results:
| MONTH | COMMENT | TARGET | ACTUAL |
|-------|---------|--------|--------|
| 6 | closed | 100 | 100 |
It seems you want to replace a null comment with the last non-null comment. Use LAST_VALUE for this:
select
month,
last_value(comment ignore nulls) over (order by month) as comment,
target,
actual
from mytable
order by month;

Find duplicate records in database against unique attributes

I want to find duplicate of IRN # entered into a table in database. Here are the unique attributes (logically unique) of the IRN.
ProjectNo, DrawingNo, DrawingRev, SpoolNo, WeldNo
An IRN can have multiple WeldNos meaning the above unique attributes may repeat for one IRN # (with of course one of the 5 attribute values must be unique).
Now I am trying to find out whether there are any duplicate IRNs entered into the system or not? How can I find that through a sql query?
P.S: Due to bad design of database, there is no primary key in the table..
Here is what I have tried so far but this does not give the correct results.
select * from WeldInfo a, WeldInfo b
where a.ProjectNo = b.ProjectNo and
a.DrawingNo = b.DrawingNo and
a.DrawingRev = b.DrawingRev and
a.SpoolNo = b.SpoolNo and
a.WeldNo = b.WeldNo and
a.IrnNo <> b.IrnNo;
But i'm not sure, have i understood your question.
select * from (
select count(*) over ( partition by ProjectNo, DrawingNo, DrawingRev, SpoolNo, WeldN) rr,t.* from WeldInfo t)
where rr > 1;
Explanation.
with tab as (
select 1 as id, 'a' as a , 'b' as b , 'c' as c from dual
union all
select 2 , 'a', 'b', 'c' from dual
union all
select 3 , 'x', 'b', 'c' from dual
union all
select 3 , 'x', 'b', 'c' from dual
union all
select 3 , 'x', 'd', 'c' from dual
)
select t.*
, count(*) over (partition by a,b,c) cnt1
, count(distinct id) over (partition by a,b,c) cnt2
from tab t;