How to get Details of all Department(dept_id, name, dept_manager and total_employees) from tables(Department, Employee)? - sql

Here are the details of the tables:
Employee :
emp_ID | Primary Key
emp_name | Varchar
emp_email | varchar <br>
emp_dept_id | Foreign Key
Departments :
dept_ID | Primary Key
dept_name | Varchar
emp_id | Foreign Key
Manager details are already there in the employee table.
I am using Oracle Database.
Employee:
emp_ID emp_name emp_email emp_dept_id
1 Cyrus abc#xyz.com 10
2 Andrew xyz#abc.com 20
3 Mark xyz#abc.com 10
4 Tony xyz#abc.com 10
5 Elvis xyz#abc.com 20
6 Rock xyz#abc.com 10
7 George xyz#abc.com 20
8 Mary xyz#abc.com 10
9 Thomas xyz#abc.com 20
10 Martin xyz#abc.com 10
Depqartments:
dept_id dept_name emp_id
10 Accounts 4
20 Development 9
These are the data in the tables. In Department table, emp_id(Foreign key) indicates the head/manager of the department .

You can try following query:
SELECT
D.DEPT_ID,
D.DEPT_NAME,
D.EMP_ID AS MANAGER_ID,
E.EMP_NAME AS MANAGER_NAME,
E.EMP_EMAIL AS MANAGER_EMAIL,
E.CNT AS "number of employees"
FROM
DEPARTMENT D
JOIN (
SELECT
EMP_ID,
EMP_NAME,
EMP_EMAIL,
EMP_DEPT_ID,
COUNT(1) OVER(
PARTITION BY EMP_DEPT_ID
) AS CNT
FROM
EMPLOYEE
) E ON ( E.DEPT_ID = D.EMP_DEPT_ID
AND E.EMP_ID = D.EMP_ID );
Cheers!!

This is pretty straightforward. You want to get the department details (1) and also include the employees associated with that department (2).
Get the department details:
SELECT <dept_col1>, <dept_col2>, ...
FROM Department
Get a summary of employees per department:
SELECT dept_ID, <aggregation> AS MyAgg
FROM department
GROUP BY dept_ID
Here you replace <aggregation> with whatever you're trying to find. In your case it would be COUNT(*).
Combine the two results. You want to join the data from (2) to (1). How are these two tables related? Here you write a LEFT JOIN to connect them on the PK/FK relationship:
SELECT
dept.<col1>, dept.<col2>, ...,
COALESCE(emp.MyAgg,0) AS MyAgg
FROM Department dept
LEFT JOIN (
SELECT dept_ID, <aggregation> AS MyAgg
FROM department
GROUP BY dept_ID
) emp ON dept.<FK> = emp.<PK>
Get the department manager's info by adding another LEFT JOIN:
SELECT
dept.<col1>, dept.<col2>, ...,
mgr.<col1>,
COALESCE(emp.MyAgg,0) AS MyAgg -- In case there are 0 employees
FROM Department dept
LEFT JOIN employee mgr ON dept.<FK> = mgr.<PK>
LEFT JOIN (
SELECT dept_ID, <aggregation> AS MyAgg
FROM department
GROUP BY dept_ID
) emp ON dept.<FK> = emp.<PK>
Give it a try and see how far you get. If you get stuck let me know.

Related

Removing duplicates by adding them up [SQL]

