SQL Assertion: The manager complex - sql

I have the following schema:
Emp(eid int, ename varchar(50), salary float, email varchar(80))
Works(eid int, did int, pct_time float)
Dept(did int, budget float, managerid int)
eid is for employee id.
did is for department id.
I've bolded primary keys.
managerid is a foreign key, as well as eid and did in Works.
Now, I'd like to add a "manager complex" assertion that ensure a manager will always have a higher salary than any employee that he/she manages.
Here's what I've been thinking:
CREATE ASSERTION managerComplex
CHECK
( NOT EXISTS (SELECT M.salary
FROM Dept D, Emp M
WHERE D.managerid = M.eid) <=
(SELECT E.salary
FROM Works W, Emp E
WHERE W.eid = E.eid) AND
D.did = W.did);
Is this even close to right?
Also, maybe I should have just made a couple CHECKs instead of an insertion?
I feel like multiple CHECKs is sloppier but would probably be easier to get right.
EDIT: The main reason I am asking is because I don't know if I'm correctly understanding NOT EXISTS

I think you are close. My edit to your trial:
CREATE ASSERTION managerComplex
CHECK
( NOT EXISTS ( SELECT *
FROM Dept D, Emp M
WHERE D.managerid = M.eid
AND M.salary < ANY
( SELECT E.salary
FROM Works W, Emp E
WHERE W.eid = E.eid
AND D.did = W.did
AND M.eid <> E.eid
)
)
)

For NOT EXISTS ( SELECT ...) to be true, the SELECT ... must return no results.
You want to create a SELECT statement that will return any employee with a salary higher than the manager listed for the department(s) they work for. See if you can write that query, and then place it inside the NOT EXISTS
I'm not entirely sure what you wrote is even valid SQL, but it's certainly not what you want. I have a potential answer for you, but as this was marked homework, I'd like to attempt to prod you in the right direction before providing a complete answer.
As OP said the homework has been turned in...
CREATE ASSERTION managerComplex
CHECK
(NOT EXISTS (SELECT E.salary
FROM Emp M, Dept D, Works W, Emp E
WHERE M.eid = D.managerid AND
W.did = D.did AND
E.eid = W.eid AND
E.salary > M.salary))
My NOT EXISTS has a query inside of it, that will return results if there are any employee salaries higher than their respective manager's salary. I look at all employees who work for a department, and only pull them into the result set if their salary is higher than the manager for that department's salary.
I'm not even sure your SQL is valid, as you have a (SELECT ...) <= (SELECT ...) AND .... Comparing result sets with a <= doesn't make any sense to me. It's possible that's just SQL I've never used, but... I've never used it.

Related

Self Join with Correlated Subquery

