Re-write oracle query to avoid multiple table scan - sql

I hope everyone is fine and learning more. I need some advice on select statement and on its fine tuning. I am using Oracle 11gR2. Please find below table and data scripts.
create table employee (emp_id number, emp_name varchar2(50), manager_id number);
create table department (dept_id number, dept_name varchar2(50), emp_name varchar2(50), manager_level varchar2(20));
create table manager_lookup (manager_level_id number, manager_level varchar2(20));
insert into employee values (1, 'EmpA',3);
insert into employee values (2, 'EmpB',1);
insert into employee values (3, 'EmpC',1);
insert into employee values (4, 'EmpD',2);
insert into employee values (5, 'EmpE',1);
insert into employee values (6, 'EmpF',3);
insert into department values (1, 'DeptA','EmpD','Level3');
insert into department values (2, 'DeptB','EmpC','Level2');
insert into department values (3, 'DeptC','EmpA','Level1');
insert into department values (4, 'DeptD','EmpF','Level1');
insert into department values (5, 'DeptD','EmpA','Level3');
insert into department values (6, 'DeptA',NULL,'Level3');
insert into manager_lookup values (1, 'Level1');
insert into manager_lookup values (2, 'Level2');
insert into manager_lookup values (3, 'Level3');
commit;
Below query is returning me dept_id by passing some emp_name. I need those dept_id where manager_level is same as emp_name passed but don't need to have that same emp_name in result data set.
SELECT b.dept_id
FROM (SELECT DISTINCT manager_level
FROM department dpt
WHERE emp_name = 'EmpA'
and emp_name is not null) a,
department b
WHERE a.manager_level = b.manager_level
AND NVL (b.emp_name, 'ABC') <> 'EmpA';
Above query is returning me data set as below:
dept_id
--------
1
4
6
I want same result set but need to rewrite above query in a way to avoid department table scan two times. This is just sample query but in real time scanning big table two times is giving performance issues. I want to re-write this query in a way to perform better and avoid same table scan two times.
Can you please help on providing your wonderful suggestions or solutions? I will really appreciate all responses.
Thank you for going over the question.

If you want your query to be more efficient, then use indexes:
create index idx_department_name_level on department(emp_name, manager_level)
and
create index idx_department_name_level on department(manager_level, emp_name, dept_id)

also, you have a redundant null check which may be avoiding indexes...
SELECT b.dept_id
FROM (SELECT manager_level
FROM department dpt
WHERE emp_name = 'EmpA') a,
department b
WHERE a.manager_level = b.manager_level
AND NVL (b.emp_name, 'ABC') <> 'EmpA';
post your explain plan for more help

This should work:
SELECT a.*
FROM
(
SELECT d.*,
SUM(CASE WHEN emp_name = 'EmpA' THEN 1 ELSE 0 END)
OVER (PARTITION BY manager_level) AS hits
FROM department d
) a
WHERE hits > 0
AND NVL(emp_name, 'Dummy') <> 'EmpA'
ORDER BY dept_id
;
The query does the following:
Calculate how many times EmpA appears in a given manager_level
Keep all records with a manager_level that has at least one occurrence of EmpA in it
Excluding the EmpA records themselves
SQL Fiddle of the query in action: http://sqlfiddle.com/#!4/a9e03/7
You can verify that the execution plan contains only one full table scan.

Related

SQL query to print the name of the employee getting max % hike in the annual salary from 2018 to 2019

