Oracle SQL Exclude row from query results - sql

I have two tables in Oracle SQL:
PROJECT (PID, Pname, Budget, DID)
DIVISION (DID, Dname)
Bold = Primary key
Italic = Foreign key
I want to list the division that has more projects than the division marketing.
Here is my code:
select dname as "Division"
from division d, project p
where d.did = p.did
group by dname
having count(pid) >= all
(select count(p.pid)
from project p, division d
where p.did = d.did and d.dname = 'marketing')
I return the correct record but also the marketing record. How can I exclude the marketing record from the results?

Why don't you exclude the marketing record from your initial SQL by adding:
and d.dname != 'marketing'
To the first where clause.

You might gain efficiency with a common table expression (WITH clause) to aggregate the count by department, then you can query it ...
with cte as (
select dname,
count(*) projects
from project p,
division d
where p.did = d.did
group by dname)
select dname,
projects
from cte
where projects > (select projects
from cte
where dname = 'Marketing)

Related

How does the WITH clause know which table to work with?

I came across the following SQL statement in a book:
WITH dcount AS
(
SELECT deptno, COUNT(*) AS dcount
FROM employees
GROUP BY deptno
)
SELECT
e.lname EMP_lastname,
e.deptno e_dept,
d1.dcount edept_count,
m.lname manager_name,
m.deptno mdept,
d2.dcount mdept_count
FROM
employees e,
dcount d1,
employees m,
dcount d2
WHERE
e.deptno = d1.deptno
AND e.mgr = m.empno
AND m.deptno = d2.deptno
AND e.mgr = 7839
The purpose of the statement is to display an employees name, the department they work in, the number of coworkers in the department, the manager's name, and the number of people in the manager's department.
The part that confuses me is where it says d1.count and d2.count. How is it counting different things if the count is based on the same WITH statement?
The main query is joining the same CTE but is using different joining predicates each time:
It's joining d1 using e.deptno = d1.deptno.
It's joining d2 using m.deptno = d2.deptno.
Therefore each resulting row is picking a different row from the CTE.

Postgresql with Outer Join to Get Nulls Included

New to databases. I have a schema as follows:
Employee(**eid**, pname, age)
Dept(**dname**, num_managers) / Dept = department
Dept_Wing(**wingno**, wing_name, dname) / Dept_Wing = which wing of department
Wing_Sub_Division(**dname**, **wingno**, **sub_div_no**) / Wing_Sub_Division = which sub-division of the wing
On_Duty(**eid**, **dname**, **wingno**, sub_div_no)
I want to get the: dname, wingno, number on duty for that wing
for each and all wings than have less than 15% employees on duty of the total number of employees (so this includes dept_wings with 0 employees on duty).
I tried
select w.wingo, od.wingo
from dept_wing w left outer join on_duty od on on w.wingo=od.wingno
just to get all the wings which have even no employees on duty, but I'm not able to even return those wings! Any is guidance appreciated!
It seems you are working with composite keys; a department wing is identified by dname and wingno, so you have the pair in Dept_Wing, Wing_Sub_Division, and On_Duty. This makes this query quite simple.
Here is how to get the number of employees on duty per wing:
select dname, wingno, count(*)
from on_duty
group by dname, wingno
So the whole query is
select
wing.dname, wing.wingno, coalesce(duty.cnt, 0) as employees_on_duty
from dept_wing wing
left join
(
select dname, wingno, count(*) as cnt
from on_duty
group by dname, wingno
) duty on duty.dname = wing.dname and duty.wingno = wing.wingno
where coalesce(duty.cnt, 0) < 0.15 * (select count(*) from employee);

SQL Query - Unsure How to Fix Logical Error

Edit: Sorry! I am using Microsoft SQL Server.
For clarification, you can have a department named "x" with a list of jobs, a department named "y" with a different list of jobs, etc.
I also need to use >= ALL instead of TOP 1 or MAX because I need it to return more than one value if necessary (if job1 has 20 employees, job2 has 20 employees and they are both the biggest values, they should both return).
In my query I'm trying to find the most common jobTitle and the number of employees that work under this jobTitle, which is under the department 'Research and Development'. The query I've written consists of joins to be able to return the necessary data.
The problem I am having is with the WHERE statement. The HAVING COUNT(JobTitle) >= ALL is finding the biggest number of employees that work under a job, however the problem is that my WHERE statement is saying the Department must be 'Research and Development', but the job with the most amount of employees comes from a different department, and thus the output produces only the column names and nothing else.
I want to redo the query so that it returns the job with the largest amount of employees that comes from the Research and Development department.
I know this is probably pretty simple, I'm a noob :3 Thanks a lot for the help!
SELECT JobTitle, COUNT(JobTitle) AS JobTitleCount, Department
FROM HumanResources.Employee AS EMP JOIN
HumanResources.EmployeeDepartmentHistory AS HIST
ON EMP.BusinessEntityID = HIST.BusinessEntityID JOIN
HumanResources.Department AS DEPT
ON HIST.DepartmentID = DEPT.DepartmentID
WHERE Department = 'Research and Development'
GROUP BY JobTitle, Department
HAVING COUNT(JobTitle) >= ALL (
SELECT COUNT(JobTitle) FROM HumanResources.Employee
GROUP BY JobTitle
)
If you only want one row, then a typical method is:
SELECT JobTitle, COUNT(*) AS JobTitleCount
FROM HumanResources.Employee AS EMP JOIN
HumanResources.EmployeeDepartmentHistory AS HIST
ON EMP.BusinessEntityID = HIST.BusinessEntityID JOIN
HumanResources.Department AS DEPT
ON HIST.DepartmentID = DEPT.DepartmentID
WHERE Department = 'Research and Development'
GROUP BY JobTitle
ORDER BY COUNT(*) DESC
FETCH FIRST 1 ROW ONLY;
Although FETCH FIRST 1 ROW ONLY is the ANSI standard, some databases spell it LIMIT or even SELECT TOP (1).
Note that I removed DEPARTMENT both from the SELECT and the GROUP BY. It seems redundant.
And, if I had to guess, your query is going to overstate results because of the history table. If this is the case, ask another question, with sample data and desired results.
EDIT:
In SQL Server, I would recommend using window functions. To get the one top job title:
SELECT JobTitle, JobTitleCount
FROM (SELECT JobTitle, COUNT(*) AS JobTitleCount,
ROW_NUMBER() OVER (ORDER BY COUNT(*) DESC) as seqnum
FROM HumanResources.Employee AS EMP JOIN
HumanResources.EmployeeDepartmentHistory AS HIST
ON EMP.BusinessEntityID = HIST.BusinessEntityID JOIN
HumanResources.Department AS DEPT
ON HIST.DepartmentID = DEPT.DepartmentID
WHERE Department = 'Research and Development'
GROUP BY JobTitle
) j
WHERE seqnum = 1;
To get all such titles, when there are duplicates, use RANK() or DENSE_RANK() instead of ROW_NUMBER().
with employee_counts as (
select
hist.DepartmentID, emp.JobTitle, count(*) as cnt,
case when dept.Department = 'Research and Development' then 1 else 0 end as is_rd,
from HumanResources.Employee as emp
inner join HumanResources.EmployeeDepartmentHistory as hist
on hist.BusinessEntityID = emp.BusinessEntityID
inner join HumanResources.Department as dept
on dept.DepartmentID = hist.DepartmentID
group by
hist.DepartmentID, emp.JobTitle
)
select * from employee_counts
where is_rd = 1 and cnt = (
select max(cnt) from employee_counts
/* where is_rd = 1 */ -- ??
);

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?

Every derived table needs an alias

Here's my code, I need the Full names and salaries of faculty with lowest salary from every department. My subquery works on it's own, but I can't get the rest to work together.
SELECT CONCAT(FName,' ',LName) AS 'Faculty',DepartmentID,Salary
FROM Faculty,
(SELECT DISTINCT DepartmentID AS 'Department', MIN(Salary) AS 'MinSalary'
FROM Faculty GROUP BY DepartmentID)
WHERE Faculty.DepartmentID= 'Department' AND Salary= 'MinSalary'
ORDER BY DepartmentID
you need to pud alias on your subquery
SELECT CONCAT(FName,' ',LName) AS 'Faculty',DepartmentID,Salary
FROM Faculty,
(
SELECT DISTINCT DepartmentID AS 'Department',
MIN(Salary) AS 'MinSalary'
FROM Faculty GROUP BY DepartmentID
) xx -- <<< this is the alias. (Don't forget this)
WHERE Faculty.DepartmentID= 'Department' AND Salary= 'MinSalary'
ORDER BY DepartmentID
xx is the name of your subquery in the example above. (and I think the query is not giving you the results you want)
To modify your query for better performance, (Assuming you are using MYSQL because of CONCAT function)
SELECT CONCAT(a.FName,' ',a.LName) AS FacultyName,
a.DepartmentID,
a.Salary
FROM Faculty a
INNER JOIN
(
SELECT DepartmentID ,
MIN(Salary) AS MinSalary
FROM Faculty
GROUP BY DepartmentID
) xx -- <<< this is the alias.
ON a.DepartmentID = xx.DepartmentID AND
a.Salary = xx.MinSalary
-- WHERE .. (add extra condition here)
ORDER BY DepartmentID
SQLFiddle Demo
UPDATE 1
It might also be SQL Server 2012. (already supports CONCAT())
SELECT CONCAT([FName], ' ',[LName]) FullName, [DepartmentID], [Salary]
FROM
(
SELECT [FName], [LName], [DepartmentID], [Salary],
ROW_NUMBER() OVER (partition BY DepartmentID
ORDER BY Salary) rn
FROM Faculty
) x
WHERE x.rn = 1
SQLFiddle Demo
As the error message in the title hints, Standard SQL requires sub-queries in the FROM clause to have names. You should also learn to use the JOIN notation, not the comma-separated list of table names. You need to know the old (pre-SQL92) notation to recognize it; you should not use it yourself.
SELECT CONCAT(F.FName, ' ', F.LName) AS 'Faculty', F.DepartmentID, F.Salary
FROM Faculty AS F
JOIN (SELECT DISTINCT DepartmentID AS 'Department', MIN(Salary) AS 'MinSalary'
FROM Faculty
GROUP BY Department) AS D
ON F.DepartmentID = D.Department AND F.Salary = D.MinSalary
ORDER BY F.DepartmentID;
Better to use aliases for both table and the subselect query table:
SELECT CONCAT(F.FName,' ',F.LName) AS Faculty,F.DepartmentID, F.Salary
FROM Faculty F,
(SELECT DISTINCT DepartmentID AS Department, MIN(Salary) AS MinSalary
FROM Faculty GROUP BY DepartmentID) AS DEP_MIN_SALARY
WHERE F.DepartmentID= DEP_MIN_SALARY.Department
AND F.Salary= DEP_MIN_SALARY.MinSalary
ORDER BY FDepartmentID;
One observation: You are not retrieving any columns from the derived table. Hope that is intentional.
SELECT CONCAT(F.FName,' ',F.LName) AS Faculty, F.DepartmentID, F.Salary
FROM Faculty F
JOIN (
SELECT DepartmentID,
MIN(Salary) AS MinSalary
FROM Faculty
GROUP BY DepartmentID
) G
ON F.DepartmentID = G.DepartmentID AND F.Salary = G.MinSalary
ORDER BY F.DepartmentID
The problem with using single-quotes around alias names is that you fall into the trap evident in your query - your first WHERE condition is matching DepartmentID against the string literal 'Department'. This goes too for the 'MinSalary' column.
Of note also is the fact that GROUP BY does not need DISTINCT - because you are already getting distinct values of DepartmentID.
And to the first problem that brought you here - each derived table (aka sub-query) needs an alias, or something with which to identify the columns from it. In the example below, you can see that the first part of the ON clause compares two DepartmentID columns. The aliases F and G are imperative here to distinguish between the two, hence the requirement to alias each table.
Your query needs an alias for subquery
select
concat(F.FName, ' ', F.LName) as Faculty,
F.DepartmentID,
D.MinSalary
from faculty as F
inner join
(
select T.DepartmentID, min(T.Salary) as MinSalary
from Faculty as T
group by T.DepartmentID
) as D on D.DepartmentID = F.DepartmentID and D.MinSalary = F.Salary
order by F.DepartmentID
Your derived table needs a name which is what the error message is complaining about (see YouNeedToPutATableAliasHere below):
SELECT CONCAT(FName,' ',LName) AS Faculty,DepartmentID,Salary
FROM Faculty,
(SELECT DISTINCT DepartmentID AS Department, MIN(Salary) AS MinSalary
FROM Faculty GROUP BY DepartmentID) AS YouNeedToPutATableAliasHere
WHERE Faculty.DepartmentID= 'Department' AND Salary= MinSalary
ORDER BY DepartmentID
Note I've also removed some single-quotes where they didn't make sense to me. (Perhaps you meant double-quotes, square brackets, or backticks for column names, but single quotes probably won't work - you may need to do the same for 'Department'?)
You will also be creating a Cartesian join unless you specify conditions correlating the two tables. A better query for "faculty with the minimum salary in the dept 'Department'" might be (note I have removed the redundant GROUP BY):
SELECT CONCAT(FName,' ',LName) AS Faculty,F.DepartmentID,F.Salary
FROM Faculty F
INNER JOIN (
SELECT DepartmentID,
MIN(Salary) AS MinSalary
FROM Faculty
GROUP BY DepartmentID
) AS MinSal ON MinSal.DepartmentID = F.DepartmentID
AND F.Salary = MinSal.MinSalary
WHERE F.DepartmentID= 'Department'
ORDER BY F.DepartmentID
Or, since you are only selecting the one department 'Department', you could do:
SELECT CONCAT(FName,' ',LName) AS Faculty,F.DepartmentID,F.Salary
FROM Faculty F
WHERE F.DepartmentID= 'Department'
AND F.Salary = (
SELECT MIN(Salary)
FROM Faculty
WHERE DepartmentID = F.DepartmentID
)
Or even (which would also work for multiple departments but may be slower than a join):
SELECT CONCAT(FName,' ',LName) AS Faculty,F.DepartmentID,F.Salary
FROM Faculty F
WHERE F.DepartmentID= 'Department'
AND NOT EXISTS(
SELECT 1
FROM Faculty FF
WHERE FF.DepartmentID = F.DepartmentID
AND FF.Salary < F.Salary
)
In fact, in looking at your original query and in an effort to make this question complete while reading #RichardTheKiwi's comments, I will incorporate that interpretation into this answer. This assumes you are using MySQL (from the CONCAT function) and that you meant backticks instead of forward ticks for column names. It also assumes you wanted the minimum-salaried faculty for every department, not just the specified one as 'Department'. It just requires a small change from my first suggested query though - namely the removal of the WHERE clause:
SELECT CONCAT(FName,' ',LName) AS Faculty,F.DepartmentID,F.Salary
FROM Faculty F
INNER JOIN (
SELECT DepartmentID,
MIN(Salary) AS MinSalary
FROM Faculty
GROUP BY DepartmentID
) AS MinSal ON MinSal.DepartmentID = F.DepartmentID
AND F.Salary = MinSal.MinSalary
ORDER BY F.DepartmentID