I need to find an employee with a salary greater than their Manager. For this, I used to following query and it works;
SELECT
e1.EmpID
,e1.EmpName
,e1.EmpSalary
,e1.ManagerID
FROM empsalary e
INNER JOIN empsalary e1 ON e.EmpID = e1.ManagerID
WHERE e1.EmpSalary > e.EmpSalary
But following one is not working. I want to know why it is not? Why it's result is null? What should be correct format?
SELECT *
FROM empsalary as e
WHERE e.empsalary=(
SELECT e1.empsalary
FROM empsalary as e1
WHERE e.EmpID = e1.ManagerID
AND e1.EmpSalary > e.EmpSalary)
Sample data and code here;
EmpID EmpName EmpSalary ManagerID
----------- ---------- -------------------- -----------
1 Neevan 100000 6
2 Mukesh 30000 6
3 Disha 50000 6
4 Martin 90000 6
5 Roy 170000 6
6 Anvesh 168000 NULL
CREATE TABLE empsalary
(
EmpID INT
,EmpName VARCHAR(10)
,EmpSalary BIGINT
,ManagerID INT
)
INSERT INTO empsalary
VALUES
(1,'Neevan',100000,6)
,(2,'Mukesh',30000,6)
,(3,'Disha',50000,6)
,(4,'Martin',90000,6)
,(5,'Roy',170000,6)
,(6,'Anvesh',168000,NULL)
Your correlations are all backwards. The right way is:
SELECT e.*
FROM empsalary e
WHERE e.empsalary > (SELECT m.empsalary
FROM empsalary m
WHERE m.EmpID = e.ManagerID
);
Notice that I have used the m table alias for the manager record. This helps to follow the logic.
There's no need for me to re-write how to do it, as Gordon has done a good enough job already, but I can explain why yours returned null...
For me to answer this, I needed to re-write yours slightly to help me to read it. It is essentially the same as yours. Also, similarly to how Gordon has done, I've substituted e for m and e1 for e. I've also called your tables tblSalaries, so that the table names aren't the same as the column name:
SELECT *
FROM tblSalaries as m
WHERE m.empsalary=(
SELECT e.empsalary
FROM tblSalaries as e
WHERE e.ManagerID = m.EmpID
AND e.EmpSalary > m e.EmpSalary)
If we work backwards and interpret the last part firstly:
...
(
SELECT e.empsalary
FROM tblSalaries as e
WHERE e.ManagerID = m.EmpID
AND e.EmpSalary > m.EmpSalary)
Firstly, WHERE m.ManagerID = e.EmpID is saying 'Find all employees in e who's manager ID is the same as the employee ID in m'. Considering there is only one managerID (6), then records 1-5 from e will all match the manager record, on m (6) on this.
Your next clause AND e.EmpSalary > m.EmpSalary is finding those who's salary is greater than the managers'. Therefore, you are left with record 5 (Roy) as you intended.
Now to return to your main query:
SELECT *
FROM tblSalaries as m
WHERE m.empsalary= (...tblSalaries as e...)
We have established that table e in the brackets has returned Roy, but we have also established that it only matches records in table m, where m is a manager. Ultimately, you are asking then to find where the manager's salary = Roy's salary; the answer, null.

SQL select with multiple different conditions

