Oracle SQL Query to get the RAG - sql

RAG PCT
------ ---
GREEN 100
AMBER 50
ORANGE 20
RED 0
I need an oracle query to result like (Suggest to use Inner Join or Outer Join)
if the given PCT >100 then Green
if the given PCT >=50 and PCT < 100 then AMBER
if the given PCT >=20 and PCT < 0 then ORANGE
else RED

You can implement the logic using CASE, like :
SELECT pct, CASE
WHEN pct >= 100 THEN 'GREEN'
WHEN pct >= 50 THEN 'AMBER'
WHEN pct >= 20 then 'ORANGE'
ELSE 'RED'
END
FROM mytable
CASE stops on the first matching condition (hence no need to write WHEN pct >= 50 AND pct < 100 for example, since pct >= 100 is already caught by the previous condition.
If you are using a separate table to store the lower bound of each interval (like
myranges), as shown in your example, and you are looking to JOIN it with a table that cotains actual data (like mydata), then it is a little more tricky : you would need to ensure that you are joining with the relevant range record :
SELECT d.*, r.*
FROM mydata d
INNER JOIN myranges r
ON d.value >= r.pct
AND (
LEAD (r.pct) OVER (ORDER BY pct) IS NULL
OR d.value < LEAD (r.pct) OVER (ORDER BY pct)
)

What you're asking for doesn't entirely make sense - you've said "Use a join" but have provided nothing to join to, but never mind. The following strictly implements your spec:
WITH cteData AS (SELECT 'GREEN' AS RAG, 100 AS PCT FROM DUAL UNION ALL
SELECT 'AMBER', 50 FROM DUAL UNION ALL
SELECT 'ORANGE', 20 FROM DUAL UNION ALL
SELECT 'RED', 0 FROM DUAL)
SELECT RAG, PCT, CASE
WHEN PCT > 100 THEN 'GREEN'
WHEN PCT >= 50 AND PCT < 100 THEN 'AMBER'
WHEN PCT >= 20 AND PCT < 0 THEN 'ORANGE'
ELSE 'RED'
END AS COLOR
FROM cteData;
When executed, the above produces:
RAG PCT COLOR
GREEN 100 RED
AMBER 50 AMBER
ORANGE 20 RED
RED 0 RED
And may Codd have mercy upon your soul.

If I understand you correctly, I think this might be something like what you're after:
WITH rag_data AS (SELECT 'GREEN' AS rag, 100 AS PCT FROM DUAL UNION ALL
SELECT 'AMBER' AS rag, 50 AS PCT FROM DUAL UNION ALL
SELECT 'ORANGE' AS rag, 20 AS PCT FROM DUAL UNION ALL
SELECT 'RED' AS rag, 0 AS PCT FROM DUAL),
sample_data AS (SELECT -1 NUM FROM dual UNION ALL
SELECT 0 NUM FROM dual UNION ALL
SELECT 1 NUM FROM dual UNION ALL
SELECT 19 NUM FROM dual UNION ALL
SELECT 20 NUM FROM dual UNION ALL
SELECT 21 NUM FROM dual UNION ALL
SELECT 49 NUM FROM dual UNION ALL
SELECT 50 NUM FROM dual UNION ALL
SELECT 51 NUM FROM dual UNION ALL
SELECT 99 NUM FROM dual UNION ALL
SELECT 100 NUM FROM dual UNION ALL
SELECT 101 NUM FROM dual)
SELECT NUM,
rag,
pct,
rn
FROM (SELECT sd.num,
rd.rag,
rd.pct,
row_number() OVER (PARTITION BY sd.num ORDER BY rd.pct DESC) rn
FROM sample_data sd
INNER JOIN rag_data rd ON sd.num >= rd.pct)
WHERE rn = 1;
NUM RAG PCT RN
---------- ------ ---------- ----------
0 RED 0 1
1 RED 0 1
19 RED 0 1
20 ORANGE 20 1
21 ORANGE 20 1
49 ORANGE 20 1
50 AMBER 50 1
51 AMBER 50 1
99 AMBER 50 1
100 GREEN 100 1
101 GREEN 100 1

Related

Check Distinct value Present in the group

I have a table with multiple pos and I need to find the Purchase Id where it has only wallet per group and nothing else in the group.
For eg,here PID - 4 and 5 has only wallet , rest has other's as well. So wallet_flag should be 1 in the output.
I tried to use window's function but could not achieve the result. Can you please suggest.
select PID
,POS
, SUM(CASE WHEN POS='bwallet' THEN 1 ELSE 0 END ) OVER(PARTITION BY PID) as FLAG
from PAYMENTS
where "status" = 'SUCCESS'
OUTPUT:
Here's one option:
Sample data:
SQL> with test (pid, pos, amount) as
2 (select 1, 'wallet', 10 from dual union all
3 select 1, 'BT' , 10 from dual union all
4 select 1, 'Cash' , 10 from dual union all
5 select 2, 'BT' , 50 from dual union all
6 select 3, 'Cash' , 24 from dual union all
7 select 3, 'BT' , 12 from dual union all
8 select 4, 'wallet', 100 from dual union all
9 select 5, 'wallet', 20 from dual union all
10 select 5, 'wallet', 100 from dual
11 ),
Query begins here; cnt will be 0 if there's only "wallet" per PID:
12 temp as
13 (select pid,
14 sum(case when pos = 'wallet' then 0 else 1 end) cnt
15 from test
16 group by pid
17 )
18 select a.pid, a.pos, a.amount,
19 case when b.cnt = 0 then 1 else 0 end wallet_flag
20 from test a join temp b on a.pid = b.pid
21 order by a.pid;
PID POS AMOUNT WALLET_FLAG
---------- ------ ---------- -----------
1 wallet 10 0
1 BT 10 0
1 Cash 10 0
2 BT 50 0
3 Cash 24 0
3 BT 12 0
4 wallet 100 1
5 wallet 20 1
5 wallet 100 1
9 rows selected.
SQL>
SELECT
your_table.*,
MIN(
CASE pos
WHEN 'wallet' THEN 1
ELSE 0
END
)
OVER (
PARTITION BY pid
)
AS wallet_flag
from
your_table
https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=e05a7863b9f4d912dcdf5ced5ec1c1b2

oracle grouping everytime the sum amount is below 15

I've following data:
SELECT 1 note, 1000 amt FROM dual union all
SELECT 2 note, 2000 amt FROM dual union all
SELECT 3 note, 8000 amt FROM dual union all
SELECT 4 note, 3000 amt FROM dual union all
SELECT 5 note, 1500 amt FROM dual union all
SELECT 6 note, 1600 amt FROM dual union all
SELECT 7 note, 20000 amt FROM dual union all
SELECT 8 note, 20000 amt FROM dual union all
SELECT 9 note, 2100 amt FROM dual union all
SELECT 10 note, 4500 amt FROM dual union all
SELECT 11 note, 1000 amt FROM dual union all
SELECT 12 note, 16000 amt FROM dual
and I need sum the amount, but for every sum <= 15000, they will be grouped together. If the amount is > 15000, they will be on their own group like this:
NOTE
AMT
group
1
1000
1
11
1000
1
5
1500
1
6
1600
1
2
2000
1
9
2100
1
4
3000
1
10
4500
2
3
8000
2
12
16000
3
7
20000
4
8
20000
5
I need the solution in oracle sql, is it possible? I'm using oracle 11g
One method is a recursive subquery:
with tt(note, amt, seqnum) as (
select t.note, t.amt, row_number() over (order by amt) as seqnum
from t
),
cte(note, amt, seqnum, grp, running_amt) as (
select note, amt, seqnum, 1, amt
from tt
where seqnum = 1
union all
select tt.note, tt.amt, tt.seqnum,
(case when tt.amt + cte.running_amt > 15000 then cte.grp + 1 else cte.grp end),
(case when tt.amt + cte.running_amt > 15000 then tt.amt else tt.amt + cte.running_amt end)
from cte join
tt
on tt.seqnum = cte.seqnum + 1
)
select *
from cte;
Here is a db<>fiddle.
Note: This is ordering by amt -- as in your sample data. You can as easily order by note (which also makes sense) just by adjusting seqnum in tt.
You can use the SQL for Pattern Matching:
WITH t AS (
SELECT 1 note, 1000 amt FROM DUAL UNION ALL
SELECT 2 note, 2000 amt FROM DUAL UNION ALL
SELECT 3 note, 8000 amt FROM DUAL UNION ALL
SELECT 4 note, 3000 amt FROM DUAL UNION ALL
SELECT 5 note, 1500 amt FROM DUAL UNION ALL
SELECT 6 note, 1600 amt FROM DUAL UNION ALL
SELECT 7 note, 20000 amt FROM DUAL UNION ALL
SELECT 8 note, 20000 amt FROM DUAL UNION ALL
SELECT 9 note, 2100 amt FROM DUAL UNION ALL
SELECT 10 note, 4500 amt FROM DUAL UNION ALL
SELECT 11 note, 1000 amt FROM DUAL UNION ALL
SELECT 12 note, 16000 amt FROM DUAL)
SELECT note, amt, amt_group, sum_amt
FROM t
MATCH_RECOGNIZE (
ORDER BY amt
MEASURES
MATCH_NUMBER() AS amt_group,
NVL(SUM(amt), amt) AS sum_amt
ALL ROWS PER MATCH
PATTERN (s*)
DEFINE
s AS SUM(amt) <= 15000
) mr
ORDER BY amt
NOTE
AMT
AMT_GROUP
SUM_AMT
1
1000
1
1000
11
1000
1
2000
5
1500
1
3500
6
1600
1
5100
2
2000
1
7100
9
2100
1
9200
4
3000
1
12200
10
4500
2
4500
3
8000
2
12500
12
16000
3
16000
7
20000
4
20000
8
20000
5
20000

Create table from loop output Oracle SQL

I need to pull a random sample from a table of ~5 million observations based on 175 demographic options. The demographic table is something like this form:
1 40 4%
2 30 3%
3 30 3%
- -
174 2 .02%
175 1 .01%
Basically I need this same demographic breakdown randomly sampled from the 5M row table. For each demographic I need a sample of the same one from the larger table but with 5x the number of observations (example: for demographic 1 I want a random sample of 200).
SELECT *
FROM (
SELECT *
FROM my_table
ORDER BY
dbms_random.value
)
WHERE rownum <= 100;
I've used this syntax before to get a random sample but is there any way I can modify this as a loop and substitute variable names from existing tables? I'll try to encapsulate the logic I need in pseudocode:
for (each demographic_COLUMN in TABLE1)
select random(5*num_obs_COLUMN in TABLE1) from ID_COLUMN in TABLE2
/*somehow join the results of each step in the loop into one giant column of IDs */
You could join your tables (assuming the 1-175 demographic value exists in both, or there is an equivalent column to join on), something like:
select id
from (
select d.demographic, d.percentage, t.id,
row_number() over (partition by d.demographic order by dbms_random.value) as rn
from demographics d
join my_table t on t.demographic = d.demographic
)
where rn <= 5 * percentage
Each row in the main table is given a random pseudo-row-number within its demographic (via the analytic row_number()). The outer query then uses the relevant percentage to select how many of those randomly-ordered rows for each demographic to return.
I'm not sure I've understood how you're actually picking exactly how many of each you want, so that probably needs to be adjusted.
Demo with a smaller sample in a CTE, and matching smaller match condition:
-- CTEs for sample data
with my_table (id, demographic) as (
select level, mod(level, 175) + 1 from dual connect by level <= 175000
),
demographics (demographic, percentage, str) as (
select 1, 40, '4%' from dual
union all select 2, 30, '3%' from dual
union all select 3, 30, '3%' from dual
-- ...
union all select 174, 2, '.02%' from dual
union all select 175, 1, '.01%' from dual
)
-- actual query
select demographic, percentage, id, rn
from (
select d.demographic, d.percentage, t.id,
row_number() over (partition by d.demographic order by dbms_random.value) as rn
from demographics d
join my_table t on t.demographic = d.demographic
)
where rn <= 5 * percentage;
DEMOGRAPHIC PERCENTAGE ID RN
----------- ---------- ---------- ----------
1 40 94150 1
1 40 36925 2
1 40 154000 3
1 40 82425 4
...
1 40 154350 199
1 40 126175 200
2 30 36051 1
2 30 1051 2
2 30 100451 3
2 30 18026 149
2 30 151726 150
3 30 125302 1
3 30 152252 2
3 30 114452 3
...
3 30 104652 149
3 30 70527 150
174 2 35698 1
174 2 67548 2
174 2 114798 3
...
174 2 70698 9
174 2 30973 10
175 1 139649 1
175 1 156974 2
175 1 145774 3
175 1 97124 4
175 1 40074 5
(you only need the ID, but I'm including the other columns for context); or more succinctly:
with my_table (id, demographic) as (
select level, mod(level, 175) + 1 from dual connect by level <= 175000
),
demographics (demographic, percentage, str) as (
select 1, 40, '4%' from dual
union all select 2, 30, '3%' from dual
union all select 3, 30, '3%' from dual
-- ...
union all select 174, 2, '.02%' from dual
union all select 175, 1, '.01%' from dual
)
select demographic, percentage, count(id) as ids, min(id) as min_id, max(id) as max_id
from (
select d.demographic, d.percentage, t.id,
row_number() over (partition by d.demographic order by dbms_random.value) as rn
from demographics d
join my_table t on t.demographic = d.demographic
)
where rn <= 5 * percentage
group by demographic, percentage
order by demographic;
DEMOGRAPHIC PERCENTAGE IDS MIN_ID MAX_ID
----------- ---------- ---------- ---------- ----------
1 40 200 175 174825
2 30 150 1 174126
3 30 150 2452 174477
174 2 10 23448 146648
175 1 5 19074 118649
db<>fiddle

SQL oracle group list number

Please help me: group list number
A new group starts when the values descend. You can find the groups where they start using lag(). Then do a cumulative sum:
select t.*,
1 + sum(case when prev_col2 < col2 then 0 else 1 end) over (order by col1) as grp
from (select t.*,
lag(col2) over (order by col1) as prev_col2
from t
) t;
In Oracle 12.1 and above, this is a simple application of the match_recognize clause:
with
inputs ( column1, column2 ) as (
select 1, 1000 from dual union all
select 2, 2000 from dual union all
select 3, 3000 from dual union all
select 4, 6000 from dual union all
select 5, 7500 from dual union all
select 6, 0 from dual union all
select 7, 500 from dual union all
select 8, 600 from dual union all
select 9, 900 from dual union all
select 10, 2300 from dual union all
select 11, 4700 from dual union all
select 12, 40 from dual union all
select 13, 1000 from dual union all
select 14, 2000 from dual union all
select 15, 4000 from dual
)
-- End of simulated inputs (not part of the solution).
-- SQL query begins BELOW THIS LINE. Use actual table and column names.
select column1, column2, column3
from inputs
match_recognize(
order by column1
measures match_number() as column3
all rows per match
pattern ( a b* )
define b as column2 >= prev(column2)
)
order by column1 -- If needed.
;
OUTPUT:
COLUMN1 COLUMN2 COLUMN3
---------- ---------- ----------
1 1000 1
2 2000 1
3 3000 1
4 6000 1
5 7500 1
6 0 2
7 500 2
8 600 2
9 900 2
10 2300 2
11 4700 2
12 40 3
13 1000 3
14 2000 3
15 4000 3
You can use window function to mark the point where column_2 restarts and use cumulative sum to get the desired result
Select column_1,
Column_2,
Sum(flag) over (order by column_1) as column_3
From (
Select t.*,
Case when column_2 < lag(column_2,1,0) over (order by column_1) then 1 else 0 end as flag
From your_table t
) t;

I have one source table whose data looks like

**COLOR** **TIMES**
ORANGE 1
RED 2
BLACK 3
YELLOW 4
But I need Data to be display in below format :-
**COLOR** **TIMES**
ORANGE 1
RED 1
RED 1
BLACK 1
BLACK 1
BLACK 1
YELLOW 1
YELLOW 1
YELLOW 1
YELLOW 1
Please suggest me the query in oracle SQL
A possible solution:
SQL> with t as (
2 select 'ORANGE' as color, 1 as times from dual
3 union all select 'RED' as color, 2 as times from dual
4 union all select 'BLACK' as color, 3 as times from dual
5 union all select 'YELLOW' as color, 4 as times from dual),
6 num as (
7 select rownum as n
8 from dual
9 connect by level <= 4)
10 select t.color,
11 1 as times
12 from t
13 join num on num.n <= t.times
14 order by t.times;
COLOR TIMES
------ ----------
ORANGE 1
RED 1
RED 1
BLACK 1
BLACK 1
BLACK 1
YELLOW 1
YELLOW 1
YELLOW 1
YELLOW 1
10 rows selected.
Maybe not very nice but it is working:
WITH t AS
(SELECT 'Orange' AS color, 1 AS times FROM dual UNION ALL
SELECT 'Red' AS color, 2 AS times FROM dual UNION ALL
SELECT 'Black' AS color, 3 AS times FROM dual UNION ALL
SELECT 'Yellow' AS color, 4 AS times FROM dual),
t2 AS
(SELECT DISTINCT color, 1 AS item, times, LEVEL AS C
FROM t
CONNECT BY LEVEL <= times)
SELECT color, item
FROM t2;