Query to find before and after values of a given value - oracle

If we have table Employees
EMP_ID ENAME SALARY DEPT_ID
1 abc 1000 10
2 bca 1050 10
3 dsa 2000 20
4 zxc 3000 30
5 bnm 5000 30
6 rty 5050 30
I want to get the rank of the salary with before 2 values and after 2 values including the given rank
Like if I give rank 4 it should give ranks 2,3,4,5,6 details.
output should be
5 bnm 5000 30
4 zxc 3000 30
3 dsa 2000 20
3 dsa 2000 20
2 bca 1050 10
1 abc 1000 10
I have a query
WITH dept_count AS (
SELECT
e.*,
dense_rank() over( ORDER BY salary DESC) AS rk
FROM employees e
)
SELECT
*
FROM dept_count dc
WHERE dc.rk BETWEEN (
SELECT
c.rk-2
FROM dept_count c
WHERE c.rk =4
)
AND (
SELECT
c.rk + 2
FROM dept_count c
WHERE c.rk = 4
)
but I need a query which can be simplified.
Could someone help me with this query?

You just need to use ROW_NUMBER() along with a substitution parameter :
WITH dept_count AS (
SELECT
e.*,
ROW_NUMBER() OVER( ORDER BY salary DESC) AS rk
FROM employees e
)
SELECT *
FROM dept_count
WHERE rk BETWEEN &prm - 2 AND &prm + 2

Related

PL SQL function that includes multiple tables

