multiple columns in ALL clause - sql

Is it possible to multiple columns with ALL clause.
please provide some examples
i tried this correlated query on HR shema.
SELECT *
FROM job_history j
WHERE ( employee_id, job_id ) = ALL (SELECT employee_id, job_id
FROM employees e
WHERE e.employee_id = j.employee_id
AND e.job_id = j.job_id)
it does not consider about inner query and where conditions.Instead of that it returns job_history table.
Lets assume the tables
job_history
employee_id job_id
----------- ----------
102 IT_PROG,
101 AC_ACCOUNT
employees
employee_id job_id
----------- ----------
102 IT_PROG,
So according to my knowledge the query should output only 102 IT_PROG,
But its output is
employee_id job_id
----------- ----------
102 IT_PROG,
101 AC_ACCOUNT
thats the problem..

Oracle optimizer changes ANY, ALL and SOME operators during run time with EXISTS / NOT EXISTS and shifts the filtering criteria inside the correlated query. I checked one example on one of the site...
SELECT e1.empno, e1.sal
FROM emp e1
WHERE e1.sal > ALL (SELECT e2.sal
FROM emp e2
WHERE e2.deptno = 20);
is interpreted by the compiler as
SELECT e1.empno, e1.sal
FROM emp e1
WHERE NOT EXISTS (SELECT e2.sal
FROM emp e2
WHERE e2.deptno = 20
AND e1.sal <= e2.sal)
Still not very sure but may be the query that you have provided is being interpreted as following:-
Original Query:
SELECT *
FROM job_history j
WHERE ( employee_id, job_id ) = ALL (SELECT employee_id, job_id
FROM employees e
WHERE e.employee_id = j.employee_id
AND e.job_id = j.job_id)
Oracle Interpretation(just a guess work based on > ANY example)
SELECT *
FROM job_history j
WHERE NOT EXISTS (SELECT employee_id, job_id
FROM employees e
WHERE e.employee_id = j.employee_id
AND e.job_id = j.job_id
and e.employee_id != j.employee_id
AND e.job_id != j.job_id)
But still there is long way to discover the actual interpretation by ALL, ANY and SOME.
Anyways, there clauses were used long before I guess before 8i and so, now a days they are considered as obsolete, although Oracle supports them but they have been taken over by analitical functions and other techniques of querying data.

It must be like this:
SELECT *
FROM job_history j
WHERE ( employee_id, job_id ) =ANY (SELECT employee_id, job_id
FROM employees e
WHERE e.employee_id = j.employee_id
AND e.job_id = j.job_id)
Opposite of =ANY is <>ALL.
x =ANY (SELECT ...) is equal to x IN (SELECT ...),
x <>ALL (SELECT ...) is equal to x NOT IN (SELECT ...)
Then you may try this one:
SELECT *
FROM job_history j
JOIN employees e USING (employee_id, job_id)

Related

I need to display all employees which were hired before their managers. Can somebody help me?

I need to run an SQL query which will display all the employees who were hired before their managers and they have the minimum salary for his function.
SELECT ENAME,JOB, SALAR FROM EMP
WHERE JOB = 'SALESMAN'
OR JOB = 'CLERK'
OR JOB = 'ANALYST'
Use window functions:
select . . .
from (select e.*, m.hiredate as mgr_hiredate,
min(salar) over (partition by job) as min_salar
from emp e left join
emp em
on e.mgr = m.empno
) em
where hiredate < mgr_hiredate and salar = min_salar;
You can join EMP to itself to add details about each employee's manager (we're interested in his HIREDATE). Later in WHERE section you can check that manager's HIREDATE is later than corresponding employee's.
You can also pre-calculate all minimal salaries for each job, then join this information to each employee and later you can check that employee's salary is equal to the minimal.
SELECT EMPNO, ENAME
FROM EMP e
INNER JOIN EMP m ON e.MGR = m.EMPNO
INNER JOIN (
SELECT JOB, MIN(SALAR) as MINSALAR
FROM EMP
GROUP BY JOB
) s ON s.JOB = e.JOB
WHERE m.HIREDATE > e.HIREDATE AND e.SALAR = s.MINSALAR

How to get result if exactly one match inner join

