Oracle: Getting highest value from two fields (date then value) - oracle

I have something like this (date simplified to integer just for the example):
Order Date Value
12 5 555
12 5 800
12 2 900
13 3 122
13 4 155
14 1 121
... ... ...
And I'd like to get the order with the highest date and then the highest value:
Order Date Value
12 5 800
13 4 155
14 1 121
... ... ...
I know this is similar to several other questions but cant figure out how to apply those answers to my case, sorry.
Thanks!

Use analytic function ROW_NUMBER
SELECT "Order", "Date", "Value"
FROM (
select t.*,
row_number()
over (partition by "Order" order by "Date" desc, "Value" desc ) As rn
FROM table1 t
) x
WHERE rn = 1

You can use the analytic FIRST/LAST function (see Oracle documentation). This solution does not need the subquery/outer query arrangement. I changed the column names since at least ORDER and DATE are Oracle keywords.
with
inputs ( ord, dt, val ) as (
select 12, 5, 555 from dual union all
select 12, 5, 800 from dual union all
select 12, 2, 900 from dual union all
select 13, 3, 122 from dual union all
select 13, 4, 155 from dual union all
select 14, 1, 121 from dual
)
-- End of test data (not part of the solution).
-- SQL query begins BELOW THIS LINE
select ord, max(dt) as dt, max(val) keep (dense_rank last order by dt) as val
from inputs
group by ord
;
ORD DT VAL
--- -- ---
12 5 800
13 4 155
14 1 121

Lets assume your table name is orders
In a subquery you find orders with highest dates and for those you find highest values... It will look like this:
select order,date,max(value) value from orders
where (order,date) in
(select order, max(date) date
from orders
group by order
)
group by order,date
order by order,date desc

Related

How to compare two comma separated string and insert in DB

I have following table (Users) with 2 columns and trying to update second column with first column ( first column is autoincrement and primary key field).
UserId CatId
1 10
2 78
3 99
4 89
5 80
I am finding difficult to update second column with P_uid order by (1,3,4). I tried normal Update statement but it doesn't work. I am passing following ids into SP.
P_uid varchar2(20) := '1,3,4';
P_new_cat_id varchar2(20) := '100,12,13';
Expected output
---------------
UserId CatId
1 100
2 78
3 12
4 13
5 80
Use a MERGE statement:
MERGE INTO users dst
USING (
SELECT 1 AS id, 100 AS new_catid FROM DUAL UNION ALL
SELECT 3, 12 FROM DUAL UNION ALL
SELECT 4, 13 FROM DUAL
) src
ON (src.id = dst.userid)
WHEN MATCHED THEN
UPDATE
SET catid = src.new_catid;
Which, for the sample data:
CREATE TABLE users (UserId, CatId) AS
SELECT 1, 10 FROM DUAL UNION ALL
SELECT 2, 78 FROM DUAL UNION ALL
SELECT 3, 99 FROM DUAL UNION ALL
SELECT 4, 89 FROM DUAL UNION ALL
SELECT 5, 80 FROM DUAL;
Then, after the merge, the table contains:
USERID
CATID
1
100
2
78
3
12
4
13
5
80
If you have delimited strings then (apart from finding a better solution for passing multi-row data) you can split the values into rows using a row-generator function and use the previous technique:
DECLARE
P_uid varchar2(20) := '1,3,4';
P_new_cat_id varchar2(20) := '100,12,13';
BEGIN
MERGE INTO users dst
USING (
SELECT REGEXP_SUBSTR(p_uid, '\d+', 1, LEVEL) AS id,
REGEXP_SUBSTR(p_new_cat_id, '\d+', 1, LEVEL) AS new_catid
FROM DUAL
CONNECT BY LEVEL <= LEAST(
REGEXP_COUNT(p_uid, '\d+'),
REGEXP_COUNT(p_new_cat_id, '\d+')
)
) src
ON (src.id = dst.userid)
WHEN MATCHED THEN
UPDATE
SET catid = src.new_catid;
END;
/
Which outputs the same.
db<>fiddle here

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 select the first 5 dates from each group and put them in a single column separated by comma in Oracle?

