Oracle SQL Select Query Getting Max Row As a Fraction of a Rollup Total - oracle

hoping I might be able to get some advise regarding Oracle SQL…
I have a table roughly as follows (there are more columns, but not necessary for this example)…
LOCATION USER VALUE
1 1 10
1 2 20
1 3 30
2 4 10
2 5 10
2 6 20
1 60
2 40
100
I’ve used rollup to get subtotals.
What I need to do is get the max(value) row for each location and express the max(value) as a percentage or fraction of the subtotal for each location
ie:
LOCATION USER FRAC
1 3 0.5
2 6 0.5
I could probably solve this using my limited knowledge of select queries, but am guessing there must be a fairly quick and slick method..
Thanks in advance :)

Solution using analytic functions
(Please note the WITH MY_TABLE AS serving only as dummy datasource)
WITH MY_TABLE AS
( SELECT 1 AS LOC_ID,1 AS USER_ID, 10 AS VAL FROM DUAL
UNION
SELECT 1,2,20 FROM DUAL
UNION
SELECT 1,3,30 FROM DUAL
UNION
SELECT 2,4,10 FROM DUAL
UNION
SELECT 2,5,10 FROM DUAL
UNION
SELECT 2,6,20 FROM DUAL
)
SELECT LOC_ID,
USER_ID,
RATIO_IN_LOC
FROM
(SELECT LOC_ID,
USER_ID,
RATIO_IN_LOC,
RANK() OVER (PARTITION BY LOC_ID ORDER BY RATIO_IN_LOC DESC) AS ORDER_IN_LOC
FROM
(SELECT LOC_ID,
USER_ID,
VAL,
VAL/SUM(VAL) OVER (PARTITION BY LOC_ID) AS RATIO_IN_LOC
FROM MY_TABLE
)
)
WHERE ORDER_IN_LOC = 1
ORDER BY LOC_ID,
USER_ID;
Result
LOC_ID USER_ID RATIO_IN_LOC
1 3 0,5
2 6 0,5

with inputs ( location, person, value ) as (
select 1, 1, 10 from dual union all
select 1, 2, 20 from dual union all
select 1, 3, 30 from dual union all
select 2, 4, 10 from dual union all
select 2, 5, 10 from dual union all
select 2, 6, 20 from dual
),
prep ( location, person, value, m_value, total ) as (
select location, person, value,
max(value) over (partition by location),
sum(value) over (partition by location)
from inputs
)
select location, person, round(value/total, 2) as frac
from prep
where value = m_value;
Notes: Your table exists already? Then skip everything from "inputs" to the comma; your query should begin with with prep (...) as ( ...
I changed user to person since user is a keyword in Oracle, you shouldn't use it for table or column names (actually you can't unless you use double quotes, which is a very poor practice).
The query will output two or three or more rows per location if there are ties at the top. Presumably this is what you desire.
Output:
LOCATION PERSON FRAC
---------- ---------- ----------
1 3 .5
2 6 .5

Related

Multiply with Previous Value from One colum in Oracle SQL

