I have a practice that I should find the employees who earn more than average salary and works in the departments with employees whose last name contains the letter u
the select statement I have used was
SELECT employee_id,
last_name,
salary
FROM employees
WHERE salary > (SELECT AVG(salary)
FROM employees )
AND department_id IN(SELECT department_id
FROM employees
WHERE LOWER(last_name) LIKE '%u%')
Could anyone check this statement is suitable or not ?
thank you
That looks fine to me, assuming you mean the average salary across all departments in the database, and all employees (active or not) across all of time.
I would think you might be more interested in all active employees in this current financial year, for example.
You haven't provided the schema, so be careful to check for conditions like:
inactive departments
inactive / terminated employees
period you are interested in for comparing the salary
Your queries looks like it will work. You can rewrite it to remove all the sub-queries (that will require additional table/index scans) and just use analytic queries:
SELECT employee_id,
last_name,
salary
FROM (
SELECT employee_id,
last_name,
salary,
AVG( salary ) OVER () AS avg_salary,
COUNT( CASE WHEN LOWER( last_name ) LIKE '%u%' THEN 1 END )
OVER ( PARTITION BY department_id ) AS num_last_name_with_u
FROM employees
)
WHERE salary > avg_salary
AND num_last_name_with_u > 0;
db<>fiddle
My first Question are you getting the expected result ?
Let me break down your Query
SELECT department_id FROM employees WHERE LOWER(last_name)
Here you are selecting the department so it retrieve the department id, what is the need of selecting department Id when all you need employee_id with last name contains u so change it to employee_id instead of department_id
select avg(salary) over (partition by department_id order by employee_id)
So using partition by you must get the avg salary per department
SELECT employee_id,last_name,salary
FROM
employees
WHERE salary>(SELECT AVG(salary) OVER (PARTITION BY department_id)
FROM
employees )
AND employee_id IN
( SELECT employee_id
FROM
employees
WHERE LOWER(last_name) LIKE '%u%')
Let me know if you have any issues running it, any corrections to Query is appreciated
Related
SELECT
job_id, emp_name, salary, AVG(SALARY) AS AVERAGE_SALARY
FROM
employees
GROUP BY
emp_name, department_id;
I've tried this but this doesn't seem to work.
table: https://i.stack.imgur.com/3jB6x.png
output
: https://i.stack.imgur.com/q7R5T.png
my output: https://i.stack.imgur.com/EfxcZ.png
You seem to want a window average:
select
job_id, emp_name, salary,
avg(salary) over(partition by department_id) as avg_dpt_salary
from employees
This gives you one row per employee, along with the average salary of all employees of the same department.
You're not grouping by salary, yet you want the average. In this case the non-averaged salary column will return a value from a single record if it even works at all.
There's a couple ways to interpret what you're asking for.
Perhaps you want a report of each employee, but you want the average for the entire department. I believe you're grouping by emp_name because you want each employee's name in the output. You could do that this way in MariaDB:
select
job_id, emp_name, salary, (
select avg(salary) from employees b where b.department_id = a.department_id
) as avg_salary
from
employees a;
The other way to interpret what you're asking is if the same employee has multiple entries in the database and you only want one entry per employee name. The problem there, is what value are you expecting for the non-averaged salary? I've excluded that in the next example since it doesn't make sense to me.
select
emp_name, department_id, avg(salary) as avg_salary
from
employees a;
group by
emp_name, department_id;
I'm not certain either of these actually generate the data you're looking for, so please give me feedback on which of these is closer to what you're looking for and I'll edit the answer if necessary.
Can anyone explain why this query:
SELECT employee_id, last_name, salary
FROM employees
WHERE department_id IN (SELECT department_id
FROM employees
WHERE last_name LIKE '%u%'
)
AND salary > (SELECT AVG(salary)
FROM employees);
returns way less rows than this nested one:
SELECT employee_id, last_name, salary
FROM employees
WHERE department_id IN (SELECT department_id
FROM employees
WHERE last_name LIKE '%u%'
AND salary > (SELECT AVG(salary)
FROM employees);
)
The first returns all employees who meet the following conditions:
The employee is in a department has a "u" employee.
The employee has a salary larger than the average.
The second returns all employees who meet these conditions:
The employee is in a department that has a "u" employee who has a salary larger than the average.
The two are very different conditions. I wouldn't expect them to return the same result set.
Also, whenever you have more than one table in a query, you should use table aliases that are abbreviations of the table name and you should qualify all column names.
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.
I'm working on Oracle SQL and HR database, I'm trying to select maximum salary in department like this:
SELECT MAX(salary), department_id
FROM employees GROUP BY department_id;
It works fine, but I want to know >who< is earning the most, so I simply change query this way:
SELECT first_name, last_name, MAX(salary), department_id
FROM employees GROUP BY department_id;
And it's wrong. Could you help me, please?
The most efficient way to do this sort of analysis is generally to use analytic functions (window functions). Something like
SELECT first_name,
last_name,
salary,
department_id
FROM (SELECT e.*,
rank() over (partition by department_id
order by salary desc) rnk
FROM employees e)
WHERE rnk = 1
Depending on how you want to handle ties (if two people in the department are tied for the maximum salary, for example, do you want both people returned or do you want to return one of the two arbitrarily), you may want to use the row_number or dense_rank functions rather than rank.
Create a view:
create view max_salary as select max(salary), department_id from employees group by department_id
Then create a query:
select first_name, last_name, salary, department_id
from employees a, max_salary b
where a.department_id = b.department_id
and a.salary = b.salary
i want to display department_id's along with count,and count should be more than 5, and i want to have employees who are not hired in January.
i tried the below query
SELECT * FROM EMPLOYEES
WHERE DEPARTMENT_ID IN
(
SELECT DEPARTMENT_ID
FROM EMPLOYEES
GROUP BY DEPARTMENT_ID
HAVING COUNT(*)>5
)
AND HIRE_DATE NOT LIKE '%JAN%';
but here I didnt get count.I want count Also.
SELECT department_ID, count(employee_id) as '# of Employees' FROM EMPLOYEES
WHERE DEPARTMENT_ID IN
(
SELECT DEPARTMENT_ID
FROM EMPLOYEES
GROUP BY DEPARTMENT_ID
HAVING COUNT(*)>5
)
AND HIRE_DATE NOT LIKE '%JAN%'
group by department_ID;
This query returns the department_id and because I group by department_id, the count of employees that belong to each department will be returned
Output will look something like this
Department_Id | # of Employees
1 7
2 6
4 9
If you want the dept id and count of employees (where employee hire date is not in Jan) then something like the following should work. I say "something like the following" because I suspect the WHERE hire_date NOT LIKE '%JAN%' could be improved, but it would just depend on the format of that column.
SELECT
DEPARTMENT_ID,
COUNT(*)
FROM EMPLOYEES
WHERE HIRE_DATE NOT LIKE '%JAN%'
GROUP BY DEPARTMENT_ID
HAVING COUNT(*)>5;
If you also want to list the individual employees along with these departments, then something like this might work:
SELECT a.*, b.count(*)
FROM EMPLOYEES AS a
INNER JOIN (
SELECT
DEPARTMENT_ID,
COUNT(*)
FROM EMPLOYEES
WHERE HIRE_DATE NOT LIKE '%JAN%'
GROUP BY DEPARTMENT_ID
HAVING COUNT(*)>5) AS b
ON a.department_id = b.department_id
WHERE a.HIRE_DATE NOT LIKE '%JAN%';
Again, though, I think you can leverage your schema to improve the where clause on HIRE_DATE. A like/not-like clause is generally going to be pretty slow.
Select the count from your inner query and join to it:
SELECT E.*, DEPT_COUNT
FROM EMPLOYEES E
JOIN (
SELECT DEPARTMENT_ID, COUNT(*) DEPT_COUNT
FROM EMPLOYEES
GROUP BY DEPARTMENT_ID
HAVING COUNT(*) > 5
) DC ON E.DEPARTMENT_ID = DC.DEPARTMENT_ID
AND HIRE_DATE NOT LIKE '%JAN%'