I have a table like this:
Division
Region
Date of Last Visit
1
2
11/20/2021
1
2
11/18/2021
1
7
10/18/2021
1
7
11/19/2021
2
2
11/17/2021
2
3
09/20/2021
2
3
10/20/2021
I want to write a query that groups by the division and region columns and gives me the last 5 dates for each group separated by commas in a single column. Something like this:
Division
Region
Date of Last Visit
Today
Days since last visit
1
2
11/20/2021, 11/18/2021
sysdate
sysdate - max(date of last visit)
1
7
10/18/2021, 11/19/2021
sysdate
sysdate - max(date of last visit)
2
2
11/17/2021
sysdate
sysdate - max(date of last visit)
2
3
9/20/2021, 10/20/2021
sysdate
sysdate - max(date of last visit)
The last two columns are custom calculated columns that I also need for the final output table. Any help would be greatly appreciated as I have tried a lot of things but I keep getting errors about it not being grouped properly, possibly because of the two extra columns at the end. But even without that, I am not sure how to fetch only the last 5 dates per group in oracle.
Thanks!
You want to filter the greatest-n-per-group using the ROW_NUMBER analytic function and then aggregate:
SELECT division,
region,
LISTAGG(TO_CHAR(date_of_last_visit, 'DD/MM/YYYY'), ',')
WITHIN GROUP (ORDER BY date_of_last_visit DESC)
AS date_of_last_visit,
SYSDATE AS today,
TRUNC(SYSDATE - MAX(date_of_last_visit)) AS days_since_last_visit
FROM (
SELECT t.*,
ROW_NUMBER() OVER (PARTITION BY division, region
ORDER BY date_of_last_visit DESC) AS rn
FROM table_name t
)
WHERE rn <= 5
GROUP BY
division,
region
Which, for the sample data:
CREATE TABLE table_name (division, region, date_of_last_visit) as
select 1, 2, date '2021-11-20' from dual union all
select 1, 2, date '2021-11-18' from dual union all
select 1, 7, date '2021-10-18' from dual union all
select 1, 7, date '2021-11-19' from dual union all
select 2, 2, date '2021-11-17' from dual union all
select 2, 3, date '2021-09-20' from dual union all
select 2, 3, date '2021-10-20' from dual;
Outputs:
DIVISION
REGION
DATE_OF_LAST_VISIT
TODAY
DAYS_SINCE_LAST_VISIT
1
2
20/11/2021,18/11/2021
30-NOV-21
10
1
7
19/11/2021,18/10/2021
30-NOV-21
11
2
2
17/11/2021
30-NOV-21
13
2
3
20/10/2021,20/09/2021
30-NOV-21
41
db<>fiddle here
Here you go; read comments within code.
SQL> with test (division, region, datum) as
2 -- sample data
3 (select 1, 2, date '2021-11-20' from dual union all
4 select 1, 2, date '2021-11-18' from dual union all
5 select 1, 7, date '2021-10-18' from dual union all
6 select 1, 7, date '2021-11-19' from dual union all
7 select 2, 2, date '2021-11-17' from dual union all
8 select 2, 3, date '2021-09-20' from dual union all
9 select 2, 3, date '2021-10-20' from dual
10 ),
11 temp as
12 -- rank rows per division/region, sorted by date column in descending order
13 (select t.*,
14 rank() over (partition by division, region order by datum desc) rnk
15 from test t
16 )
17 -- select up to 5 last rows per division/region
18 select division, region,
19 listagg(datum, ', ') within group (order by datum) dates,
20 trunc(sysdate) today,
21 --
22 (select trunc(sysdate) - a.datum
23 from temp a
24 where a.division = t.division
25 and a.region = t.region
26 and a.rnk = 1) days_since
27 from temp t
28 where rnk <= 5
29 group by division, region
30 order by division, region;
DIVISION REGION DATES TODAY DAYS_SINCE
---------- ---------- ------------------------------ ---------- ----------
1 2 11/18/2021, 11/20/2021 11/30/2021 10
1 7 10/18/2021, 11/19/2021 11/30/2021 11
2 2 11/17/2021 11/30/2021 13
2 3 09/20/2021, 10/20/2021 11/30/2021 41
SQL>

