Error while using Window Clause in Oracle Query - oracle

I have been looking at this code for the past two days now and I can not seem to get it to work.
It does work without the Window clause though.
It keeps giving me:
ORA-00907: missing right parenthesis.
select P.*,
first_value(product_name) over (w) MAX_PRICE,
Last_value(product_name) over (w) MIN_PRICE
from product P
window w as (
partition by product_category
order by price desc
range between unbounded preceding and unbounded following
);

The window clause goes inside the analytic function:
select P.*,
first_value(product_name) over (
partition by product_category
order by price desc
range between unbounded preceding and unbounded following
) AS MAX_PRICE,
Last_value(product_name) over (
partition by product_category
order by price desc
range between unbounded preceding and unbounded following
) MIN_PRICE
from product p;
Or, from Oracle 21, you can use:
select P.*,
first_value(product_name) over w AS MAX_PRICE,
Last_value(product_name) over w AS MIN_PRICE
from product p
window w as (
partition by product_category
order by price desc
range between unbounded preceding and unbounded following
)
(Without the brackets around the window in the analytic function.)
db<>fiddle here

Related

running balance debit credit column in oracle query

Output result
I want the running balance in my query. I had wrote the query.
May be i am mistaking any where please let me know.
SELECT
ACC_VMAST.VM_DATE,
ACC_VDET.CHEQUE,
ACC_VMAST.NARRATION,
ACC_VDET.DEBIT,
ACC_VDET.CREDIT,
sum(nvl(ACC_VDET.DEBIT,0) - nvl(ACC_VDET.CREDIT,0) )
over (order by ACC_VMAST.VM_DATE , ACC_VDET.DEBIT ) running_bal
FROM ACC_VMAST,
ACC_VDET,
ACC_COA
WHERE ACC_VMAST.VM_PK=ACC_VDET.VM_PK
AND ACC_COA.COA_PK=ACC_VDET.COA_PK
AND ACC_VMAST.POST_BY IS NOT NULL
AND ACC_VMAST.CANCEL_STATUS IS NULL
AND ACC_VMAST.VM_DATE BETWEEN '07/06/2021' AND '07/07/2021'
AND ACC_VDET.COA_PK= '303'
ORDER BY ACC_VMAST.VM_DATE , ACC_VDET.DEBIT;
If you have rows that have the same values for the ORDER BY clause then when you SUM the values then all the rows with the same ORDER BY value will be grouped together and totalled.
To prevent that, you can add the ROWNUM pseudo-column to the ORDER BY clause of the analytic function so that there will not be any ties:
SELECT m.VM_DATE,
d.CHEQUE,
m.NARRATION,
d.DEBIT,
d.CREDIT,
SUM( COALESCE(d.DEBIT,0) - COALESCE(d.CREDIT,0) )
OVER ( ORDER BY m.VM_DATE, d.DEBIT, ROWNUM ) AS running_bal
FROM ACC_VMAST m
INNER JOIN ACC_VDET d
ON (m.VM_PK = d.VM_PK)
INNER JOIN ACC_COA c
ON (c.COA_PK = d.COA_PK)
WHERE m.POST_BY IS NOT NULL
AND m.CANCEL_STATUS IS NULL
AND m.VM_DATE BETWEEN DATE '2021-07-06' AND DATE '2021-07-07'
AND d.COA_PK = '303'
ORDER BY
m.VM_DATE,
d.DEBIT;
You need to add a row range with "rows between unbounded preceding and current row".
sum(nvl(ACC_VDET.DEBIT,0) - nvl(ACC_VDET.CREDIT,0) )
over (order by ACC_VMAST.VM_DATE , ACC_VDET.DEBIT rows between unbounded preceding and current row ) running_bal

Left Join with Multiple Conditions and MAX Value

