Every derived table needs an alias - sql

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

Related

How to `insert into` with select using `group by` and having a unique constraint key in Oracle?

I am trying to insert into a table, 5 values from other 2 tables, but when I
try to insert them, Oracle sends me a message that I am violating the unique key restriction, what I do not understand why I have this problem if I am using the distinct clause in the select.
Thanks
My query:
insert into grados_salariales (Department_id,
Department_name,Sumatoria,Sal_minimo,Sal_maximo)
Select distinct departments.department_id,
departments.department_name,
sum(employees.salary),min(employees.salary),
max(employees.salary)
from employees,departments
group by salary,department_name,
departments.department_id;
This is the table that already exist and the unique key statement
create table
grados_salariales(
Department_id number,
Department_name varchar(50),
Sumatoria number,
Sal_minimo number,
Sal_maximo number);
Alter table grados_salariales
add constraint Department_id_pk
primary key ( Department_id);
I would expect inserting the department_id without problems.
This is your query:
select distinct d.department_id, d.department_name,
sum(e.salary), min(e.salary),
max(e.salary)
from employees e join
departments d
on e.department_id = d.department_id
group by e.salary, d.epartment_name, d.department_id;
The problem is the salary in the group by. If you want one row per department, then you can do:
select d.department_id, d.department_name,
sum(e.salary), min(e.salary),
max(e.salary)
from employees e join
departments d
on e.department_id = d.department_id
group by d.department_name, d.department_id;
Notes:
Never use commas in the FROM clause.
Always use proper, explicit, standard JOIN syntax.
Use table aliases so your queries are easier to write and read.
SELECT DISTINCT is almost never appropriate with GROUP BY.

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 */ -- ??
);

How do I use a value from the superquery inside a subquery?

I need to create a query that shows the last name of an employee, the employee id, the last name of the manager of that employee and the id of that manager.
The last name, id, and the manager id of that employee is easy to do because it is already in one row, which means that the following is sufficient:
SELECT last_name, employee_id, manager_id FROM employees WHERE manager_id IS NOT NULL;
But to get the last_name of the manager, you have to search the same table by the manager id you got from the employee. The solution I found is:
SELECT last_name,
employee_id,
(SELECT last_name FROM employees WHERE employee_id = manager_id),
manager_id
FROM employees
WHERE manager_id IS NOT NULL;
However, it seems that 'manager_id' doesn't work in the subquery (although I expected that) and the output is NULL (for the manager id, all the other columns do have values).
So my question is, how can I use the manager_id in the subquery?
Side note: The manager_id can be different for each employee, so using a constant value doesn't work.
What you need is a correlated subquery. I strongly, strongly recommend that you use table aliases and qualified column names in all your queries. However, these are particularly important with correlated subqueries.
You should write this query as:
SELECT e.last_name, e.employee_id,
(SELECT m.last_name
FROM employees m
WHERE m.employee_id = e.manager_id
),
e.manager_id
FROM employees e
WHERE e.manager_id IS NOT NULL;
The alias e is an abbreviation for the table reference to employees in the outer query. The alias m is an abbreviation for the table reference in the subquery.
Notice that all column references use the table alias. This makes the query unambiguous, can prevent unexpected errors, and makes the query much easier for you and others to understand.
You could use a self inner join ( a join with the same table)
SELECT
a.last_name
, a.employee_id
, b.last_name
, a.manager_id
FROM employees a
INNER JOIN employees b ON b.employee_id = a.manager_id;
The inner join work only if a.manager_id is not null so you can avoid this where condition
When you want to refer to a table in the outer query, you need to either use the full table name like table.field or, as in your case, if the outer query table is same as the subquery table, you need to assign an alias to the outer query table and use it in the subquery like this:
SELECT
last_name, employee_id,
(SELECT last_name FROM employees WHERE employee_id = emp_outer.manager_id),
manager_id
FROM employees emp_outer
WHERE manager_id IS NOT NULL;

Reason for The multi-part identifier could not be bound

error - The multi-part identifier "Grant.EmpID" could not be bound.
query -
select emp.EmpID,
COUNT(*) as CountRecords,
COUNT(GrantName) AS CountValues
From Employee as emp full join [Grant] as gr
on emp.EmpID = [Grant].EmpID
-- This is the cause for the error ! Change it to gr.EmpID
group by emp.EmpID
Why does this error occur ? Can't I call a person by real name and also by nickname ?
You're aliasing [Grant]. In other words, you're stating that from here on out, [Grant] will be referred to as gr.
Use the ALIAS in the GROUP BY clause, not the tableName.
SELECT emp.EmpID,
COUNT(*) as CountRecords,
COUNT(GrantName) AS CountValues
FROM Employee as emp
FULL JOIN [Grant] as gr
on emp.EmpID = gr.EmpID -- use the alias.
GROUP BY gr.EmpID -- use the alias.
here's the SQL Order of Operation
FROM clause
WHERE clause
GROUP BY clause
HAVING clause
SELECT clause
ORDER BY clause
No you can't because sql server is not a human. imagine we have an Employee table which references itself
select *
from Employee Emp, Employee Mng
where Emp.ManagerID = Mng.EmployeeID
Mng and Emp are two instances of Employee
so if I select
select * from Employee e, Employee
it will return all employee TWO times, because I am telling give me employees once under the name Employee once under the name e (alias)

Oracle SQL Exclude row from query results

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)