Group by and bring back value in first row - oracle

I have query
SELECT ID, TIME, PRICE, QTY
FROM myTable
that returns:
ID TIME PRICE QTY
1295179228 1/29/2015 20:59:37 15.24 1112
1295179228 1/29/2015 20:59:37 15.23 2
1295179228 1/29/2015 20:59:38 15.28 22
1295179228 1/29/2015 20:59:38 15.27 1800
I am then using group by to return the min time, average price and sum of qty BUT I also want to return the first time
SELECT ID, t2.name, min(TIME) as MinTIME, avg(PRICE) as AVGPrice, sum( QTY) as SUMQTY
FROM myTable t inner join table2 t2 on t.id = t2.id
group by ID, t2.name
But how do I add a column in that group by query above that will also return the first PRICE. In that case that would be 15.24
I have been googling and I see oracle has FIRST() and FIRST_VALUE() functions but I could not get them to work.
Thank you.

Assuming that "first price" means "price with the earliest time"
min(price) keep (dense_rank first order by time)

WITH CTE AS
(SELECT ID,"TIME",PRICE,QTY,ROW_NUMBER() OVER (PARTITION BY id ORDER BY TIME ASC) as rn
FROM t)
SELECT ID, min("TIME") as MinTIME, avg(PRICE) as AVGPrice, sum( QTY) as SUMQTY,
MAX(CASE WHEN rn=1 THEN PRICE ELSE 0 END) as FirstPrice
FROM CTE
group by ID

Related

In case second highest value is missing then use the highest value of salary from employee table

I have an employee table as below. As you can see that second highest salary is 200
Incase the second highest salary is missing then there will be only one row as shown at last . In this case the query should fetch only 100
I have written query as but it is not working. Please help! Thanks
select salary "SecondHighestSalary" from(
(select id,salary,rank() over(order by salary desc) rnk
from employee2)
)a
where (rnk) in coalesce(2,1)
I have also tried the following but it is fetching 2 rows but i need only 1
It sounds like you'd want something like
with ranked_emp as (
select e.*,
rank() over (order by e.sal desc) rnk,
count(*) over () cnt
from employee2 e
)
select salary "SecondHighestSalary"
from ranked_emp
where (rnk = 2 and cnt > 1)
or (rnk = 1 and cnt = 1)
Note that I'm still using rank since you're using that in your approach and you don't specify how you want to handle ties. If there are two employees with the same top salary, rank will assign both a rnk of 1 and no employee would have a rnk of 2 so the query wouldn't return any data. dense_rank would ensure that there was at least one employee with a rnk of 2 if there were employees with at least 2 different salaries. If there are two employees with the same top salary, row_number would arbitrarily assign one the rnk of 2. The query I posted isn't trying to handle those duplicate situations because you haven't outlined exactly how you'd want it to behave in each instance.
If you are in Oracle 12.2 or higher, you can try:
select distinct id,
nvl
(
nth_value(salary, 2) from first
over(partition by id
order by salary desc
range between unbounded preceding and unbounded
following),
salary
) second_max_salary
from employee2

Oracle: how to calculate running total

Oracle 19.3 on Windows.
Trying to figure out how to show a running total.
Inner table shows unique sku_id and its qty sold.
I am trying to show a running total, but in some instances, when I sold the same number of items, running total is not changing.
What am I doing wrong?
select sku_id, sku_qty, sum(sku_qty) over (ORDER BY sku_qty desc) running_total
from (
-- April sales
select
sku_id
,sum(item_qty) sku_qty
from ecomm_order_item
where extract(YEAR from created_date) = 2021
and extract(MONTH from created_date) = 4
group by sku_id
order by sku_qty desc
)
order by sku_qty desc
You are ordering by sku_qty and if you have two rows with the same sku_qty then they will have the same rank in the ordering and the SUM will count them both at the same time.
If you want them to be counted separately then you need to give them a unique ordering.
For example, you could add sku_id to the ORDER BY clause:
select sku_id,
sku_qty,
sum(sku_qty) over (ORDER BY sku_qty desc, sku_id) running_total
from ...
Or, could use ROWNUM
select sku_id,
sku_qty,
sum(sku_qty) over (ORDER BY sku_qty desc, ROWNUM) running_total
from ...
Or anything else unique.
Or, you could change the windowing clause from the default RANGE window to use ROWS:
select sku_id,
sku_qty,
sum(sku_qty) over (
ORDER BY sku_qty desc
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) running_total
from ...
db<>fiddle here

use RANK or DENSE_RANK along with aggregate function

