ID value is lost after performing MIN then GROUP BY. Why? - sql

To simplify, if I had a table called 'employee':
id INT NOT NULL PRIMARY KEY,
salary FLOAT,
department VARCHAR(255)
I want to perform and query where I retrieve the minimum salary in each department.
So my query is something like this:
SELECT employee.ID, MIN(employee.salary), employee.department
FROM employee
GROUP BY employee.department
But regardless of which records are found. The ID values in the result set are renamed to 1,2,3.... up to however many records (departments) exist in the result set.
How can I maintain the actual ID's of the employees after performing the AGGREGATE function and GROUP BY?

You can't. Think about it, If a Department has 20 employees, and for that department, there are three employees that have the same minimum salary, which EmployeeId do you want the the query output to display? if it was guaranteed that there was only one employee in each dept with that lowest salary, then it can be done by selecting the specific employee records where the salary is the minimum value for each Department:
Select EmployeeID
From Employee e
Where Salary =
(Select Min(Salary) From EMployee
Where DepartmentId = e.DepartmentId)
but this will return multiple records per department when more than one employee has that min salary level.

I would guess you're using MySQL or SQLite, because your query is ambiguous and isn't allowed by standard SQL or most brands of RDBMS. MySQL and SQLite are more permissive, so it's your responsibility to resolve the ambiguity.
Here's my usual fix:
SELECT e1.ID, e1.salary, e1.department
FROM employee e1
LEFT OUTER JOIN employee e2 ON (e1.department = e2.department
AND e1.salary > e2.salary)
WHERE e2.department IS NULL;
Here's another solution that gives the same result:
SELECT e1.ID, e1.salary, e1.department
FROM employee e1
JOIN (SELECT e2.department, MIN(e2.salary) AS min_salary
FROM employee e2 GROUP BY e2.department) d
ON (e1.salary = d.min_salary);
Both of these give multiple rows per department if there are multiple employees in the department with identical minimal salaries. You need to decide how to resolve that case, because it's not clear from your problem description.

Your script is invalid:
SELECT employee.ID, MIN(employee.salary), employee.department
FROM employee
GROUP BY employee.department
Instead, look at this:
SELECT MIN(employee.salary), employee.department
FROM employee
GROUP BY employee.department
If you need the employee id as well, then you need to use a subquery.

This will do the trick:
SELECT employee.department, MIN(employee.salary), employee.ID
FROM employee
GROUP BY 1

In modern SQL Server releases (and other reasonably powerful and modern SQL engines), SQL "Window functions" are probably the best alternatives (to be preferred over subqueries and self-joins) to do what you desire:
SELECT ID, salary, department
FROM employee
WHERE 1 = ROW_NUMBER() OVER(PARTITION BY department ORDER BY salary ASC)
This works when, if multiple employees have the same (department-minimal) salary, you want just a "random-ish" one of them (you can add criteria to the ORDER BY if you want one picked by some specific criteria); look into RANK, instead of ROW_NUMBER, if you want all.

Related

Using MAX() without grouping by

Im using Oracle-Apex
I have a table with names and salaries. I want to get the name with the highest salary using MAX(salary).
So the query is like this:
SELECT NAME FROM EMPLOYEE
GROUP BY NAME
HAVING MAX(SALARY) = SALARY;
This doesnt work, error ORA-00979: not a GROUP BY expression, appears. So i use this to stop that error:
SELECT NAME FROM EMPLOYEE
GROUP BY NAME, SALARY
HAVING MAX(SALARY) = SALARY;
And it groups every different salary in one row and returns the max salary in each row, since every salary is different, it returns every row.
How do i group everything in one single big group without modyfing the tables? I mean i want this to work:
SELECT NAME FROM EMPLOYEE
WHERE MAX(SALARY) = SALARY;
But with having. Its simple really, but i cant find the way.
use subquery
SELECT NAME FROM EMPLOYEE
where salary = (select max( salary) from EMPLOYEE)
You need a WHERE clause and compare the salary with the max salary of the table:
SELECT NAME FROM EMPLOYEE
WHERE SALARY = (SELECT MAX(SALARY) FROM EMPLOYEE);
You can get the name of person who has max salary using following nested query.
select NAME
from EMPLOYEE
where SALARY= ( select max(SALARY)
from EMPLOYEE )

Not Sure How NOT EXISTS works