I'm new to PL SQL and have to write a function, which has customer_id as an input and has to output a product_name of the best selling product for that customer_id.
The schema looks like this:
I found a lot of simple examples where it includes two tables, but I can't seem to find one where you have to do multiple joins and use a function, while selecting only the best selling product.
I could paste a lot of very bad code here and how I tried to approach this, but this seems to be a bit over my head for current knowledge, since I've been learning PL SQL for less than 3 days now and got this task.
With some sample data (minimal column set):
SQL> select * from products order by product_id;
PRODUCT_ID PRODUCT_NAME
---------- ----------------
1 BMW
2 Audi
SQL> select * From order_items;
PRODUCT_ID CUSTOM QUANTITY UNIT_PRICE
---------- ------ ---------- ----------
1 Little 100 1
1 Little 200 2
2 Foot 300 3
If we check some totals:
SQL> select o.product_id,
2 o.customer_id,
3 sum(o.quantity * o.unit_price) total
4 from order_items o
5 group by o.product_id, o.customer_id;
PRODUCT_ID CUSTOM TOTAL
---------- ------ ----------
2 Little 400
1 Little 100
2 Foot 900
SQL>
It says that
for customer Little, product 2 was sold with total = 400 - that's our choice for Little
for customer Little, product 1 was sold with total = 100
for customer Foot, product 2 was sold with total = 900 - that's our choice for Foot
Query might then look like this:
temp CTE calculates totals per each customer
rank_them CTE ranks them in descending order per each customer; row_number so that you get only one product, even if there are ties
finally, select the one that ranks as the highest
SQL> with
2 temp as
3 (select o.product_id,
4 o.customer_id,
5 sum(o.quantity * o.unit_price) total
6 from order_items o
7 group by o.product_id, o.customer_id
8 ),
9 rank_them as
10 (select t.customer_id,
11 t.product_id,
12 row_number() over (partition by t.customer_id order by t.total desc) rn
13 from temp t
14 )
15 select * From rank_them;
CUSTOM PRODUCT_ID RN
------ ---------- ----------
Foot 2 1 --> for Foot, product 2 ranks as the highest
Little 2 1 --> for Little, product 1 ranks as the highest
Little 1 2
SQL>
Moved to a function:
SQL> create or replace function f_product (par_customer_id in order_items.customer_id%type)
2 return products.product_name%type
3 is
4 retval products.product_name%type;
5 begin
6 with
7 temp as
8 (select o.product_id,
9 o.customer_id,
10 sum(o.quantity * o.unit_price) total
11 from order_items o
12 group by o.product_id, o.customer_id
13 ),
14 rank_them as
15 (select t.customer_id,
16 t.product_id,
17 row_number() over (partition by t.customer_id order by t.total desc) rn
18 from temp t
19 )
20 select p.product_name
21 into retval
22 from rank_them r join products p on p.product_id = r.product_id
23 where r.customer_id = par_customer_id
24 and r.rn = 1;
25
26 return retval;
27 end;
28 /
Function created.
SQL>
Testing:
SQL> select f_product ('Little') result from dual;
RESULT
--------------------------------------------------------------------------------
Audi
SQL> select f_product ('Foot') result from dual;
RESULT
--------------------------------------------------------------------------------
Audi
SQL>
Now, you can improve it so that you'd care about no data found issue (when customer didn't buy anything), ties (but you'd then return a collection or a refcursor instead of a scalar value) etc.
[EDIT] I'm sorry, ORDERS table has to be included into the temp CTE; your data model is correct, you don't have to do anything about it - my query was wrong (small screen + late hours issue; not a real excuse, just saying).
So:
with
temp as
(select i.product_id,
o.customer_id,
sum(i.quantity * i.unit_price) total
from order_items i join orders o on o.order_id = i.order_id
group by i.product_id, o.customer_id
),
The rest of my code is - otherwise - unmodified.

To display student id and the minimum mark scored by that students in any subject.Give an alias as minimum_mark. Sort the result based on minimum_mark

data in mark table:
VALUE SUBJECT_ID STUDENT_ID
---------- ---------- ----------
73 1 6
98 1 1
68 2 1
75 3 6
78 5 6
75 1 7
69 4 7
I have tried the following code, it executes successfully and gives the desired result, however it fails to clear one test case and IDK why?
SELECT DISTINCT
student_id,
min(value) as MINIMUM_MARK
FROM mark
WHERE value IN (
SELECT
min(value)
FROM mark
GROUP BY (subject_id)
)
GROUP BY (student_id)
ORDER BY MINIMUM_MARK;
The expected output is :
STUDENT_ID MINIMUM_MARK
---------- ------------
1 68
7 69
6 73
You can simply use group by as following:
Select student_id,
Min(value) as minimum_marks
From marks
Group by student_id
Order by minimum_marks
Cheers!!
I would just use ROW_NUMBER here:
WITH cte AS (
SELECT m.*, ROW_NUMBER() OVER (PARTITION BY STUDENT_ID ORDER BY "VALUE" DESC) rn
FROM mark
)
SELECT STUDENT_ID, MARK
FROM cte
WHERE rn = 1
ORDER BY MARK;

PIVOT table in Oracle

Can you help me to pivot the details in my Oracle table PAY_DETAILS
PAY_NO NOT NULL NUMBER
EMP_NO NOT NULL VARCHAR2(10)
EMP_ERN_DDCT_NO NOT NULL VARCHAR2(21)
ERN_DDCT_CATNO NOT NULL VARCHAR2(10)
ERN_DDCT_CATNAME NOT NULL VARCHAR2(1000)
PAY_MONTH NOT NULL DATE
AMOUNT NOT NULL NUMBER(10,2)
EARN_DEDUCT NOT NULL VARCHAR2(2)
select EMP_NO,EMP_ERN_DDCT_NO,AMOUNT,EARN_DEDUCT, ERN_DDCT_CATNO from pay_details
EMP_NO EMP_ERN_DDCT_NO AMOUNT EA ERN_DDCT_C
---------- --------------------- ---------- -- ----------
219 10 175 A 001
219 1 5000 A 002
794 7 50000 A 001
769 6 35000 A 001
465 4 5000 A 002
289 2 5000 A 002
435 3 5000 A 002
816 38 5 D 201
737 30 5 D 201
Is it possible to make this output into a cross tab?
So, lets assume you want to pivot your salary data for each month. You can use the following query.
SELECT * FROM
(
SELECT emp_no,
emp_ern_ddct_no,
pay_month,
amount
FROM pay_details
)
PIVOT
(
SUM(amount)
FOR pay_month IN ('01/01/2016', '02/01/2016', '03/01/2016','04/01/2016','05/01/2016','06/01/2016','07/01/2016','08/01/2016','09/01/2016','10/01/2016','11/01/2016','12/01/2016')
)
ORDER BY emp_no;
This is just an example, you can PIVOT your data based on different columns. For more details refer to the following link.
http://www.techonthenet.com/oracle/pivot.php
http://www.oracle-developer.net/display.php?id=506
Since you are on Oracle 10g PIVOT wont work. Try using something similar to the below query.
SELECT emp_no,
SUM(CASE WHEN pay_month ='01/01/2016' THEN AMOUNT ELSE 0 END) jan_pay,
SUM(CASE WHEN pay_month ='02/01/2016' THEN AMOUNT ELSE 0 END) feb_pay,
SUM(CASE WHEN pay_month ='03/01/2016' THEN AMOUNT ELSE 0 END) march_pay
.........
FROM pay_details
GROUP BY emp_no;

3rd highest salary in oracle

I had been looking for the query to find the 3rd highest salary from the database (using Oracle database). I found the below query -
SELECT *
FROM
( SELECT e.*, row_number() over (order by sal DESC) rn FROM emp e
)
WHERE rn = 3;
I do not have oracle installed in my system, so I'm not try it out. But I want to know if the below query will work or not. If not, then why ?
WITH Sal_sort AS
(SELECT DISTINCT sal FROM salary ORDER BY sal DESC
)
SELECT * FROM Salary S, Sal_sort SS WHERE S.Sal = SS.Sal AND SS.rownum = 3;
Input Data
emp_no emp_fname emp_lname salary
1 aa bb 30
2 ee yy 31
3 rr uu 32
4 tt ii 33
5 tt ii 33
6 tt ii 33
7 tt ii 33
8 tt ii 30
9 tt ii 31
Example:
select * from ee;
select emp_no,salary ,dense_rank() over (order by salary ) dr
from ee
Output
emp_no salary dr
1 30 1
8 30 1
9 31 2
2 31 2
3 32 3
4 33 4
5 33 4
6 33 4
7 33 4
So much easier in version 12 of the database and higher now.
SELECT *
FROM employees
ORDER BY salary DESC OFFSET 2 ROWS FETCH NEXT 1 ROWS ONLY
Tim talks about this feature here.
And if you take a look at the plan, you can see it's not magic, the optimizer is using analytic functions to derive the results.
JUST ONE LINE
select * from (
select salary,dense_rank() over (order by salary desc) rank from employees) where rank=3;
or
select * from (
select a.*,dense_rank() over (order by a.salary desc) rank from employees a) where rank=3;
Without Dense_rank()
SELECT salary FROM employees
ORDER BY salary DESC
OFFSET 2
FETCH 1 NEXT ONE ROWS ONLY;
With Dense_rank()
SELECT salary
FROM
(
SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) as rank from employees
)
WHERE rank = 3;