I am very much new to SQL and have been practicing straight forward queries since a few days. I cam across this question which I would request help for. So there is a table with employee name, salary and salary date as columns. Date is simply Year from 2015 to 2019. I am trying to write a query where I can get employee name with the maximum % hike in salary from 2018 to 2019. I wrote the below query but stuck for hours at the same.
CREATE TABLE data (
salary INTEGER NOT NULL,
emp_name TEXT NOT NULL,
Sal_Date YEAR NOT NULL
);
INSERT INTO data VALUES (10000, 'Ryan', 2015);
INSERT INTO data VALUES (12000, 'Bryan', 2016);
INSERT INTO data VALUES (11000, 'Manthan', 2016);
INSERT INTO data VALUES (15000, 'Susan', 2017);
INSERT INTO data VALUES (16000, 'Alien', 2017);
INSERT INTO data VALUES (10000, 'Ryan', 2018);
INSERT INTO data VALUES (12000, 'Bryan', 2018);
INSERT INTO data VALUES (11000, 'Manthan', 2018);
INSERT INTO data VALUES (15000, 'Susan', 2018);
INSERT INTO data VALUES (16000, 'Alien', 2018);
INSERT INTO data VALUES (11000, 'Ryan', 2019);
INSERT INTO data VALUES (13000, 'Bryan', 2019);
INSERT INTO data VALUES (15000, 'Manthan', 2019);
INSERT INTO data VALUES (18000, 'Susan', 2019);
INSERT INTO data VALUES (32000, 'Alien', 2019);
SELECT salary from data
group by ;
I am getting no logic to solve this. Can anybody please help with the query and logic explanation. I'll be grateful. Thanks
SELECT * , (salary - LAG(salary,1,salary) over (partition by emp_name order by sal_date)) /salary * 100 as promotionPercentage
from data
order by emp_name , sal_date
db<>fiddle here
You can use a Self-join to join a table to itself and return the salary for the previous year, then order by the % change in salary and return the first one
SELECT TOP 1 t1.emp_name
FROM data t1
JOIN data t2 ON t1.emp_name = t2.emp_name AND t1.Sal_date = t2.Sal_Date + 1
WHERE t1.Sal_Date = 2019
ORDER BY (t1.Sal_Date - t2.Sal_Date) / t2.Sal_Date * 100
Alternatively, you can use the LAG analytic function but I am not sure it is present in all DBMS?
I am using MSSQL for this one
You can self-join the table with the condition that:
same emp_name
the first table is for 2018, the other one 2019
Then, you can compare the salary in 2018 and 2019 in the same row.
Here is a query example. You may need to modify a bit depending on your SQL engine.
SELECT
d18.emp_name,
cast(d19.salary AS FLOAT) / d18.salary - 1 AS hike_18_to_19
FROM
data d18
INNER JOIN
data d19
ON
d18.emp_name = d19.emp_name
AND d18.Sal_Date = 2018
AND d19.Sal_Date = 2019
ORDER BY
hike_18_to_19 DESC
You get something like this:
emp_name hike_18_to_19
0 Alien 1.000000
1 Manthan 0.363636
2 Susan 0.200000
3 Ryan 0.100000
4 Bryan 0.083333

Obtaining values from a column using the max function with oracle sql, including duplicates

I've been trying all day to use the max function on the results from the select query at the bottom. I want to get from the following:
ROOM NURSES_PER_ROOM
RM2 1
RM1 .1666666666666666666666666666666666666667
RM3 1
To:
ROOM
RM2
RM3
Where RM2 and RM3 have the highest NURSES_PER_ROOM ratio.
create table Nurse
(PIN varchar2(6) not null primary key,
first_name char(16),
last_name char(20));
create table Room
(number_ varchar2(6) not null primary key,
size_ varchar2(6) not null);
create table Allocation
(nurse varchar2(6) not null primary key,
room varchar2(6) not null,
foreign key (nurse)
references Nurse(PIN),
foreign key (room)
references Room(number_));
insert into nurse(PIN, first_name, last_name)
values ('NU0011', 'Mary', 'Fritz');
insert into nurse(PIN, first_name, last_name)
values ('NU0012', 'Goth', 'Mortimer');
insert into nurse(PIN, first_name, last_name)
values ('NU0013', 'Rosa', 'Lotta');
insert into nurse(PIN, first_name, last_name)
values ('NU0014', 'Josie', 'Josiah');
insert into nurse(PIN, first_name, last_name)
values ('NU0015', 'Ruth', 'Williams');
insert into nurse(PIN, first_name, last_name)
values ('NU0016', 'Paige', 'Wakeham');
insert into room(number_, size_)
values ('RM1', '6');
insert into room(number_, size_)
values ('RM2', '2');
insert into room(number_, size_)
values ('RM3', '3');
insert into allocation(nurse, room)
values ('NU0011', 'RM1');
insert into allocation(nurse, room)
values ('NU0012', 'RM3');
insert into allocation(nurse, room)
values ('NU0013', 'RM3');
insert into allocation(nurse, room)
values ('NU0014', 'RM3');
insert into allocation(nurse, room)
values ('NU0015', 'RM2');
insert into allocation(nurse, room)
values ('NU0016', 'RM2');
select room, (number_nurses/ro.size_) as nurses_per_room from
(select room, count(nurse) as number_nurses
from allocation
group by room), room ro
where ro.number_ = room
Any help is greatly appreciated, thank you.
EDIT: Trying to do this without JOIN operations or the CASE statement.
This is the way I understood the question:
test CTE calculates number of nurses per room
it is then used in a subquery to fetch only rooms with highest numbers of nurses
SQL> with test as
2 (select a.room,
3 count(*) / r.size_ nurses_per_room
4 from allocation a join room r on r.number_ = a.room
5 group by a.room, r.size_
6 )
7 select t.room
8 from test t
9 where t.nurses_per_room = (Select max(t1.nurses_per_room) from test t1);
ROOM
------
RM2
RM3
SQL>
If I understand correctly, you want all rows where the calculation number_nurses has its max value. If so, use analytic functions:
select r.*
from (select ro.number_, count(*) / ro.size_ as number_nurses,
rank() over (order by count(*) / ro.size_) as seqnum
from allocation a join
room r
on ro.number_ = a.room
group by ro.number_, ro.size_
) r
where seqnum = 1;