How can I write a query to join two tables and return result if exactly one match in there. I have to discard results if zero match and more than one match.
All I am looking for is to extend the INNER JOIN. Let me just get to the point. I have two tables Dept & Emp. One Dept can have multiple Emp's & not the other way around.
Table Dept
Table Emp
I need to JOIN it on Dept_id
Expected Results
You can join with a not exists condition:
select d.*, e.emp_id, e.emp_name
from dept d
inner join emp e
on d.dept_id = e.dept_id
and not exists (
select 1
from emp e1
where e1.dept_id = d.dept_id and e1.emp_id != e.emp_id
)
One alternative to existing solutions can be one using analytics (window functions),
instead of joining twice:
select dept_id, dept_name, emp_id, emp_name
from
(
SELECT
d.Dept_id, d.Dept_name, e.Emp_id, e.Emp_Name,
count(*) over (partition by d.dept_id) cnt1
FROM d
INNER JOIN e
ON d.Dept_id = e.Dept_id
) where cnt = 1;
You could use a subquery for group by dept_id haing count = 1
select t.dept_id, dept.dept_name, emp.Emp_name
from (
select dept_id
from emp
group by dept_id
having count(*) = 1
) t
INNER JOIN dept on t.dept_id = dept.dept_id
INNER JOIN emp ON t.dept_id = emp.dept_id
You can phrase this as an aggregation query in Oracle:
select d.dept_id, d.dept_name,
max(e.emp_id) as emp_id,
max(e.emp_name) as emp_name
from dept d inner join
emp e
using (dept_id)
group by d.dept_id, d.dept_name
having count(*) = 1;
This works because if there is only one match, then max() returns the value from the one row.
Also, try below query;
SELECT a.depid dept_id,dept_name,emp_id,emp_name
FROM
(SELECT case WHEN count(*)=1 THEN dept_id END depid FROM emp GROUP BY dept_id) a INNER JOIN emp ON depid=dept_id
INNER JOIN dept b ON a.depid = b.dept_id
WHERE depid IS NOT NULL
Another way would be
select d.dept_id, d.dept_name, e.emp_name
from emp e
join dept d on d.dept_id = e.dept_id
where e.dept_id in
( select dept_id from emp group by dept_id having count(*) = 1 )

Need to retrieve columns from 3 tables