I have a query like this:
select employee_id, salary
from salary
left join employee on salary.employee_id=employee.id_employee;
It returns me these results
EMPLOYEE ID | SALARY
-------------|-------
1 | 50
2 | 50
3 | 50
1 | 30
How do I remove duplicates by adding them up, like this:
EMPLOYEE ID | SALARY
------------|--------
1 | 80
2 | 50
3 | 50
There is no reason for a left join from salary to employee. Presumably, every employee_id in salary refers to a valid employee. So, this should do what you want:
select s.employee_id, sum(s.salary) as salary
from salary s
group by s.employee_id;
If you want all employees, even those who are not in the salary table, then an outer join is appropriate -- but employee should be first:
select e.id_employee, sum(s.salary) as salary
from employee e left join
salary s
on s.employee_id = e.id_employee
group by e.id_employee;
Employees not in salary will have a value of NULL.
Note that the group by condition in this query is on employee, the first table in the left join.
select employee_id, SUM(salary) as salary
from salary
left join employee on salary.employee_id=employee.id_employee
group by emplyee_id;
This is exactly what a group by clause is for. You'll have to group by the emplopyee_id and specify how you want to aggregate the salary:
SELECT employee_id, SUM(salary)
FROM salary
GROUP BY employee_id

SQL - Retrieving data within groups before and after some condition

With the two following tables:
EMPLOYEE (Fname, Lname, SSN, DNO)
DEPARTMENT (Dname, Dnumber)
For each department that has more than five employees, retrieve the
department name and the number of its employees who are making more
than $40,000
Here is an incorrect solution to this:
SELECT
dname,
COUNT(*)
FROM
Department, Employee
WHERE
dnumber = dno
AND salary > 40000
GROUP BY
dname
HAVING
COUNT(*) > 5;
It is clear that it would not list any department that have five or more employees unless they all have more than $40,000 salary, because where is applied before group by clause. which is not what we want.
Here is the correct solution:
SELECT
dname, COUNT(*)
FROM
Department, Employee
WHERE
dnumber = dno
AND salary > 40000
AND dno IN (SELECT dno
FROM Employee
GROUP BY dno
HAVING COUNT(*) > 5)
GROUP BY
dname
I cant see why is this correct?
Isn't it going to restrict the rows first with employees who have more than $40,000, then do the grouping just like the first query? what is different here?
Sub-Query, the basic:
First, let make this query a bit easier to read :
SELECT
dname,
COUNT(*)
FROM
Department,
Employee
WHERE
dnumber = dno
AND salary > 40000
AND dno IN (
SELECT dno
FROM Employee
GROUP BY dno
HAVING COUNT(*) > 5
)
GROUP BY dname
As you can see, there is what we call a "sub-query": a query inside the query.
This is the part in dno IN (/*HERE is the Sub-query*/).
As in mathematics parenthesis are run first, so SQL will go find DNO that have more than 5 employees, producing the following query :
SELECT
dname,
COUNT(*)
FROM
Department,
Employee
WHERE
dnumber = dno
AND salary > 40000
AND dno IN (
'dno10emp', 'dno24emp', 'dno45emp'
)
GROUP BY dname
Now, you find yourself with a simple query that will produce the result:
of department that have a least one employee with >40k$ salary
and are part of the department with more the 5 employee
What's wrong ?!
Well, I'll said your "good query" isn't that good, and that's why you're struggling: It'll not bring department if they don't have at least one employee with > 40k$.
Here is the query that'll do this :
SELECT
Department.dname,
COUNT(Employee.salary)
FROM
Department
LEFT JOIN Employee
ON Department.dnumber = Employee.dno
AND Employee.salary > 40000
WHERE
Department.dnumber IN (
SELECT Employee.dno
FROM Employee
GROUP BY Employee.dno
HAVING COUNT(*) > 5
)
GROUP BY Department.dname
This will bring you all department that have at least 6 employee, then count the number of employee with at least 40K$ (a department could have 0).
Could you show me ?
As an image worth a thousand word :
SQL Fiddle
MySQL 5.6 Schema Setup:
| dname | nb | salary |
|-------------------|----|--------|
| accounting | 2 | 30000 |
| accounting | 4 | 50000 |
| boss | 6 | 150000 |
| garbage-collector | 6 | 15000 |
Query 1:
SELECT
dname,
COUNT(*)
FROM
Department,
Employee
WHERE
dnumber = dno
AND salary > 40000
GROUP BY dname
HAVING COUNT(*) > 5
Results:
| dname | COUNT(*) |
|-------|----------|
| boss | 6 |
Query 2:
SELECT
dname,
COUNT(*)
FROM
Department,
Employee
WHERE
dnumber = dno
AND salary > 40000
AND
dno IN (
SELECT dno FROM Employee
GROUP BY dno
HAVING COUNT(*) > 5
)
GROUP BY dname
Results:
| dname | COUNT(*) |
|------------|----------|
| accounting | 4 |
| boss | 6 |
Query 3:
SELECT
Department.dname,
COUNT(Employee.salary)
FROM
Department
LEFT JOIN Employee
ON Department.dnumber = Employee.dno
AND Employee.salary > 40000
WHERE
Department.dnumber IN (
SELECT Employee.dno
FROM Employee
GROUP BY Employee.dno
HAVING COUNT(*) > 5
)
GROUP BY Department.dname
Results:
| dname | COUNT(Employee.salary) |
|-------------------|------------------------|
| accounting | 4 |
| boss | 6 |
| garbage-collector | 0 |
See sample data below.
http://sqlfiddle.com/#!9/357d29/2
The first query will only get departments with 6 or more highy paid employees WHILE the 2nd query will get highly paid employees of those departments with 6 or more employees. Below sample will not show in the 1st query but will show in the 2nd query.
Department Employee Salary
accounting john doe 50k
jan smith 55k
dan brown 60k
eric murphy 60k
al daniels 70k
ellen boyle 30k
1st query: nothing because only five emp have > 40k salary
2nd query: All except ellen boyle. Department has > 5 employees and all except 1 has > 40k salary
For the record, you already got correct answers. I'll just try to explain it in a different way.
Your first query has 1 select statement. It only returns employees with salary > 40k and from departments > 5 employees. Every record will only contain information about an employee with salary > 40k and from departments > 5 employees.
Your second query has 2 select statements:
This is the first one:
Select dname, count(*)
from Department, Employee
where dnumber = dno
and salary > 40000
it returns the count of all employees, by department name who earn > 40000. There are no conditions on the count(*) here. And the condition on the salary has no power over the second select statement:
SELECT Employee.dno
FROM Employee
GROUP BY Employee.dno
HAVING COUNT(*) > 5
This one returns ALL employees in all departments. This is where we have the condition on the count(*) - but it is only applied locally, to limit the number of employees per department.
And then two statements are joined together - so, first we limit the departments to the ones we are interested in, and then from those only select high-salary employees.
First, never use commas in the FROM clause. Always use proper, explicit JOIN syntax.
I think the best and simplest solution uses conditional aggregation:
SELECT d.dname, SUM(CASE WHEN e.salary > 40000 THEN 1 ELSE 0 END) as num_40kplus
FROM Department d JOIN
Employee e
ON d.dno = e.dnumber
GROUP BY dname
HAVING COUNT(*) > 5;
I see no reason why a subquery would be necessary or desirable.