I'm trying to execute a left join where multiple conditions must be met with the inclusion of pulling in the MAX sequence number that meets those conditions.
The left join is on the unique identifier in both tables. Table acaps_history has several rows for each app_id. I need to pull in only one row with the highest seq_number and activity_code of 'XU'. If the code 'XU' doesn't exist for the given app_id, then the case statement above should return 'N' for that row. The code I have currently just isn't working - returning the error "a column may not be outer-joined to a subquery":
create table orig_play3 as
(select
x.*,
case when xa.activity_code in 'XU' then 'Y' else 'N' end as cpo_flag
from
dfs_tab_orig_play_x x
left join cf.acaps_history xa on
x.APP_ID = xa.FOC_APPL_ID
and xa.activity_code in 'XU'
and xa.seq_number = (select max(seq_number) from cf.acaps_history where FOC_APPL_ID=x.app_id)
)
Given your error, it seems that the issue is the last part of your query:
and xa.seq_number = (select max(seq_number) from cf.acaps_history where FOC_APPL_ID=x.app_id)
This is still operating in the context of the ON clause, so the sub-query to find the max sequence number is the issue.
You should be able to avoid this by moving that sub-query out of the ON clause:
LEFT JOIN (
SELECT FOC_APPL_ID, activity_code, seq_number
FROM cf.acaps_history
WHERE activity_code in 'XU'
) xa
ON x.APP_ID = xa.FOC_APPL_ID
WHERE xa.seq_number = (select max(ah.seq_number) from cf.acaps_history ah where ah.FOC_APPL_ID=x.app_id and ah.activity_code in 'XU')
This may be the most inefficient way to execute this query, but it worked... It took like 3 minutes to run (table size is over 600K rows), but again, it returned the results I needed:
create table test as (
select x.*,
case when xb.activity_code in 'XU' then 'Y' else 'N' end as cpo_flag
from dfs_tab_orig_play_x x
left join
(select
xa.FOC_APPL_ID, xa.activity_code, xa.seq_number
from dfs_tab_orig_play_x x, cf.acaps_history xa
where x.app_id = xa.FOC_APPL_ID (+)
and xa.seq_number = (select max(seq_number) from cf.acaps_history where
x.app_id=FOC_APPL_ID(+) and activity_code in 'XU')) xb
on x.app_id = xb.FOC_APPL_ID (+)
)
If you are on 12c, I like OUTER APPLY for this sort of thing, because it lets you sort the rows for each app_id descending by seq_number and then just pick the highest one.
SELECT
x.*,
CASE
WHEN xa.activity_code IN 'XU' THEN 'Y'
ELSE 'N'
END
AS cpo_flag
FROM
dfs_tab_orig_play_x x
OUTER APPLY ( SELECT *
FROM cf.acaps_history xa
WHERE xa.foc_appl_id = x.app_id
AND xa.activity_code = 'XU'
ORDER BY xa.seq_number DESC
FETCH FIRST 1 ROW ONLY ) xa
Note: this logic is a little different from what you posted. In this version, it will join to the acaps_history row having the highest seq_number from among 'XU' records for the given app_id. Your version was joining to the row having the highest seq_number for the given app_id, whether that row was an 'XU' row or not. I am assuming (with little reason) that that was a bug on your part. But, if it wasn't, my version won't work as given.

ORACLE - MAX and SUM

The below query returns a list of the most popular theatre and rowtype combinations sorted by total amount:
so for example:
NAME ROWTYPE TOTALAMOUNT
theatre1 middle 200
theatre2 front 190
theatre1 front 150
theatre2 middle 100
Whereas what I need is simply the maximum per theatre:
theatre1 middle 200
theatre2 front 190
Query:
SELECT name, rowtype, sum
from ( select
name, rowtype, sum(totalamount) sum from trow, fact, theatre
Where trow.trowid = fact.trowid
AND
theatre.theatreid = fact.theatreid
GROUP BY rowtype, name
)
ORDER BY sum DESC, name, rowtype ;
You can use window functions for this:
select name, rowtype, sum
from (select name, rowtype, sum(totalamount) as sumta,
max(sum(totalamount)) over (partition by name) as maxsumta
from trow join
fact
on trow.trowid = fact.trowid join
theatre
on theatre.theatreid = fact.theatreid
group byrowtype, name
) nr
where sumta = maxsumta;
In addition, you should learn to use proper, explicit JOIN syntax. A simple rule: Never use commas in the FROM clause. Always use proper explicit JOIN syntax.
Put your current query into a common table expression and then use window functions to find the max total amount for each theatre.
WITH cte AS
(
SELECT name, rowtype, SUM(totalamount) sum
FROM trow
INNER JOIN fact
ON trow.trowid = fact.trowid
INNER JOIN theatre
ON theatre.theatreid = fact.theatreid
GROUP BY name, rowtype
)
SELECT name, rowtype, sum
FROM
(
SELECT name,
rowtype,
sum,
MAX(sum) OVER (PARTITION BY name) maxSum
FROM cte
) t
WHERE t.sum = t.maxSum

Oracle stored procedure - gradual building of of out variable