I am still learning SQL and can't find a proper way to find the following information:
I have created a table "employees" with the following columns:
'department', 'age', 'salary', 'bonus';
I am trying to design a query that will give me all employees that have someone the same age as them in another department and with a bonus superior to their salary.
(to be more precise, if someone in department 'SALES' has the same age as someone in department 'RESEARCH' and have a bonus that is superior to that guy in research's salary, then I would like to display both of them)
Is this possible to do in sql?
Thank you for your time,
-Tom
You can do this using exists. Because you care about the relationship in both direction, this is as simple as looking for people with the same age in the two departments but who do not have the same bonus:
select e.*
from employees e
where exists (select 1
from employees e2
where e2.department <> e.department and e2.age = e.age and
e2.bonus <> e.bonus
);
To get the pairs on the same row, use a self-join:
select e1.*, e2.*
from employees e1 join
employees e2
on e1.age = e2.age and e1.department <> e2.department and
e1.bonus > e2.bonus;

How to get result in following scenario

I have table
EMP(id int primary key, name varchar2(15), mgrID int).
Now this table contain all employees(including worker and manager) in company. mgrID column contain id of employee to whom they are reporting.
I want to list the name of worker who is not manager along with their name of manager.
What to do for such query.
I tried nested select query as follows:
select name, (select name from EMP where mgerID is NULL)
as Manager from EMP;
Will this query give proper result?
You could use a self-join:
SELECT e.name AS name, m.name AS manager_name
FROM emp e
JOIN emp m ON e.mgrid = m.id
Your query should fail because you sub-query is uncorrelated and will return multiple results if you have multiple top-level managers.
select name
, (select name from EMP b where b.ID = a.mgerID ) as Manager
from EMP a;
I think the self-join is the more canonical solution, but you should understand the correlated subquery as well as it has many application.

avg operation on repeated values

I have two tables, employee and certified.
The certified table contains the list of employees certified to drive a plane. One employee may be certified for many planes and vice versa. Not all employees are certified.
Each employee draws a salary. Only one salary, no matter how many certifications.
How do i find the average salary of those employees who are certified for at least one plane?
My problem is,
SELECT AVG(SALARY) FROM EMPLOYEE E, CERTIFIED C WHERE E.EID=C.EID;
This includes a salary twice if the employee is certified for two planes. So AVG(salary) gives a wrong value.
I'm a newbie, so my apologies if my question seems too basic. Help?
You want a semi-join here. You can implement it with an IN predicate:
SELECT AVG(SALARY)
FROM EMPLOYEE
WHERE EID IN (
SELECT EID
FROM CERTIFIED
)
;
or with an EXISTS predicate:
SELECT AVG(e.SALARY)
FROM EMPLOYEE AS e
WHERE EXISTS (
SELECT *
FROM CERTIFIED AS c
WHERE c.EID = e.EID
)
;
The IN predicate will not work as expected if CERTIFIED.EID is nullable and indeed has nulls, although I would assume it would be unusual to store a certification in that table not associated with any employee.
Alternatively you could use a proper join (and I would recommend you seriously consider switching to the proper join syntax too), only you would need to join to a set of distinct EID values derived from CERTIFIED, rather than directly to CERTIFIED. For the derived table you can use DISTINCT:
SELECT AVG(e.SALARY)
FROM EMPLOYEE AS e
INNER JOIN (
SELECT DISTINCT EID
FROM CERTIFIED
) AS c
ON e.EID = c.EID
;
or GROUP BY:
SELECT AVG(e.SALARY)
FROM EMPLOYEE AS e
INNER JOIN (
SELECT EID
FROM CERTIFIED
GROUP BY EID
) AS c
ON e.EID = c.EID
;
If an employee can have only one salary, I suggest
SELECT AVG(e.salary)
FROM employee e
WHERE e.id in (SELECT c.id FROM certified c)
This makes sure, that each eid is taken once. I still do not understand what the certified table is doing here. Do you need only those salaries of employees which are certified for flights?

Replacing a table value with the previous expression in oracle

Ok so I am having the following scenario
I have a table called employees and have to replaced the last names for some of the people there under the following conditions:
1-The last name must be replaced only to those employees who work on Oxford.
2-Their new last name is going to be the last name of the person that has their employee number -1 ( for instance employee#173 should have now employee#172 last name instead)
This is how I started the query:
select last_name,num_emp
from employees
where num_emp in(
select e.num_emp
from employees
join departments using(num_dept)
join office using(id_office)
where city='Oxford')
And I did a second query to make sure which values were going to replace which
Select last_name,num_emp
from employees
where num_emp in(
select (e.num_emp-1)
from employees
join departments using(num_dept)
join office using(id_office)
where city='Oxford')
Now I thought I could do this and make the code work... but it didn't:
update employees
set last_name=(select last_name
from employees
where num_emp in(
select (e.num_emp-1)
from employees
join departments using(num_dept)
join office using(id_office)
where city='Oxford')
Got error saying unexpected end of SQL command...
So I thought on making a change because I believed having too many values on the set was not the point and here is how I did it for last time:
update employees
set last_name=(select last_name
from employees)
where num_emp =(
select (e.num_emp-1)
from employees
join departments using(num_dept)
join office using(id_office)
where city='Oxford')
Got an error that says is missing right parenthesis, which I know it does not express whaat the issue is. I know I am missing something and part of the sintaxis is wrong as well as I may need to créate another table and add those values so that they get saved there and I can compare them with the original ones, but at this point I am totally blocked and can't discover what is the mistake I am doing. Please help me I'd really apprecciate it!
You are getting confused with what to update and what to update with in your statements.
Here is what to update. I use IN clauses to make it plain. An EXISTS clause would also be appropriate.
update employees
set last_name = ...
where num_dept in
(
select num_dept
from departments
where id_office in
(
select id_office
from office
where city = 'Oxford'
)
);
And here is what to update with:
set last_name =
(
select last_name
from employees prev_employee
where prev_employee.num_emp = employee.num_emp - 1
)
You should use the analytical lag function, then you also fill in gaps if for example employee 172 doesn't exist and you have to put the name of employee 171 in 173.
Your select should be something like this
with new_emp as
(select last_name,lag(last_name, 1, 0) over (order by num_emp) as new_last_name, num_emp
from employees)
select *
from new_emp
where num_emp in(
select e.num_emp
from employees e
join departments using(num_dept)
join office using(id_office)
where city='Oxford');
This select will give you the original last name, new last name, employee number.
Then afterwards your update should be this:
update employees x
set last_name = (with new_emp as
(select last_name,lag(last_name, 1, 0) over (order by num_emp) as new_last_name, num_emp
from employees)
select new_last_name
from new_emp ne
where ne.num_emp = x.num_emp)
where x.num_emp in(
select e.num_emp
from employees e
join departments using(num_dept)
join office using(id_office)
where city='Oxford');