Issue in SQL query full scanning twice? - oracle

Table A
ID EmpNo Grade
--------------------
1 100 HIGH
2 105 LOW
3 100 MEDIUM
4 100 LOW
5 105 LOW
Query:
select *
from A
where EMPNO = 100
and rownum <= 2
order by ID desc
I tried this query to retrieve max and max-1 value; I need to compare the grade from max and max-1, if equals I need to set the flag as 'Y' or 'N' without using a cursor. Also I don't want to scan the entire record twice.
Please help me.

ROWNUM is applied before ORDER BY, so you need to nest the query like this:
select * from
(select * from A where EMPNO =100 order by ID desc)
where rownum<=2
That only performs one table scan (or it may use an index on EMPNO).

select *
from (
select id, emp_no, grade
, case
when lag(grade) over (order by emp_no desc) = grade
then 'Y'
else 'N'
end
as flag
, dense_rank() over( order by emp_no desc) as rank
from t
)
where rank <=2
;

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;

How to write Oracle SQL to reset counter based on the value?

How can I reset the counter like below examples (I need to generate counter in the column named "Counter I need to generate"?
Looks like each value larger than 1 resets the counter, is that right?
If so, you could assign a group number first, based on the number of times a value > 1 occurs before the current row (including). So row 1 to 11 will be group 0, 12 and 13 will be group 1, and so on.
Then you can apply the row_number window function to generate the numbering partitioned by that group:
with VW_GROUPED as (
select
t.*,
(select count(*) from TheTable x where x.URN <= t.URN and x.GAPNOOFDAYS > 1) as GROUPNO
from
TheTable /* <- your table name here */ t)
select
g.URN,
g.CUSTOMER_ID,
g.GAPNOOFDAYS,
row_number() over (partition by GROUPNO order by URN) as "Counter I need to generate"
from
VW_GROUPED g
Here's an alternate example that generates the group in using analytic functions instead of a scalar subquery:
with grp as (
select t.*
, sum(case gapnoofdays when 1 then 0 else 1 end) over (partition by customer_id order by urn) grp
from your_table t
)
select grp.*
, row_number() over (partition by customer_id, grp order by urn) n
from grp;

Oracle SQL join query to find highest salary

So I have two tables salary and emp whose definition is shown as below
[
I am tring to create a query that Find the employee who draws the maximum salary and Display the employee details along with the nationality.
I created this query
select empcode,
max(basic) as "Highest Sal"
from salary
join emp on empcode;
Please help with this
Your query uses a simple aggregate max(basic) which would find the highest salary. Except you need to join to the EMP table to display other details. This means you can't use aggregation, because we need to GROUP BY the non-aggregated columns, which would make a nonsense of the query.
Fortunately we can solve the problem with an analytic function. The subquery selects all the relevant information and ranks each employee by salary, with a rank of 1 being the highest paid. We use rank() here because that will handle ties: two employees with the same basic will be in the same rank.
select empcode
, empname
, nationality
, "Highest Sal"
from (
select emp.empcode
, emp.empname
, emp.nationality
, salary.basic as "Highest Sal"
, rank() over (order by salary.basic desc ) as rnk
from salary join emp on emp.empcode = salary.empcode
)
where rnk = 1;
Find the employee who draws the maximum salary
An employee can have multiple salaries in your datamodel. An employee's (total) salary hence is the sum of these. You want to find the maximum salary per employee and show the employee(s) earning that much.
You can use MAX OVER to find the maximum sum:
select e.*, s.total_salary
from emp e
join
(
select
empcode,
sum(basic) as total_salary,
max(sum(basic)) over () as max_total_salary
from salary
) s on s.empcode = e.empcode and s.total_salary = s.max_total_salary
order by e.empcode;
Try this:
SELECT * FROM
(SELECT E.EmpCode, E.EmpName, E.DOB, E.DOJ, E.DeptCode, E.DesgCode, E.PhNo,
E.Qualification, E.Nationality, S.Basic, S.HRA, S.TA, S.UTA, S.OTRate
FROM EMP AS E JOIN SALARY AS S ON (E.EmpCode = S.EmpCode) order by S.Basic desc)
WHERE rownum = 1

Resources