This is just homework currently, but I am having trouble writing a SELECT statement for this question:
Produce a result set showing department name, department description,
employee first name, and employee last name for ALL departments,
including those for whom no employee has been assigned. Sort by
department name.
I believe I have the SELECT, FROM, WHERE, and ORDER BY down, but the NOT EXISTS is where I am struggling.
Here is the table:
SELECT deptName, deptDesc, empFirstName, empLastName
FROM department, employee
WHERE department.deptID=employee.deptID
AND NOT EXISTS (
SELECT deptName, deptDesc
FROM
ORDER BY deptName ;
At this point I am just trying to include those for whom no employee has been assigned.
I believe you are looking for a LEFT JOIN instead: https://www.w3schools.com/sql/sql_join_left.asp
You want to include everything from department, and also anything that matches from employee, but not just the intersection of the two.
NOT EXISTS will just return a boolean true or false if that inner query produces results with at least one row. I don't think that's what you want.
SELECT deptName, deptDesc, empFirstName, empLastName
FROM department
LEFT JOIN employee on department.deptID=employee.deptID
ORDER BY deptName;

Employee that has a higher salary than the AVERAGE of his department - optimized

We have only a table named EMPLOYEESALARY in our database with the 3 following columns:
Employee_ID, Employee_Salary, Department_ID
Now I have to SELECT every employee that has a higher salary than the AVERAGE of his department. How do I do that?
I know this is a repeat question but the best solution I found everywere was:
SELECT * from employee join (SELECT AVG(employee_salary) as sal, department_ID
FROM employee GROUP BY Department_ID) as t1
ON employee.department_ID = t1.department_ID
where employee.employee_salary > t1.sal
Can we optimize it further and do it without a subquery?
Reference:
SELECT every employee that has a higher salary than the AVERAGE of his department
Employees with higher salary than their department average?
Find Schema here, to test: SQL Fiddle
Can we do it without a subquery?
Not that I can think of. Had the condition been >= then the following would have worked
SELECT TOP 1 WITH TIES *
FROM employee
ORDER BY CASE
WHEN employee_salary >= AVG(employee_salary)
OVER (
PARTITION BY Department_ID) THEN 0
ELSE 1
END
But this is not an optimisation and it won't work correctly for the > condition if no employee has a salary greater than the average anyway (i.e. all employees in a department had the same salary)
Can we optimize it further?
You could shorten the syntax a bit with
WITH T AS
(
SELECT *,
AVG(employee_salary) OVER (PARTITION BY Department_ID) AS sal
FROM employee
)
SELECT *
FROM T
WHERE employee_salary > sal
but it still has to do much the same work.
Assuming suitable indexes on the base table already exist then the only way of avoiding some more of that work at SELECT time would be to pre-calculate the grouped SUM and COUNT_BIG in an indexed view grouped by Department_ID (to allow the average to be cheaply derived) .
A more optimal form is likely to be:
select e.*
from (select e.*, avg(employee_salary) over (partition by department_id) as avgs
from employee e
) e
where employee_salary > avgs;
This (as well as other versions) can use an index on employee(department_id, employee_salary). The final where probably should not use an index, because it is selecting lots of rows.

Retrieve the names of employees who is making least in their departments

I have two tables
EMPLOYEE (Fname, Lname, Ssn, Salary, Dno)
DEPARTMENT (Dname, Dno, Location)
I want to list the names of all employees making the least in their department
I have come up with this
select min(E.Salary) from EMPLOYEE E group by E.Dno;
but how do I join the EMPLOYEE table with it and display the 'Fname' and 'Lname';
Analytic functions would be best but this would also work:
select *
from employee e
where salary = (select min(x.salary) from employee x where x.dno = e.dno)
Use Analytic function, ROW_NUMBER() OVER( PARTITION BY DNO ORDER BY SALARY) as RN. So, WHERE RN = 1 will give you the employee with least salary in each department.
Remember, if there are two employees with same salary, then you need to use DENSE_RANK to avoid similar rank.
Note : This answer is for Oracle.
A simple way to do it is to check that no one with a lower salary exists in the same department;
SELECT e1.*
FROM employee e1
WHERE NOT EXISTS(
SELECT 1 FROM employee e2 WHERE e1.dno = e2.dno AND e1.salary > e2.salary
);
An SQLfiddle to test with.

Find the name of the employees, whose names are same but salary are different

I have an 'employee' table with
create table employee
(
e_number int,
e_name varchar(20),
salary money,
hire_date date
)
Now I want to display only the name of the employees who have the same name but different salary.
I tried select e_name,count(*) from employee group by e_name having count(*)>1;
but cannot combine it with "the same salary" section. Any help?
This assumes that you want the names of both of the people listed:
SELECT e1.e_name
FROM employee e1, employee e2
WHERE e1.e_name = e2.e_name
AND e1.salary <> e2.salary;
If you only want each name listed once, you would use a SELECT DISTINCT instead of the SELECT.
If your goal is to express this in the having clause:
Select name
from employee
group by name
having
count(*) > 1
and min(salary) != max(salary)
order by name
SELECT employee1.e_name, employee1.Salary, Employee2.Salary
FROM Employee employee1
JOIN Employee employee2
on employee1.name = employee2.name
AND Employee1.Salary <> Employee2.Salary
AND Employee1.E_Number <> employee2.E_Number
Basically get every employee, join it to every other employee via name, where the employee number is different (so don't join to yourself) and salary is different.
You probably don't need to check that employee number is different because 1 employee can only have 1 salary in your table design
Use a join, but importantly use a greater-than comparison, rather than a not-equals, to avoid duplicates:
SELECT e1.e_name as name1, e2.e_name as name2
FROM employee e1
JOIN employee e2 ON e1.e_name = e2.e_name
AND e1.salary > e2.salary;
Note also that the ON condition contains the salary comparison. It is a common misconception that the join on condition may only contain key-related comparisons. Doing this can have significant performance benefits, especially when further joins are made, because the ON condition is executed as the rows are joined - which discards non-matches immediately, whereas WHERE conditions are executed as a filter on the entire result set of the joins.
You just want the count of distinct salaries, not the count of all records.
select e_name,count(distinct salary)
from employee
group by e_name
having count(distinct salary)>1
(Drop the count in the select if unneeded - included since it was in your example)
First filter salary not double (not in), then grouping by e_name having count > 1
SELECT A.e_name
FROM employee A
WHERE A.salary NOT IN (SELECT salary FROM employee WHERE id != A.id)
GROUP BY A.e_name
HAVING COUNT(A.e_name) > 1