Trying to get a row count in a subquery - mysql

I have two tables, one is departments and the other is employees. The department id is a foreign key in the employees table. The employee table has a name and a flag saying if the person is part-time. I can have zero or more employees in a department. I'm trying to figure out out to get a list of all departments where a department has at least one employee and if it does have at least one employee, that all the employees are part time. I think this has to be some kind of subquery to get this. Here's what I have so far:
SELECT dept.name
,dept.id
,employee.deptid
,count(employee.is_parttime)
FROM employee
,dept
WHERE dept.id = employee.deptid
AND employee.is_parttime = 1
GROUP BY employee.is_parttime
I would really appreciate any help at this point.

You must join (properly) the tables and group by department with a condition in the HAVING clause:
select d.name, d.id, count(e.id) total
from dept d inner join employee e
on d.id = e.deptid
group by d.name, d.id
having total = sum(e.is_parttime)
The inner join returns only departments with at least 1 employee.
The column is_parttime (I guess) is a flag with values 0 or 1 so by summing it the result is the number of employees that are part time in the department and this number is compared to the total number of employees of the department.

As a preliminary aside, I recommend expressing joins with the JOIN keyword, and segregating join conditions from filter conditions. Doing so would make the original query look like so:
select dept.name, dept.id, employee.deptid, count(employee.is_parttime)
from employee
join dept on dept.id = employee.deptid
where employee.is_parttime = 1
group by employee.is_parttime
It doesn't make much practical difference for inner joins, but it does make the structure of the data and the logic of the query a bit clearer. On the other hand, it does make a difference for outer joins, and there is value in consistency.
As for the actual question, yes, one can rewrite the original query using a subquery or an inline view to produce the requested result. (An "inline view" is technically what one should call an embedded query used as a table in the FROM clause, but some people lump these in with subqueries.)
Example using a subquery
select dept.name, dept.id
from dept
where dept.id in (
select deptid
from employee
group by deptid
having count(*) == sum(is_parttime)
)
Example using an inline view
select dept.name, dept.id
from dept
join (
select deptid
from employee
group by deptid
having count(*) == sum(is_parttime)
) pt_dept
on dept.id = pt_dept.deptid
In each case, the subquery / inline view does most of the work. It aggregates employees by department, then filters the groups (HAVING clause) to select only those in which the part-time employee count is the same as the total count. Naturally, departments without any employees will not be represented. If a list of department IDs would suffice for a list of departments, then that's actually all you need. To get the department names too, however, you need to combine that with data from the dept table, as demonstrated in the two example queries.

Related

MySQL: Combining two tables with null values does not print 0

In my code I am trying to combine two data tables, Employee and Department. I tried to write a query to print the respective Department Name and number of employees for all departments, even the unstaffed ones. My query looks like this:
SELECT department.name, count(department.name) AS CountOfNAME
FROM department LEFT JOIN employee ON department.dept_id = employee.dept_id
GROUP BY department.name
ORDER BY Count(department.name) DESC, department.name ASC;
And the result is:
Engineering 5
Recruitment 5
Sales 3
Product 2
Finance 1
Operations 1
Research&Development 1
This code works in that it orders departments by number of employees, and then alphabetically, but Finance and Research&Development are not supposed to have any people in them. Is there any way to correctly display those results as having 0 employees? It seems to be a hard thing to do in SQL because of how join works.
The COUNT function should ignore NULL values, giving you a zero count for the finance and research departments. The problem is that you are counting a column in the department table, which always will be non NULL due to that this table is on the left side of the LEFT JOIN. Instead, try counting a column in the employee table:
SELECT department.name,
COUNT(employee.dept_id) AS CountOfNAME
FROM department
LEFT JOIN employee
ON department.dept_id = employee.dept_id
GROUP BY department.name
ORDER BY COUNT(employee.dept_id) DESC,
department.name ASC;
I suggest you create a view for the tallies of employees by department e.g.
CREATE VIEW DepartmentEmployeeTallies
AS
SELECT dept_id, COUNT(*) AS tally
FROM employee
UNION
SELECT dept_id, 0 AS tally
FROM department
WHERE dept_id NOT IN ( SELECT dept_id FROM employee );
Then things resolve to a simple join:
SELECT name, tally
FROM department
NATURAL JOIN
DepartmentEmployeeTallies;

SQL Complex Query w/ Inner Join

I have 2 tables, 1 called Employee and 1 called Salary. Employee table consists of Emp_Name, Emp_Address, Emp_ID & Salary table consists of Salary_Details and Emp_ID. > Can you write down a query for retrieving the Salary_Details of 1 of the employee based on last name using Inner Join?
I am not sure what you are looking for, but this might help you:
SELECT * FROM Employee e
INNER JOIN Salary s ON e.Emp_ID = s.Emp_ID
WHERE e.Emp_Name = 'EMPLOYEENAME'
That will give you back all fields from Employee and Salary for an Employee with the name = 'EMPLOYEENAME' (which you can exchange then).
You can adjust the columns returned as needed depending on your app...
SELECT e.Emp_Name, e.Emp_ID, s.Salary_Details
FROM Employee e
INNER JOIN Salary s USING (Emp_ID)
WHERE e.Emp_Name = 'Smith';
The USING keyword is kind of obscure and works only if the join column is named identically in both tables. The previous answer with ON instead of USING will work in all cases. I like USING as a personal preference.