Simple select query missing rows in Ignite

Selecting by ID over a table with 2 entries results in a single output result.
Here's the example table:
CREATE TABLE IF NOT EXISTS Person(
id int,
city_id int,
name varchar,
age int,
company varchar,
PRIMARY KEY (id, city_id)
);
INSERT INTO Person (id, name, city_id) VALUES (1, 'John Doe', 3);
INSERT INTO Person (id, name, city_id) VALUES (1, 'John Dean', 4);
The following queries were executed in the same order:
"SELECT * FROM Person" returns both rows - expected
"SELECT * FROM Person WHERE age is null" returns both rows - expected
"SELECT * FROM Person WHERE id = 1" returns only the first row, when it was expected to return both rows.
Would you be able to help me understand what is going on here?
Thanks!
Edit
This is an active critical issue being tracked here: https://issues.apache.org/jira/browse/IGNITE-12068
The issue has been fixed by the community and will be available in the upcoming release shortly:
https://issues.apache.org/jira/browse/IGNITE-12068
Thanks for reporting.

SQL split column

I made an easy exemple to understand better what is my question.
So, i have 2 table and i have a selection based on inner join which is into a selection.
create table students(
id_student number,
name_student varchar2(15),
id_advisor number,
money number
);
insert into students values(1, 'Student_1', 1, 100);
insert into students values(2, 'Student_2', 8,-200);
insert into students values(4, 'Student_4', 7, 256);
insert into students values(5, 'Student_5', 3, -305);
----------------
create table advisors(
id_advisor number,
name_advisor varchar2(15)
);
insert into advisors values(1, 'advisor_1');
insert into advisors values(3, 'advisor_3');
insert into advisors values(5, 'advisor_5');
------------------------------------------
SELECT name_advisor, money as money_pozitive
FROM(
select name_student, name_advisor, money from students
inner join advisors on students.id_advisor = advisors.id_advisor)
WHERE money > 0
With this code i have the following result:
name_advisor | money_pozitive
------------------------------------
advisor_1 | 100
My question is, how I add an extra column named money_negative with of course negative values ? like this:
name_advisor | money_pozitive | money_negative
---------------------------------------------------------
advisor_1 | 100 | -305
Just use case:
select name_student, name_advisor,
(case when money > 0 then money end) as money_positive,
(case when money < 0 then money end) as money_negative
from students s inner join
advisors a
on s.id_advisor = a.id_advisor;
Notes:
A subquery is not necessary.
Use table aliases. These make a query easier to write and to read.
If you have multiple table names, it is a good habit to qualify all column names (i.e., use the table aliases for the column names).
And you don't need a where clause.

Insert query issue- with foreign key

I am new to SQL. Need a help from you guys :)
I am building a java appl and stuck in one of the scenario for insert with foreign key. Suppose I have 2 tables Employee_Type and Employee:
Table Employee_Type
| idType | position |
| -------- | -------------- |
| 1| Manager|
Table Employee
empId
EmpName
emp_type
FK (emp_type) reference Employee_type(idType)
Now values in Employee_Type
1,
Manager
I am inserting manually into Employee Table
INSERT INTO
employee (empId, name, emp_type)
VALUES
(
10, 'prashant', 1
)
Here in above insert I am inserting manually emp_type which is FK . My question, is there any way to insert FK value automatically using select like below example?
INSERT INTO
employee(empId, name, emp_type)
VALUES
(
10, 'prashant',
(
SELECT
idType
FROM
Employee_type,
employee
WHERE
employee.emp_type = employee_type.idtype
)
)
You don't specify your RDBMS and the syntax may therefore differ, but you should be able to restructure the statement to use literal values in an INSERT INTO ... SELECT format:
INSERT INTO employee (empId,name,emp_type)
SELECT
/* Build a SELECT statement which includes the static values as literals */
'10' AS empId,
'prashant' AS name,
/* and the idType column */
idType
FROM Employee_type,employee
WHERE employee.emp_type=employee_type.idtype
Note that without anything else in the WHERE clause, the above will insert one row into employee for every row matched by the SELECT statement.