How to get employees listed under their manager? SQL

I have a database:
Staff Name
1 Blake
2 Jake
Mgr Emp
2 6
2 8
3 5
4 7
Is it possible to search up all employees but list them under the manager?
Select all:
Staff Name Mgr
1 Blake null
2 Jake null
6 Scott 2
8 Jack 2
3 Clark null
5 Martin 3
4 Smith null
7 Scott 4
And is there a way to look up the team when a staff is searched?
Search: "Scott"
Returns:
Staff Name Mgr
2 Jake null
6 Scott 2
8 Jack 2
http://rextester.com/OAREI7219 is solution
CREATE TABLE Managers (
MgrID int ,
EmpID int
)
CREATE TABLE Staff (
EmpID int,
name nvarchar(50)
)
Query is ;
SELECT
s.EmpID AS Staff,
s.name AS Name,
m.MgrID
FROM Staff s LEFT JOIN Managers m ON s.EmpID = m.EmpID ;
GO
Assuming that you have:
a table "staff" with all the people in the company. For clarity I will rename the "Staff" column to "id".
a many to many table "staff_manager" to link employees with their managers. For clarity I will rename the "Mgr" column to "manager_id" and the "Emp" to "employee_id".
all tables named lowercase and plural.
You may do:
SELECT employees.id, employees.name, managers.id, managers.name
FROM staff as employees
LEFT JOIN employee_managers ON (employee_managers.employee_id = employees.id)
LEFT JOIN staff as managers ON (managers.id = employee_managers.manager_id)
for the second query you add a where clause to the above query:
WHERE managers.name = "Scott" OR employees.name = "Scott"
NOTE: If you may change the model of your database, I would suggest to
set a UNIQUE constrain on the employee_id of the staff_managers table,
so that one employee can have ONLY ONE manager.
To get All Employees with their managers. Try
Select staff.Id,Staff.name,managers.mgr
From staff left join managers on staf.id=managers.emp
To search about employee and get his team with the manager also. Try
— Search: 'scott'
Select staff.Id,Staff.name,managers.mgr
From staff left join managers on staf.id=managers.emp
Where managers.mgr =(select t.mgr from managers as t where t.emp='scott')
Union all
Select staff.Id,Staff.name,managers.mgr
From managers left join staff on staf.id=managers.mgr
Where managers.emp='scott'