Oracle pivot with dynamic data

I am new to the oracle database and I am trying to use PIVOT to convert rows into columns. I have following tables..
table 1
solid solname
--------------
1 xxxxxx
2 yyyyyyy
table2
id name abbrv
----------------------------------
1 test db tdb
2 Prdocuiton db pdb
table3
id solId
-------------
1 1
1 2
1 3
1 4
1 5
1 7
1 8
1 9
1 22
1 23
1 24
1 25
2 26
2 27
1 28
1 29
1 32
1 33
1 34
1 35
1 36
1 37
3 38
1 39
1 40
1 43
1 44
table 3 is mapper table for table 1 and table 3.
I need to create a view with the columns in table2 and extra column for each solname's. So the view looks like
id name abbrv xxxxxxx yyyyyyy
--------------------------------------------------
So is there a way to do this using PIVOT in oracle database?
For Dynamic SQL Pivoting you need to do something similar :
create or replace view sol_view
as
select
t1.solname,
t2.name,
count(t3.abbrv),
from
table1 t1,
table2 t2,
table3 t3
where
t1.solid = t3.solid
and t2.id = t3.id
group by
t1.solname,
t3.name
select * from table( pivot('select * from sol_view') )
Caveat: I have never tried this but understood the logic from here:
http://technology.amis.nl/2006/05/24/dynamic-sql-pivoting-stealing-antons-thunder/
For Static SQL Pivoting, try something roughly along these lines. Never tried or tested though:
with pivot_data as (
select t1.solname, t2.name, t3.abbrv
from table1 t1, table2 t2, table3 t3
where t1.solid = t3.solid
and t2.id = t3.id
)
select *
from pivot_data
pivot (
count(abbrv)
for solname
in ('xxxxxx','yyyyyyy')
);
It was not really defined what you want to store in the xxxx and yyyy columns, maybe 1/blank, Y/N, ... ? However, your query might look close to something like this:
SELECT * FROM (
SELECT *
FROM table3 t3
JOIN table2 t2 USING (id)
JOIN table1 t1 USING (solid)
) PIVOT (
COUNT(*) FOR (solname) IN (
('xxx') AS "XXX",
('yyy') AS "YYY"
)
)
You can find more information and additional references on My Blog
TableName - **tblItem**
Id ItemName RecipeName Quantity
1 Sugar mutter paneer 200
2 Tea mutter paneer 100
3 Tomato mutter paneer 500
4 Onion mutter paneer 300
5 Ginger mutter paneer 300
6 Capsicum mutter paneer 300
7 Sugar mutter paneer 200
8 Tea mutter paneer 100
9 Onion mutter paneer 500
10 Sugar mutter paneer 200
V_VALUES varchar2(4000);
sql_query varchar2(4000);
SELECT
LISTAGG(''''||ITEMNAME||'''',',')WITHIN GROUP (ORDER BY ITEMNAME)as ITEMNAME
INTO V_LIST
FROM(SELECT DISTINCT ITEMNAME FROM tblItem);
sql_query : = 'SELECT * FROM (
SELECT ItemName,RecipeName,Sum(Quantity) as Quantity from tblItem group by ItemName,RecipeName)
PIVOT (
sum(Quantity) for ItemName in (' ||V_LIST|| ')
)';
OPEN p_cursor
FOR sql_query;

Resources