Alternative way to have an SQL query of Group By

I have two tables with columns:
Table: Employee
1.empName
2.empNo
3.deptId
Table: Department
1.deptId
2.deptName
I have written a query-
select count(*) as total, d.deptName, e.empName from Employee e JOIN Department d on e.deptid = d.deptid
Group By d.deptName, e.empName;
The above query works fine, but I wanted to learn How can I write a query to avoid including a e.empName in Group By clause, and still select it?
Is there any alternative way to accomplish this.
IF you are grouping something then you need to specify aggregate for all columns . If you are not doing so then it must be in group by for those columns.
In your case if you want to achieve it than you have to specify any aggregate function to e.empName column.
You could take the min or max of the name but this assumes that you only have one employee per department.
select count(*) as total, d.deptName, MIN(e.empName) from Employee e JOIN Department d on e.deptid = d.deptid
Group By d.deptName;
Basically if you're grouping by department you'll only get one row per department so if you've got several rows of employees per department you can't include it without some sort of transition, be careful with this, it could lead to some strange and dangerous results.

Left outer join vs subquery to include departments with no employees

Lets say I have the following database model:
And the question is as follows:
List ALL department names and the total number of employees in the department. The total number of employees column should be renamed as "total_emps". Order the list from the department with the least number of employees to the most number of employees. Note: You need to include a department in the list even when the department does not currently have any employee assigned to it.
This was my attempt:
SELECT Department.deptname
(SELECT COUNT(*)
FROM Department
WHERE Department.empno = Employee.empno ) AS total_emps
FROM Department
I'm pretty sure my solution is not correct as it won't include departments with no employees. How do you use a left inner join to solve this problem?
The query as you were trying to write it is:
(table creates modified from shree.pat18's sqlfiddle to this sqlfiddle)
create table department (deptno int, deptname varchar(20));
insert into department values (1, 'a'),(2, 'b'),(3, 'c');
create table employee (empno int, deptno int);
insert into employee values (1,1),(2,1),(3,3);
SELECT d.deptname,
(SELECT COUNT(*)
FROM EMPLOYEE e
WHERE d.deptno = e.deptno ) AS total_emps
FROM DEPARTMENT d
ORDER BY total_emps ASC;
(You were counting from DEPARTMENT instead of EMPLOYEE and comparing empno instead of deptno. And you left out a comma.)
(You were asked for every department's name and employee count so this returns that. In practice we would include a presumably unique deptno if deptname was not unique.)
I'm pretty sure my solution is not correct as it won't include
departments with no employees.
Even your answer's version of the query (with the missing comma added) has an outer select that returns a count for every department no matter what the subselect returns. So I don't know why/how you thought it wouldn't.
If you want to use LEFT (OUTER) JOIN then the DEPARTMENT rows with no employees get extended by NULL. But COUNT of a column only counts non-NULL rows.
SELECT d.deptname, COUNT(e.empno) AS total_emps
FROM DEPARTMENT d
LEFT JOIN EMPLOYEE e
ON d.deptno = e.deptno
GROUP BY d.deptno
ORDER BY total_emps ASC;
(Nb the LEFT JOIN version uses more concepts: LEFT JOIN extending by NULL, GROUP BY, and COUNT's NULL behaviour for non-*.)
First off, it's a left outer join. Now, for your query, you want to join the 2 tables based on deptno, then also group by deptno (or deptname, since that is as likely to be unique) to ensure that any aggregation we do is done for each unique department in the table. Finally, the counting is done with the count function, leading to this query:
select d.deptname, count(e.empno) as total_emps
from department d
left join employee e on d.deptno = e.deptno
group by d.deptname
SQLFiddle
Note that since we want all records from department regardless of whether there are matching records in employee or not, department must appear at the left side of the join. We could have done the same thing using a right outer join by swapping the positions of the 2 tables in the join.

Select data from one table which is connected to another 2 tables

I have 3 tables: Emplyees, Jobs and Departments
What I'm trying to achieve is to get the number of emplyees from one department.
I tried something:
SELECT count(Emplyees.id) FROM Emplyees
INNER JOIN Job ON (Job.id = Emplyees.job_id)
INNER JOIN Department ON (Department.id = 2)
but it returns the number of emplyees from all departments.
Any advice please?
An EXISTS clause will allow you to limit by the existence of something without having to worry about whether or not an employee also has other jobs, which will keep your count easy to figure.
Also, since the only thing you need from the department is the id, you can leave that table out and just filter by the dept_id field of the Job table.
SELECT count(id)
FROM Employees
WHERE EXISTS (
SELECT 1
FROM Job
WHERE id = Employees.job_id
AND dept_id = 2
)
Use WHERE clause to filter out department,where clause will apply for the whole result set returned by joins,while condition in on clause will filter result from joined table only
SELECT count(e.id)
FROM Emplyees e
INNER JOIN Job j ON (j.id = e.job_id)
INNER JOIN Department d ON (j.dept_id =d.id )
WHERE d.id = 2
And also use DISTINCT in count so if any employee has applied on multiple jobs that belong to same department will be counted as 1 i.e COUNT(DISTINCT e.id)