Retrieving dissimilar data using WHERE NOT in the joins?

I need to retrieve the name of those departments in which no students are enrolled.
Table: department
dept_id dept_name
1 IT
2 Electrical
3 Civil
4 Mechanical
5 Chemical
Table: stud_member
f_name dept_id age
AB 2 19
Rose 3 22
May 1 20
Noor 1 21
Haya 1 19
April 3 23
Sakina 2 20
For example the names of mechanical and chemical. I have written this query for it using outer join (explicitly maybe?) But is shows an error.
please tell me that why i cannot write:
SELECT dept_id, dept_name
FROM department
LEFT JOIN stud_member ON (WHERE NOT department.dept_id = stud_member.dept_id);
I will be grateful if anyone will tell me the correct answer!
Assuming dept_id in stud_member can not be NULL which is true when dept_id is a FOREIGN KEY
SELECT dept_id, dept_name
FROM department
WHERE dept_id NOT IN (SELECT dept_id FROM stud_member);
as suggested using NOT EXISTS does not have this problem
SELECT d.dept_id, d.dept_name
FROM department d
WHERE NOT EXISTS (SELECT * FROM stud_member s WHERE d.dept_id = s.dept_id);
You can select all departments where Right part of Left JOIN is NULL
SELECT d.dept_id, d.dept_name
FROM department d
LEFT JOIN stud_member sm ON d.dept_id = sm.dept_id
WHERE sm.dept_id IS NULL

SQL INNER JOIN return parent row values only on first match