I have the following result, which is easily calculated in Excel, but how to do it in Oracle, the result is the following, based on a previous select and comes from one column,
Result from select Expected result
1.62590
0.60989 0.991620151
0.83859 0.831562742
the result is based on 1.62590 * 0.60989 = 0.991620151,
1.62590 * 0.60989 * 0.83859 = 0.831562742
You can use:
SELECT id,
result,
EXP(SUM(LN(result)) OVER (ORDER BY id)) AS expected
FROM table_name;
Note: Use any other column instead of id to give the appropriate ordering or, if your rows are already ordered, use the ROWNUM pseudo-column instad of id.
Which, for the sample data:
CREATE TABLE table_name (id, Result) AS
SELECT 1, 1.62590 FROM DUAL UNION ALL
SELECT 2, 0.60989 FROM DUAL UNION ALL
SELECT 3, 0.83859 FROM DUAL;
Outputs:
ID
RESULT
EXPECTED
1
1.6259
1.62590000000000000000000000000000000001
2
.60989
.9916201510000000000000000000000000000026
3
.83859
.8315627424270900000000000000000000000085
fiddle
One option is to use a recursive CTE; it, though, expects that sample data can be sorted, somehow, so I added the ID column which starts with 1, while other values are incremented by 1:
Sample data:
SQL> with
2 test (id, col) as
3 (select 1, 1.62590 from dual union all
4 select 2, 0.60989 from dual union all
5 select 3, 0.83859 from dual
6 ),
Query begins here:
7 product (id, col, prod) as
8 (select id, col, col
9 from test
10 where id = 1
11 union all
12 select t.id, t.col, t.col * p.prod
13 from test t join product p on p.id + 1 = t.id
14 )
15 select id,
16 round(prod, 10) result
17 from product;
ID RESULT
---------- ----------
1 1,6259
2 ,991620151
3 ,831562742
SQL>
You can use a MODEL clause:
SELECT *
FROM (SELECT ROW_NUMBER() OVER (ORDER BY id) AS rn, result FROM table_name)
MODEL
DIMENSION BY (rn)
MEASURES ( result, 0 AS expected)
RULES (
expected[rn] = result[cv()] * COALESCE(expected[cv()-1], 1)
)
order by rn;
Which, for the sample data:
CREATE TABLE table_name (id, Result) AS
SELECT 1, 1.62590 FROM DUAL UNION ALL
SELECT 2, 0.60989 FROM DUAL UNION ALL
SELECT 3, 0.83859 FROM DUAL;
Outputs:
RN
RESULT
EXPECTED
1
1.6259
1.6259
2
.60989
.991620151
3
.83859
.83156274242709
fiddle

Calculate current based on previous formula

I have a scenario where the current column value is calculated based on the previous value calculated by the formula
The initial row of the group has no previous value so it will not consider.
Formula for loss= relase-withdraw-least(previous_row_loss,reverse)
Here below loss is the column I need to calculate.
I tried with the following query but not getting expected output. Can you please guide me here.
SELECT
pid,release,withdraw,reverse,
SUM(release - withdraw - LEAST( LAG(loss,1,0) OVER (ORDER BY pid)),reverse)) as loss
FROM transactions
You can use a MODEL clause:
SELECT *
FROM (
SELECT t.*,
ROW_NUMBER() OVER (ORDER BY pid, fiscalperiod) AS rn
FROM table_name t
)
MODEL
DIMENSION BY (rn)
MEASURES (pid, fiscalperiod, release, withdraw, reverse, 0 AS loss)
RULES (
loss[1] = release[1] - withdraw[1] - reverse[1],
loss[rn>1] = release[cv()] - withdraw[cv()] - LEAST(reverse[cv()], loss[cv()-1])
+ loss[cv()-1]
);
Or, probably, much less efficiently a recursive query:
WITH numbered_rows AS (
SELECT t.*,
ROW_NUMBER() OVER (ORDER BY pid, fiscalperiod) AS rn
FROM table_name t
),
recursive_query (rn, pid, fiscalperiod, release, withdraw, reverse, loss) AS (
SELECT rn,
pid,
fiscalperiod,
release,
withdraw,
reverse,
release - withdraw - reverse
FROM numbered_rows
WHERE rn = 1
UNION ALL
SELECT n.rn,
n.pid,
n.fiscalperiod,
n.release,
n.withdraw,
n.reverse,
n.release - n.withdraw + GREATEST(r.loss - n.reverse, 0)
FROM numbered_rows n
INNER JOIN recursive_query r
ON (n.rn = r.rn + 1)
)
SELECT *
FROM recursive_query;
Which, for your sample data:
CREATE TABLE table_name (pid, fiscalperiod, release, withdraw, reverse) AS
SELECT 'A1', 2022001, 10, 10, 10 FROM DUAL UNION ALL
SELECT 'A1', 2022002, 20, 13, 2 FROM DUAL UNION ALL
SELECT 'A1', 2022003, 20, 20, 10 FROM DUAL UNION ALL
SELECT 'A2', 2022002, 15, 10, 13 FROM DUAL;
Both output:
RN
PID
FISCALPERIOD
RELEASE
WITHDRAW
REVERSE
LOSS
1
A1
2022001
10
10
10
-10
2
A1
2022002
20
13
2
7
3
A1
2022003
20
20
10
0
4
A2
2022002
15
10
13
5
db<>fiddle here