I have a table with the following data:
SCORE ROW_ID NAME
0.4 1011 ABC
0.95 1011 DEF
0.4 501 GHI
0.95 501 XYZ
At any point of time, i only need single row of data with maximum score, if there has more than 1 records, take the one with minimum row_id.
Is it possible to achieve by using RANK or DENSE_RANK function? How about partition by?
MAX(score) keep(dense_rank first order by row_id)
You are looking for max score, one row, so use row_number():
select score, row_id, name
from (select t.*, row_number() over (order by score desc, row_id) rn from t)
where rn = 1
demo
You can use rank and dense_rank in your example, but they can return more than one row, for instance when you add row (0.95, 501, 'PQR') to your data.
keep dense_rank is typically used when searched value is other than search criteria, for instance if we look for salary of employee who works the longest:
max(salary) keep (dense_rank first order by sysdate - hiredate desc)
max in this case means that if there are two or more employees who works longest, but exactly the same number of days than we take highest salary.
max(salary)
keep (dense_rank first order by sysdate - hiredate desc)
over (partition by deptno)
This is the same as above, but salary of longest working employees is shown for each department separately. You can even use empty over() to show salary of longest working employee in separate column except other data like name, salary, hire_date.
You dont need to use dense_rank. This would help
SELECT * FROM (
SELECT
SCORE,
ROW_ID
NAME
FROM T
ORDER BY SCORE DESC, ROW_ID DESC
)
WHERE ROWNUM = 1;

Joining the top result in Oracle

I'm using this query:
SELECT *
FROM HISTORY
LEFT JOIN CUSTOMER ON CUSTOMER.CUST_NUMBER = HISTORY.CUST_NUMBER
LEFT JOIN (
Select LOAN_DATE, CUST_NUMBER, ACCOUNT_NUMBER, STOCK_NUMBER, LOC_SALE
From LOAN
WHERE ACCOUNT_NUMBER != 'DD'
ORDER BY LOAN_DATE DESC
) LOAN ON LOAN.CUST_NUMBER = HISTORY.CUST_NUMBER
order by DATE desc
But I want only the top result from the loan table to be joined (Most recent by Loan_date). For some reason, it's getting three records (one for each loan on the customer I'm looking at). I'm sure I'm missing something simple?
If you're after joining the latest loan row per cust_number, then this ought to do the trick:
select *
from history
left join customer on customer.cust_number = history.cust_number
left join (select loan_date,
cust_number,
account_number,
stock_number,
loc_sale
from (select loan_date,
cust_number,
account_number,
stock_number,
loc_sale,
row_number() over (partition by cust_number
order by loan_date desc) rn
from loan
where account_number != 'DD')
where rn = 1) loan on loan.cust_number = history.cust_number
order by date desc;
If there are two rows with the same loan_date per cust_number and you want to retrieve both, then change the row_number() analytic function for rank().
If you only want to retreive one row, then you'd have to make sure you add additional columns into the order by, to make sure that the tied rows always display in the same order, otherwise you could find that sometimes you get different rows returned on subsequent runs of the query.

Efficiently get min,max, and summary data

I have a table of accounts and a table of transactions. In a report I need to show the following for each account:
First Purchase Date,
First Purchase Amount,
Last Purchase Date,
Last Purchase Amount,
# of Purchases,
Total of All Purchases.
The transaction table looks like this:
TX_UID
Card_Number
Post_Date
TX_Type
TX_Amount
Currently the query I've inherited has a sub-query for each of these elements. It seems to me that there's got to be a more efficient way. I'm able to use a stored procedure for this and not a single query.
A sample of a query to get all transactions for a single account would be:
select * from tx_table where card_number = '12345' and TX_Type = 'Purchase'
Any ideas?
try this:
select tt1.post_date as first_purchase_date,
tt1.tx_amount as first_purchase_amount,
tt2.post_date as last_purchase_date,
tt2.tx_amount as last_purchase_amount,
tg.pc as purchase_count,
tg.amount as Total
from (select Card_Number,min(post_date) as mipd, max(post_date) as mxpd, count(*) as pc, sum(TX_Amount) as Amount from tx_table where TX_Type = 'Purchase' group by card_number) tg
join tx_table tt1 on tg.card_number=tt1.card_number and tg.mipd=tt1.post_date
join tx_table tt2 on tg.card_number=tt2.card_number and tg.mxpd=tt2.post_date
where TX_Type = 'Purchase'
I added the count .. I didn't see it first time.
If you need also the summary on multiple TX_Types, you have to take it from the where clause and put it in the group and the inner selection join. But I guess you need only for purchases
;with cte as
(
select
Card_Number,
TX_Type,
Post_Date,
TX_Amount,
row_number() over(partition by TX_Type, Card_Number order by Post_Date asc) as FirstP,
row_number() over(partition by TX_Type, Card_Number order by Post_Date desc) as LastP
from tx_table
)
select
F.Post_Date as "First Purchase Date",
F.TX_Amount as "First Purchase Amount",
L.Post_Date as "Last Purchase Date",
L.TX_Amount as "Last Purchase Amount",
C.CC as "# of Purchases",
C.Amount as "Total of All Purchases"
from (select Card_Number, TX_Type, count(*) as CC, sum(TX_Amount) as Amount
from cte
group by Card_Number, TX_Type) as C
inner join cte as F
on C.Card_Number = F.Card_Number and
C.TX_Type = F.TX_Type and
F.FirstP = 1
inner join cte as L
on C.Card_Number = L.Card_Number and
C.TX_Type = L.TX_Type and
L.LastP = 1

Resources