Folks,
All am looking for is to extend the INNER JOIN. Let me just get to the point. I have two tables Dept & Emp. One Dept can have multiple Emp's & not the other way around.
Table Dept
Dept_id Dept_Name
1 IT
2 HR
3 Other
Table Emp
Emp_id Dept_id Emp_Name
11 1 John
12 1 Jill
13 2 Jack
14 3 Jared
15 1 Jim
16 1 Jarret
17 2 Jacob
I need to JOIN it on Dept_id
Expected Results
Dept_id Dept_name Emp_id Emp_Name
1 IT 11 John
NULL NULL 12 Jill
NULL NULL 15 Jim
NULL NULL 16 Jarret
2 HR 13 Jack
NULL NULL 17 Jacob
3 Other 14 Jared
Hope I conveyed what i want precisely. Its just a regular Inner Join on a Foreign Key Constraint. But, I want the values from the First Table (Dept) to be NULL-ed except for the First Match. That being said, I don't care what's the first match. See it below - Just the result for the Dept_id 1.
Expected Results (Only for Dept_id = 1)
It could be
Dept_id Dept_name Emp_id Emp_Name
1 IT 11 John
NULL NULL 12 Jill
NULL NULL 15 Jim
NULL NULL 16 Jarret
OR
Dept_id Dept_name Emp_id Emp_Name
1 IT 15 Jim
NULL NULL 12 Jill
NULL NULL 11 John
NULL NULL 16 Jarret
OR
Two other possibilities.
Thanks in advance. Sorry for the long explanation even though its a simple case.
I agree with Ic. that this is a crazy query that seems to be doing something that your application should be doing instead of your sql query, that being said, it's fun to write those queries :)
SELECT CASE WHEN RowNumber = 1 THEN Dept_id ELSE NULL END AS Dept_id,
CASE WHEN RowNumber = 1 THEN Dept_name ELSE NULL END AS Dept_name,
Emp_id, Emp_Name
FROM Dept d
INNER JOIN
(SELECT ROW_NUMBER() OVER (PARTITION BY Dept_id ORDER BY Emp_Name) AS RowNumber,
Dept_id, Dept_name, Emp_id, Emp_Name
FROM Emp ) t on t.Dept_id = d. Dept_id
select case
when lag(d.dept_id) over (partition by d.dept_id order by e.emp_id) = d.dept_id then null
else d.dept_id
end as dept_id,
case
when lag(d.dept_name) over (partition by d.dept_id order by e.emp_id) = d.dept_name then null
else d.dept_name
end as dept_name,
e.emp_id,
e.emp_name
from dept d
join emp e on e.dept_id = d.dept_id
order by d.dept_id, d.dept_name, e.emp_id
Honestly, I'm not really sure why you want this result. Formatting is usually best left for the UI layer and NOT your database server. Doing it this way makes the data look like the non-first-employees for each department actually do not have a department, and it fundamentally breaks any sorting or editing functions you may have in your client.
However, you could try:
SELECT FormattedDept.Dept_id, FormattedDept.Dept_Name, Emp.Emp_id, Emp.Emp_Name
FROM Emp
LEFT OUTER JOIN
(
SELECT Dept.Dept_id, Dept.Dept_Name, MIN(Emp_id) AS Emp_id
FROM Dept
INNER JOIN Emp ON Dept.Dept_id = Emp.Dept_id
GROUP BY Dept.Dept_id, Dept.Dept_Name
) FormattedDept ON Emp.Dept_id = FormattedDept.Dept_id
AND Emp.Emp_id = FormattedDept.Emp_id
ORDER BY Emp.Dept_id, Emp.Emp_id
SQL Fiddle Demo
As I mention in my comment above, I'm not sure that you would really want the results to return in the way you are asking. Consider this. If the result set was exported to Excel, and a user changed the sort, then you would lose what dept employee 1-x was in and employee 0 would still show their department. I would suggest you group by Dept_id, and not null out the dept_id and dept_name. Handle the display of that data in your display code.
WITH newRecord
AS
(
SELECT "Emp_id", "Dept_id", "Emp_Name",
ROW_NUMBER() OVER (PARTITION BY "Dept_id" ORDER BY "Emp_id" ASC) RN
FROM Emp
)
SELECT CASE WHEN RN = 1 THEN b."Dept_id" ELSE NULL END AS "Dept_ID",
CASE WHEN RN = 1 THEN b."Dept_Name" ELSE NULL END AS "Dept_Name",
"Emp_id", "Emp_Name"
FROM newRecord a
INNER JOIN Dept b ON a."Dept_id" = b."Dept_id"
SQLFiddle Demo
because i cant think of a reason you would want that other then reporting purposes then you can also do this by using SQL*Plus formmating.
just run the following command before the query in SQL*Plus
break on dept_id skip 1
Enjoy.