How to get records based on most occurring value?

I have following table and I want to get records where promotion is mostly occurring.
For example if I got two events
FREQ_VISITOR, value= 250
HIGH_SHOPPER, value= 320
Then Promo 1 and Promo 2 should come in result. Since these 2 promos exists mostly for every trigger and their given values.
Here's one option, based on what I understood:
SQL> with test (event_name, value, promotion) as
2 (select 'freq_visitor', 250, 'promo1' from dual union all
3 select 'high_shopper', 320, 'promo2' from dual union all
4 select 'freq_visitor', 250, 'promo3' from dual union all
5 select 'high_shopper', 320, 'promo1' from dual union all
6 select 'freq_visitor', 250, 'promo2' from dual
7 ),
8 cnt_promo as
9 (select promotion, count(*) cnt
10 from test
11 group by promotion
12 ),
13 most_promos as
14 (select max(cnt) max_cnt
15 from cnt_promo
16 )
17 select c.promotion
18 from cnt_promo c join most_promos m on c.cnt = m.max_cnt;
PROMOT
------
promo1
promo2
SQL>
This is a good candidate for analytic functions.
The below code is a bit longer than the self-join approach, and flows from the inside-out instead of a more traditional top-to-bottom direction. But this approach will likely be faster, since it only reads from the table once. And this approach is easier to debug than common table expressions, since you can highlight and run different inline views and watch the result set be built.
--Promotions with the highest counts.
select promotion
from
(
--RANK the promotion counts.
select promotion, promotion_count,
rank() over (order by promotion_count desc) promotion_rank
from
(
--Count of promos per event and value.
select promotion, count(*) promotion_count
from
(
--Test data
select 'freq_visitor' event_name, 250 value, 'promo1' promotion from dual union all
select 'high_shopper' event_name, 320 value, 'promo2' promotion from dual union all
select 'freq_visitor' event_name, 250 value, 'promo3' promotion from dual union all
select 'high_shopper' event_name, 320 value, 'promo1' promotion from dual union all
select 'freq_visitor' event_name, 250 value, 'promo2' promotion from dual
) test_data
group by promotion
) add_promo_count
) add_promo_rank
where promotion_rank = 1
order by promotion;

Oracle 11g - How to calculate the value of a number in range minimum or max

i need a help to get solution to my problem, Please.
I have a table like this :
ID Number
|6 |20.90 |
|7 |45.00 |
|8 |52.00 |
|9 |68.00 |
|10 |120.00 |
|11 |220.00 |
|12 |250.00 |
The first range is 0 - 20.90.
When the value is in the half, the value id is for the max range.
When i got value 20.91, i want to get "ID = 6".
If the value is 31.00, i want to get "ID = 6"
If the value is
33.95, i want to get "ID = 7".
if the value is 44.99, i want to get ID = 7
How i can do it? Is there a function that will do what I need?
If you want the record with a number that is closest to your input, then you can use this:
select *
from (
select *
from mytable
order by abs(number - my_input_number), id
)
where rownum < 2
The inner query selects all records, but orders them by the distance they have from your input number. This distance can be calculated with number - my_input_number. But that could be negative, so we take the absolute value of that. This result is not output; it is just used to order by. So records with smaller distances will come first.
Now we need just the first of those records, and that is what the outer query does with the typical Oracle reserved word rownum: it represents a sequence number for every record of the final result set (1, 2, 3, ...). The where clause will effectively filter away all records we do not want to see, leaving only one (with smallest distance).
As mathguy suggested in comments, the order by now also has a second value to order by in case the input value is right at the mid point between the two closest records. In that case the record with the lowest id value will be chosen.
This is a good illustration of the power of analytic functions:
with mytable ( id, value ) as (
select 6, 20.90 from dual union all
select 7, 45.00 from dual union all
select 8, 52.00 from dual union all
select 9, 68.00 from dual union all
select 10, 120.00 from dual union all
select 11, 220.00 from dual union all
select 12, 250.00 from dual
),
inputs ( x ) as (
select 0.00 from dual union all
select 20.91 from dual union all
select 31.00 from dual union all
select 33.95 from dual union all
select 44.99 from dual union all
select 68.00 from dual union all
select 32.95 from dual union all
select 400.11 from dual
)
-- End of test data (not part of the solution). SQL query begins BELOW THIS LINE
select val as x, new_id as closest_id
from (
select id, val,
last_value(id ignore nulls) over (order by val desc) as new_id
from (
select id, (value + lead(value) over (order by value))/2 as val
from mytable
union all
select null, x
from inputs
)
)
where id is null
order by x -- if needed
;
Output:
X CLOSEST_ID
------ ----------
0 6
20.91 6
31 6
32.95 6
33.95 7
44.99 7
68 9
400.11 12