Oracle query to keep looking until value is not 0 anymore

I am using Oracle 11.
I have 2 tables
TblA with columns id, entity_id and effective_date.
TblADetail with columns id and value.
If Value = 0 for the effective date, I want to keep looking for the next effective date until I found value <> 0 anymore.
The below query only look for value on 3/10/21.
If value = 0, I want to look for value on 3/11/21. If that's not 0, I want to stop.
But, if that's 0, I want to look for value on 3/12/21. If that's not 0, I want to stop.
But, if that's 0, I want to keep looking until value is not 0.
How can I do that ?
SELECT SUM(pd.VALUE)
FROM TblA p,TblADetail pd
WHERE p.id = pd.id
AND p.effective_date = to_date('03/10/2021','MM/DD/YYYY')
AND TRIM (p.entity_id) = 123
Sample data:
TblA
id entity_id effective_date
1 123 3/10/21
2 123 3/11/21
3 123 3/12/21
TblADetail
id value
1 -136
1 136
2 2000
3 3000
In the above data, for entity_id 123, starting from effective_date 3/10/21, I would like to to return value 2000 (from TblADetail) effective_date 3/11/21.
So, starting from a certain date, I want the results from the minimum date that has non-zero values.
Thank you.
You can do what you need to do by grouping the sum on the effective date, and using the MIN analytic function to find the earliest date. Once you've done that, you simply need to select the date that matches the earliest date.
E.g.:
with tbla as (select 1 id, ' 123' entity_id, to_date('10/03/2021', 'dd/mm/yyyy') effective_date from dual union all
select 2 id, ' 123' entity_id, to_date('11/03/2021', 'dd/mm/yyyy') effective_date from dual union all
select 3 id, ' 123' entity_id, to_date('12/03/2021', 'dd/mm/yyyy') effective_date from dual),
tbla_detail as (select 1 id, -136 value from dual union all
select 1 id, 136 value from dual union all
select 2 id, 2000 value from dual union all
select 3 id, 3000 value from dual),
results as (select a.effective_date,
sum(ad.value) sum_value,
min(case when sum(ad.value) != 0 then a.effective_date end) over () min_effective_date
from tbla a
inner join tbla_detail ad on a.id = ad.id
where a.effective_date >= to_date('10/03/2021', 'dd/mm/yyyy')
and trim(a.entity_id) = '123'
group by a.effective_date)
select sum_value
from results
where effective_date = min_effective_date;
SUM_VALUE
----------
2000
Straightforward; read comments within code. Sample data in lines #1 - 13, query begins at line #14.
SQL> with
2 -- sample data
3 tbla (id, entity_id, effective_date) as
4 (select 1, 123, date '2021-03-10' from dual union all
5 select 2, 123, date '2021-03-11' from dual union all
6 select 3, 123, date '2021-03-12' from dual
7 ),
8 tblb (id, value) as
9 (select 1, -136 from dual union all
10 select 1, 136 from dual union all
11 select 2, 2000 from dual union all
12 select 3, 3000 from dual
13 ),
14 tblb_temp as
15 -- simple grouping per ID
16 (select id, sum(value) value
17 from tblb
18 group by id
19 )
20 -- return TBLA values whose ID equals TBLB_TEMP's minimum ID
21 -- whose value isn't zero
22 select a.id, a.entity_id, a.effective_date
23 from tbla a
24 where a.id = (select min(b.id)
25 from tblb_temp b
26 where b.value > 0
27 );
ID ENTITY_ID EFFECTIVE_
---------- ---------- ----------
2 123 03/11/2021
SQL>

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

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

Resources