I'm sorry for my strange title, but I don't know what exactly I'm looking for. The task is quite simple. I have the table of competitions. Another table groups. In every group there are several contestants. In the last table are stored the results of contestants. The task is to get the first three of the contestants of every group.
So I have to loop through the groups, get the first three contestants (according to achieved points) of every group and append them into some variable.
Here is the pseudocode:
CREATE OR REPLACE PROCEDURE get_first_three_of_all(contestants OUT SOME_TYPE) AS
CURSOR groups SELECT...
BEGIN
FOR group IN groups LOOP
APPEND(contestants, get_first_three_of_one_group(group.id))
END LOOP;
END;
I have no idea, how to solve this task. I even don't know what should I look for. Would you be so kind and help me, please? Thanks.
Edited: simplified structure of my tables:
Competition: competition_id
Contestant: contestant_id
GroupContestant: contestant_group_id, competition_d, group_number, contestant_id
Result: contestant_group_id, juror, points
Select to get data of one group (group number YYY) is here:
SELECT * FROM (
SELECT res.contestant_group_id, SUM(res.points) AS points
FROM Result res
WHERE res.couple_group_id IN (SELECT couple_group_id
FROM GroupContestant
WHERE competition_id = XXX
AND group_number = YYY)
GROUP BY res.contestant_group_id
ORDER BY points DESC
)
WHERE ROWNUM <= 3;
Analytic functions to the rescue. To select top 3 results for each group, each competition:
SELECT * FROM (
SELECT grp.competition_id, grp.group_number, res.contestant_group_id, res.points,
row_number() over (partition by grp.competition_id, grp.group_number
order by res.points desc) rn
FROM (SELECT contestant_group_id, SUM(points) AS points
FROM Result
GROUP BY contestant_group_id) res
JOIN GroupContestant grp ON (grp.contestant_group_id = res.contestant_group_id)
)
WHERE rn <= 3;
Pay attention to how you resolve ties (consider using rank or dense_rank instead of row_number).
You can use RANK() analytic function to achieve the goal:
select *
from (select group_num,
points,
rank() over(partition by group_num order by points desc) rank
from results
inner join group_contestant
using (contestant_group_id))
where rank <= 3
order by group_num, points desc;
Here is SQLFiddle to play with.

Oracle 9 PL/SQL - How to get first row using order by without sub query

I know the "how to limit" or "how to get 1st row" has been posted many times but I can't find a solution to my specific issue.
I have a inventory balance table that contains bin # with quantities
I want on my row the bin # that contains the highest quantity
The real queries are much bigger and complex than this but this example shows the issue I am facing
I first I did
select itemnumber,
(select binnumber from inventory_balance where current_balance = (select max(current_balance) from inventory_balance where inventory_balance.itemnumber = item_table.itemnumber)) as binnumber
from item_table;
This will work when there is only one "bin" with the highest quantity.
If there are 2 bins for the same item with a quantity of 10 (which is the highest quantity), the sub query will return 2 rows, triggering a oracle error
Then I tried this :
select
itemnumber,
(select binnumber from (select binnumber from inventory_balance where current_balance = (select max(current_balance) from inventory_balance where inventory_balance.itemnumber = item_table.itemnumber)) where rownum =1) as binnumber
from item_table;
Now this will not work because it seems that the references to item_table.itemnumber is invalid when inside the from (...). I get "invalid column name" error when trying to do so.
I can't use ROW_NUMBER() because the "OLAP Window functions" do not seem to be activated on the database.
Something like this:
SELECT t.itemnumber,
MIN( b.binnumber ) KEEP ( DENSE_RANK LAST ORDER BY b.current_balance ASC ) AS binnumber
FROM item_table t
LEFT OUTER JOIN inventory_balance b
ON ( t.itemnumber = b.itemnumber )
GROUP BY t.itemnumber;
Looking at the explain plan then this will only scan inventory_balance once whereas doing nested selects to get the MAX balance and then filter an outer query based on that requires two scans of inventory_balance.
Although all the required output for you minimal working example seems to be contained in the inventory_balance table so you can do (if you are not interested in the itemnumbers where there are no entries in the inventory_balance table):
SELECT itemnumber,
MIN( binnumber ) KEEP ( DENSE_RANK LAST ORDER BY current_balance ASC ) AS binnumber
FROM inventory_balance
GROUP BY itemnumber;
If you want the highest binnumber (instead of the lowest) then you can just change it to:
MAX( binnumber ) KEEP ...

Resources