Oracle sql retrive records based on maximum time

i have below data.
table A
id
1
2
3
table B
id name data1 data2 datetime
1 cash 12345.00 12/12/2012 11:10:12
1 quantity 222.12 14/12/2012 11:10:12
1 date 20/12/2012 12/12/2012 11:10:12
1 date 19/12/2012 13/12/2012 11:10:12
1 date 13/12/2012 14/12/2012 11:10:12
1 quantity 330.10 17/12/2012 11:10:12
I want to retrieve data in one row like below:
tableA.id tableB.cash tableB.date tableB.quantity
1 12345.00 13/12/2012 330.10
I want to retrieve based on max(datetime).
The data model appears to be insane-- it makes no sense to join an ORDER_ID to a CUSTOMER_ID. It makes no sense to store dates in a VARCHAR2 column. It makes no sense to have no relationship between a CUSTOMER and an ORDER. It makes no sense to have two rows in the ORDER table with the same ORDER_ID. ORDER is also a reserved word so you cannot use that as a table name. My best guess is that you want something like
select *
from customer c
join (select order_id,
rank() over (partition by order_id
order by to_date( order_time, 'YYYYMMDD HH24:MI:SS' ) desc ) rnk
from order) o on (c.customer_id=o.order_id)
where o.rnk = 1
If that is not what you want, please (as I asked a few times in the comments) post the expected output.
These are the results I get with my query and your sample data (fixing the name of the ORDER table so that it is actually valid)
SQL> ed
Wrote file afiedt.buf
1 with orders as (
2 select 1 order_id, 'iphone' order_name, '20121201 12:20:23' order_time from dual union all
3 select 1, 'iphone', '20121201 12:22:23' from dual union all
4 select 2, 'nokia', '20110101 13:20:20' from dual ),
5 customer as (
6 select 1 customer_id, 'paul' customer_name from dual union all
7 select 2, 'stuart' from dual union all
8 select 3, 'mike' from dual
9 )
10 select *
11 from customer c
12 join (select order_id,
13 rank() over (partition by order_id
14 order by to_date( order_time, 'YYYYMMDD HH24:MI:SS' ) desc ) rnk
15 from orders) o on (c.customer_id=o.order_id)
16* where o.rnk = 1
SQL> /
CUSTOMER_ID CUSTOM ORDER_ID RNK
----------- ------ ---------- ----------
1 paul 1 1
2 stuart 2 1
Try something like
SELECT *
FROM CUSTOMER c
INNER JOIN ORDER o
ON (o.CUSTOMER_ID = c.CUSTOMER_ID)
WHERE TO_DATE(o.ORDER_TIME, 'YYYYMMDD HH24:MI:SS') =
(SELECT MAX(TO_DATE(o.ORDER_TIME, 'YYYYMMDD HH24:MI:SS')) FROM ORDER)
Share and enjoy.

Resources