There are three tables: dept, emp, sal . You can find their structure and data in the images.
I need to extract the list of employees who have location as pune and have max salary in their department. Since there are five departments, the final output will contain five rows and columns of emp_id, dept, dept_id, salary.
I've tried...
select e.emp_id, dept,e.dept_id, max(sal) as 'highest salary'
from sal s,emp e,dept d
where e.emp_id = s.emp_id and d.dept_id = e.dept_id and loc ='Pune'
group by e.emp_id,e.dept_id,dept
order by e.dept_id
I would use apply :
select t.emp_id, d.dept, d.dept_id, t.sal
from dept d
cross apply ( select top 1 e.emp_id, s.sal as sal
from emp e
inner join sal s on s.emp_id = e.emp_id
where d.dept_id = e.dept_id and e.loc = 'Pune'
order by s.sal desc
) t;
Unless you have window functions (that depends on whether you're using MySQL, Oracle, SQLite, etc) you will need to do it in two steps.
Find the highest salary per department (for employees in 'Pune'), then another set of joins to find out who those people are.
SELECT
dep.dept,
dep.dept_id,
emp.emp_id,
sal.sal
FROM
emp
INNER JOIN
sal
ON sal.emp_id = emp.emp_id
INNER JOIN
(
SELECT
emp.dept_id,
MAX(sal.sal) AS max_sal
FROM
emp
INNER JOIN
sal
ON sal.emp_id = emp.emp_id
WHERE
emp.loc = 'Pune'
)
dep_sal
ON dep_sal.dept_id = emp.dept_id
AND dep_sal.max_sal = sal.sal
INNER JOIN
dep
ON dep.dept_id = emp.dept_id
WHERE
emp.loc = 'Pune'
ORDER BY
dep.dept,
emp.emp_id
EDIT: With SQL Server 2008 on-wards it's a bit easier...
WITH
emp_sal_ranked AS
(
SELECT
emp.dept_id,
emp.emp_id,
sal.sal,
RANK(sal.sal) OVER (PARTITION BY emp.dept_id
ORDER BY sal.sal
)
AS rank_sal
FROM
emp
INNER JOIN
sal
ON sal.emp_id = emp.emp_id
WHERE
emp.loc = 'Pune'
)
SELECT
dep.dept,
dep.dept_id,
emp_sal_ranked.emp_id,
emp_sal_ranked.sal
FROM
emp_sal_ranked
INNER JOIN
dept
ON dept.dept_id = emp_sal_ranked.dept_id
WHERE
emp_sal_ranked.rank_sal = 1
ORDER BY
dep.dept,
emp_sal_ranked.emp_id

small tricky query on self join

I have a table EMP with columns as below:
create table emp(
empno number(4,0),
ename varchar2(10),
job varchar2(9),
mgr_id number(4,0),
sal number(7,2),
deptno number(2,0));
I want to list all employees' names along with their manager names, including those who do not have a manager. For those employees, their manager's name should be displayed as 'BOSS'.
The following query should work:
select e.ename, (case when m.ename is null then 'BOSS' else m.ename end) as mgrName
from emp e
left join emp m on m.empno = e.mgr_id
To my mind, the better solution is proposed by Charanjith.
In Oracle, we could even use NVL function instead of "case when" in order to replace null value by something. The result should be the same.
select e.ename empName, NVL(m.ename, 'BOSS') mgrName from emp e
left join emp m on m.empno = e.mgr_id
Moreover, we could see another solution : using inner join to filter on emp when a manager exists. Then union for all employees who don't have any manager.
select e.ename empName, m.ename mgrName from emp e inner join emp m on e.mgr_id = m.empno
union
select e.ename empName, 'BOSS' mgrName from emp e where not exists (select 1 from emp m where e.mgr_id = m.empno)
This work fine in oracle:
SELECT e.ename,
nvl(m.ename, 'BOSS')mgr
FROM emp a
LEFT JOIN emp b
ON m.empno = e.mgr_id;

Raising salary by 15% using SQL only?

I am trying to modify the SCOTT schema by raising employees salaries by 15%. If the resulting salary exceeds his or her highest possible salary, HISAL, in SALGRADE we just use HISAL, my code is:
select
coalesce((
select sal*1.15
from emp , salgrade
where
sal*1.15<=hisal
) ,
(select hisal from emp ,salgrade where sal*1.15>hisal )) raised_sal
from emp ;
The inner select returns multiple rows
Does anyone have suggestions or different code?
If we break-down what you're trying to do:
coalesce gives the first value, unless that is null then the second etc. You haven't specified any join condition, so you're doing a cartesian join between emp and salgrade. This will return the number of rows in emp multiplied by the number of rows in salgrade; subject to your join condition.
The second part of your query does exactly the same in the opposite direction.
Assuming a table_structure of:
create table emp (
emp_id number
, salary number -- current salary
, salgrade_id number -- current salary grade
, dept_id -- current department
);
create table salgrade (
, salgrade_id number
, hisal number -- max salary for that grade
);
You want to be doing something like this:
select least( e.salary * 1.15, s.hisal )
from emp e
join salgrade s
on e.salgrade_id = s.salgrade_id
The least function is similar to min; in that it will take the minimum value. However, it works over multiple columns on the same row, rather than multiple rows over the same column.
By taking the least of hisal and salary * 1.15 we ensure that if the salary increase is higher than hisal then hisal is taken instead.
In this case join means that for every emp_id we're taking the hisal for the appropriate salary grade. I would highly recommend reading up on SQL joins - this link was 7th on Google, which was a bit of a surprise ( Jeff seems to be everywhere ), but it's quite good and has many more links.
To change the query to enable you to do it for all employees earning over 100,000 ( the rich get richer ) it would look something like this:
select least( e.salary * 1.15, s.hisal )
from emp e
join salgrade s
on e.salgrade_id = s.salgrade_id
where e.salary > 100000
Or to complicate matters if you wanted to do it for only IT peoples ( why not ), you could extend it to something like this:
select least( e.salary * 1.15, s.hisal )
from emp e
join salgrade s
on e.salgrade_id = s.salgrade_id
join dept d
on e.dept_id = d.dept_id
where d.dept = 'IT'
These joins by being inner joins as opposed to outer joins ( normally left outer joins - see the link ) imply that every employee has a grade and a department.
You should join emp with salgrade through a non equijoin:
select sal*1.15
from
emp
inner join
salgrade sg
on emp.sal BETWEEN sg.losal AND sg.hisal <-- here!
where
sal*1.15<=hisal
More info: Introduction to Basic SQL, Part 3 - Complex Joins and Sub-queries:
"we joined using the BETWEEN operator. The paybands in the SALGRADE
table use a range of salary to designate a grade."
EDIT
To calculate the employee grade you should look for employee salary and look into salgrade for a salary range that contains employee salary.
This is the salgrade data:
...
INSERT INTO SALGRADE VALUES (2,1201,1400);
INSERT INTO SALGRADE VALUES (3,1401,2000);
...
If employee salary is 1495 that means that his grade is 2.
If you increase 1395 in 15% then this is out of range of your salrange and, as you explain, you should set salary at 1400 that is the hisal for range 2.
Your query:
select
coalesce((
select sal*1.15
from emp e2
inner join
salgrade sg
on e2.sal BETWEEN sg.losal AND sg.hisal
where
e2.EMPNO = e1.EMPNO and
sal*1.15<=hisal
) ,
(select hisal from emp e2 inner join salgrade sg
on e2.sal BETWEEN sg.losal AND sg.hisal
where e2.EMPNO = e1.EMPNO
)) raised_sal
from emp e1 ;
Without subqueries:
select
case
when sal*1.15 < hisal then sal*1.15
when sal*1.15 >= hisal then hisal
end
raised_sal
from
emp e1
inner join
salgrade sg
on e1.sal BETWEEN sg.losal AND sg.hisal
Disclaimer, not tested.
I think you missed only join condition ...
in both the query you can give join condition, if any (like emp.employee_name = salgrade.employee_name)
and have to give inner query condition also...
like
select
coalesce((
select sal*1.15
from emp e2, salgrade
where
sal*1.15<=hisal
and e2.employee_name = e1.employee_name
) ,
(select hisal from emp e3,salgrade where sal*1.15>hisal
and e3.employee_name = e1.emplyee_name
)) raised_sal
from emp e1;