I am having all employees(manager and employees) under one table called Employee. Table looks as follows,
Table
+-------+------------+---------+---------+------------+
|emp_id | name | dept_id | salary | manager_id |
+=======+============+=========+=========+============+
| 1 | Sally | 1 | 20000 | null |
| 2 | Ajit | 2 | 20000 | 1 |
| 3 | Rahul | 1 | 20000 | 1 |
| 4 | uday | 1 | 20000 | null |
| 5 | john | 1 | 20000 | null |
| 6 | netaji | 2 | 20000 | 2 |
| 7 | prakriti | 3 | 1111 | 3 |
| 8 | sachin | 3 | 1111 | 3 |
| 9 | santosh | 1 | 1111 | 2 |
| 10 | Ravi | 1 | 1111 | 2 |
+-------+------------+---------+---------+------------+
Both managers and employees belong to same table. manager_id refers = emp_id who is manager.
I want to write query to count number of employees belonging to each manager. So even if certain manager doesn't have any employee under her or him the count will show as 0
Result should be as follows,
Expected Output
+------+----------+
|Count | Manager |
+======+==========+
| 2 | Sally |
| 3 | Ajit |
| 2 | Rahul |
| 0 | Uday |
| 0 | John |
+------+----------+
You need to do left self-join on the table. The left join will ensure that there is a row for every manager even if there are no employees under them. You need to use the COUNT() aggregate on a field from the employee side of the join that will be NULL if the manager has no employees. COUNT() doesn't actually count NULLs so this should give you zeroes where you want them.
The WHERE clause in this query defines managers by looking if their manager_id is NULL or if there are any matches in the joined table which means there are people that have them set as their manager.
SELECT mgr.name, COUNT(emp.emp_id) AS employee_count
FROM Employee AS mgr
LEFT JOIN Employee AS emp ON emp.manager_id=mgr.emp_id
WHERE mgr.manager_id IS NULL OR emp.emp_id IS NOT NULL
GROUP BY mgr.name
The correct solution likely involves fixing the scheme as any approach will fail for a "sub-manager" (who is managed and thus has a manager_id) but does not currently manage anybody.
Anyway, if the above limitation is acceptable, then people are managers if either
They have a NULL manager_id (as stated in a comment), or
They currently manage people other employees
Then this query (example sqlfiddle) can be used:
SELECT m.name as Manager, COUNT(e.id) as `Count`
FROM employee m
LEFT JOIN employee e
ON m.id = e.manager_id
GROUP BY m.id, m.name, m.manager_id
HAVING `Count` > 0 OR m.manager_id IS NULL
Notes/explanation:
The LEFT [OUTER] join is important here; otherwise managers who did not manage anybody would not be found. The filtering is then applied via the HAVING clause on the grouped result.
The COUNT is applied to a particular column, instead of *; when done so, NULL values in that column are not counted. In this case that means that employees (m) without a match (e) are not automatically selected by the COUNT condition in the HAVING. (The LEFT JOIN leaves in the left-side records, even when there is no join-match - all the right-side columns are NULL in this case.)
The GROUP BY contains all the grouping fields, even if they appear redundant. This allows the manager_id field to be used in the HAVING, for instance. (The group on ID was done in case two managers ever have the same name, or it is to be selected in the output clause.)
here is the solution; you are to make self join on employee table.
SELECT e1.manager_id, e2.name, COUNT (1) AS COUNT
FROM Employee e1 JOIN Employee e2 ON e1.manager_id = e2.id
GROUP BY e1.manager_id, e2.name
UNION ALL
SELECT e3.id, e3.name, 0 AS COUNT
FROM Employee e3
WHERE manager_id IS NULL
AND e3.id NOT IN ( SELECT e1.manager_id
FROM Employee e1
JOIN
Employee e2
ON e1.manager_id = e2.id
GROUP BY e1.manager_id, e2.name)
Maybe that helps:
select t1.name, count(*) -- all managers with emps
from t t1
join t t2
on t1.emp_id = t2.manager_id
group
by t1.name
union
all
select t1.name, 0 -- all managers without emps
from t t1
left
join t t2
on t1.emp_id = t2.manager_id
where t1.manager_id is null
and t2.emp_id is null
try below:
select (select count(*) from employees b where b.manager_id = a.emp_id)) as Count, a.Name as manager from employees a where a.emp_id in (select distict c.manager_id from employees c)
Query
CREATE TABLE employee(emp_id varchar(5) NOT NULL,
emp_name varchar(20) NULL,
dt_of_join date NULL,
emp_supv varchar(5) NULL,
CONSTRAINT emp_id PRIMARY KEY(emp_id) ,
CONSTRAINT emp_supv FOREIGN KEY(emp_supv)
REFERENCESemployee(emp_id));
you need to do
LEFT OUTER JOIN like this:
SELECT movies.title,sequelies.title AS sequel_title
FROM movies
LEFT OUTER JOIN movies sequelies
ON movies.sequel_id = sequelies.id ;
Related
I am a bit new to write complex SQL queries. There are two tables and we call them employee and status.
I need to filter duplicate records based on the key and status.
Key : |emplyee_dep_id|employee_reg_date|employee_rep_manager|
It is a composite key.
And there is a column in the Employee table called emp_status_id. In the Status table, there are two columns status_code and status_id.
Finally, I need to filter duplicate records based on the composite key with status "promoted" joining two tables.
I have written two queries but need to develop a single query. Could you help me to combine these two queries, please?
Sample data
|emplyee_dep_id|employee_reg_date|employee_rep_manager|status_id|email|
| 1 | 20-01-01 | Anne | 3 |a#a.com|
| 1 | 20-01-01 | Anne | 3 |u#a.com|
| 1 | 20-01-01 | Anne | 3 |y#a.com|
| 1 | 20-01-01 | Anne | 3 |h#a.com|
| 1 | 20-01-01 | Anne | 1 |b#a.com|
|Status_id|status_code|
| 3 | Promoted |
| 1 | Probation |
Query:
SELECT
emp.emplyee_dep_id, emp.employee_reg_date,
emp.employee_rep_manager, employee_status
FROM
employee emp
INNER JOIN
(SELECT
emplyee_dep_id, employee_reg_date, employee_rep_manager,
COUNT(*) AS CountOf
FROM
employee
GROUP BY
emplyee_dep_id, employee_reg_date, employee_rep_manager
HAVING
COUNT(*) > 1) emp1 ON emp.emplyee_dep_id = emp1.emplyee_dep_id
AND emp.employee_reg_date = emp1.employee_reg_date
AND emp.employee_rep_manager = emp1.employee_rep_manager
SELECT
employee_status
FROM
Employee
INNER JOIN
Status ON Employee.status_id = Status.status_id
WHERE
Status.status_code = 'promoted'
Instead of the Employee in the 2nd query, put the 1st query and join with status
SELECT e.*, s.status_code
from
(
SELECT emp.emplyee_dep_id,emp.employee_reg_date,emp.employee_rep_manager,employee_status as status_id
FROM employee emp
INNER JOIN (SELECT
emplyee_dep_id,employee_reg_date,employee_rep_manager, COUNT(*) AS CountOf
FROM employee
GROUP BY emplyee_dep_id,employee_reg_date,employee_rep_manager
HAVING COUNT(*)>1
) emp1
ON emp.emplyee_dep_id=emp1.emplyee_dep_id
AND emp.employee_reg_date=emp1.employee_reg_date
AND emp.employee_rep_manager = emp1.employee_rep_manager
) E
INNER JOIN Staus s
ON E.status_id = s.status_id
where s.status_code = 'promoted'
Group by emplyee_dep_id,employee_reg_date,employee_rep_manager;
And here is the fiddle
I am working with MySQL Workbench to get the table I am looking for. I am almost there. Here is the result of the query:
----------------------------------------------------------------------------
employee.manager_id | employee.id | employee.first_name | employee.last_name
----------------------------------------------------------------------------
null | 1 | Petra | Wallace
null | 3 | Peter | Willis
null | 5 | Michael | Best
1 | 2 | David | Lone
3 | 4 | Barbara | Grinder
5 | 6 | Anthony | Krone
Now, I want to replace the values of the column employee.manager_id with the following:
When the value is null, either leave it null or substitute it with the string "none"
When the value has a number, it references the number of the employee.id. For example, the value 1 in employee.manager_id represents employee.id number 1, who is Petra Wallace.
I would like to show in the employee.manager_id column, the employee.first_name and the employee.last_name, instead of a number. Anybody has any idea how to do it?
A left join retrieves a manager based on the JOIN criteria. If there isn't a manager, then all of the manager.* fields are NULL.
SELECT
COALESCE(manager.first_name,"none") as manager_first_name,
manager.last_name as manager_last_name,
employee.id,
employee.first_name,
employee.last_name
FROM employee
LEFT JOIN employee manager
ON employee.manager_id = manager.id
ORDER BY employee.manager_id
Try this:
SELECT
CASE WHEN employee.manager_id IS NULL THEN "NONE"
ELSE (SELECT CONCAT(e.first_name, ' ', e.last_name), e.id FROM employee e
WHERE e.id = employee.manager_id)
END AS case_generated_column,
employee.id, employee.first_name, employee.last_name FROM employee ORDER BY employee.manager_id;
I have a SQL database with a table called staff, having following columns:
workerID (Prim.key), name, department, salary
I am supposed to find the workers with the highest salary per department and used the following statement:
select staff.workerID, staff.name, staff.department, max(staff.salary) AS biggest
from staff
group by staff.department
I get one worker shown from each department, but they are NOT the workers with the highest salary, BUT the biggest salary value is shown, even though the worker does not get that salary.
The person shown is the worker with the "lowest" workerID per department.
So, there is some sorting going on using the primary key, even though it is not mentioned in the group by statement.
Can someone explain, what is going on and maybe how to sort correctly.
Explanation for what is going on:
You are performing a GROUP BY on staff.department, however your SELECT list contains 2 non-grouping columns staff.workerID, staff.name. In standard sql this is a syntax error, however MySql allows it so the query writers have to make sure that they handle such situations themselves.
Reference: http://dev.mysql.com/doc/refman/5.0/en/group-by-handling.html
In standard SQL, a query that includes a GROUP BY clause cannot refer to nonaggregated columns in the select list that are not named in the GROUP BY clause.
MySQL extends the use of GROUP BY so that the select list can refer to nonaggregated columns not named in the GROUP BY clause.
The server is free to choose any value from each group, so unless they are the same, the values chosen are indeterminate.
Starting with MySQL 5.1 the non-standard feature can be disabled by setting the ONLY_FULL_GROUP_BY flag in sql_mode: http://dev.mysql.com/doc/refman/5.6/en/sql-mode.html#sqlmode_only_full_group_by
How to fix:
select staff.workerID, staff.name, staff.department, staff.salary
from staff
join (
select staff.department, max(staff.salary) AS biggest
from staff
group by staff.department
) t
on t.department = staff.department and t.biggest = staff.salary
In the inner query, fetch department and its highest salary using GROUP BY. Then in the outer query join those results with the main table which would give you the desired results.
This is the usual case group by with a aggregate function does not guarantee proper row corresponding to the aggregate function. Now there are many ways to do it and the usual practice is a sub-query and join. But if the table is big then performance wise it kills, so the other approach is to use left join
So lets say we have the table
+----------+------+-------------+--------+
| workerid | name | department | salary |
+----------+------+-------------+--------+
| 1 | abc | computer | 400 |
| 2 | cdf | electronics | 200 |
| 3 | gfd | computer | 400 |
| 4 | wer | physics | 300 |
| 5 | hgt | computer | 700 |
| 6 | juy | electronics | 100 |
| 7 | wer | physics | 400 |
| 8 | qwe | computer | 200 |
| 9 | iop | electronics | 800 |
| 10 | kli | physics | 800 |
| 11 | qsq | computer | 600 |
| 12 | asd | electronics | 300 |
+----------+------+-------------+--------+
SO we can get the data as
select st.* from staff st
left join staff st1 on st1.department = st.department
and st.salary < st1.salary
where
st1.workerid is null
The above will give you as
+----------+------+-------------+--------+
| workerid | name | department | salary |
+----------+------+-------------+--------+
| 5 | hgt | computer | 700 |
| 9 | iop | electronics | 800 |
| 10 | kli | physics | 800 |
+----------+------+-------------+--------+
My favorite solution to this problem uses LEFT JOIN:
SELECT m.workerID, m.name, m.department, m.salary
FROM staff m # 'm' from 'maximum'
LEFT JOIN staff o # 'o' from 'other'
ON m.department = o.department # match rows by department
AND m.salary < o.salary # match each row in `m` with the rows from `o` having bigger salary
WHERE o.salary IS NULL # no bigger salary exists in `o`, i.e. `m`.`salary` is the maximum of its dept.
;
This query selects all the workers that have the biggest salary from their department; i.e. if two or more workers have the same salary and it is the bigger in their department then all these workers are selected.
Try this:
SELECT s.workerID, s.name, s.department, s.salary
FROM staff s
INNER JOIN (SELECT s.department, MAX(s.salary) AS biggest
FROM staff s GROUP BY s.department
) AS B ON s.department = B.department AND s.salary = B.biggest;
OR
SELECT s.workerID, s.name, s.department, s.salary
FROM (SELECT s.workerID, s.name, s.department, s.salary
FROM staff s
ORDER BY s.department, s.salary DESC
) AS s
GROUP BY s.department;
Sorry about the complicated title.
I have two tables, customers and orders:
customers - names may be duplicated, ids are unique:
name | cid
a | 1
a | 2
b | 3
b | 4
c | 5
orders - pid is unique, join on cid:
pid | cid | date
1 | 1 | 01/01/2012
2 | 1 | 01/01/2012
3 | 2 | 01/01/2012
4 | 3 | 01/01/2012
5 | 3 | 01/01/2012
6 | 3 | 01/01/2012
So I used this code to get a count:
select customers.name, orders.date, count(*) as count
from customers
left JOIN orders ON customers.cid = orders.cid
where date between '01/01/2012' and '02/02/2012'
group by name,date
which worked fine but didnt give me null rows when the cid of customers didnt match a cid in orders, e.g. name-c, id-5
select customers.name, orders.date, count(*) as count
from customers
left JOIN orders ON customers.cid = orders.cid
AND date between '01/01/2012' and '02/02/2012'
group by name,date
So I changed the where to apply to the join instead, which works fine, it gives me the null rows.
So in this example I would get:
name | date | count
a | 01/01/2012 | 3
b | null | 1
b | 01/01/2012 | 3
c | null | 1
But because names have different cid's it is giving me a null row even if the name itself does have rows in orders, which I don't want.
So I'm looking for a way for the null rows to only be returned when any other cid's that share the same name also do not have any rows in orders.
Thanks for any help.
---EDIT---
I have edited the counts for null rows, count never returns null but 1.
The result of
select * from (select customers.name, orders.date, count(*) as count
from customers
left JOIN orders ON customers.cid = orders.cid
AND date between '01/01/2012' and '02/02/2012'
group by name,date) as t1 group by name
is
name | date | count
a | 01/01/2012 | 3
b | null | 1
c | null | 1
First, select your date grouped by (name, date), excluding NULLs, then join with a set of distinct names:
SELECT names.name, grouped.date, grouped.count
FROM ( SELECT DISTINCT name FROM customers ) as names
LEFT JOIN (
SELECT customers.name, orders.date, COUNT(*) as count
FROM customers
LEFT JOIN orders ON customers.cid = orders.cid
WHERE date BETWEEN '01/01/2012' AND '02/02/2012'
GROUP BY name,date
) grouped
ON names.name = grouped.name
The best approach would be Group them together based on Cid's and then other parameters.
So you would get the proper output with NULL values based on Left Outer Join.
The case:
I have 2 tables, 'contracts' and 'members' tied with contract.id = members.cid.
Each contract has one main member and several secondary members (usually the children and spouse of the main member). The main member's details (name, address etc) are stored in table contracts whereas, extra members details are kept in table members. (bad logic, i know but this was a mess to begin with and i need time to redesign the db and website)
The desired output:
When I run a batch print of all contracts (lets say, every Friday) I need to also print a copy of the contract for each member, too but with the member's details on the contract instead of the main member.
The question:
How does this translate into a mysql query? Ok, its a left join, but how do I say "print data from table members instead of contracts for the joined rows"?
Main fields that occur in the 2 tables are name + surname, those should be enough for a draft query example.
Example tables and data:
contracts
-------------------------
id | name | surname |
-------------------------
1 | Tom | Jones |
2 | Jamie | Oliver |
members
--------------------------------
id | cid | name | surname |
--------------------------------
1 | 1 | Jack | Jones |
2 | 1 | Anne | Jones |
3 | 2 | Cathy | Wilson |
So the results I want shoudld be:
cid | name | surname |
--------------------------
1 | Tom | Jones |
1 | Jack | Jones |
1 | Anne | Jones |
2 | Jamie | Oliver |
2 | Cathy | Wilson |
If i write
SELECT c.name as name, c.surname as surname, m.name as name, m.surname as surname
FROM contracts c
join members m on c.id = m.cid
I simply end up with
name and name_1, surname and surname_1 but I want ALL names to fall under name and likewise for all other matching columns.
Hope this works :::
select c1.id, c1.name, c1.surname
from contracts c1
union
(Select m.id, m.name, m.surname
from
members m left join contracts c on (c.id = m.cid))
This is what I finally did and it worked (field names are different but the syntax is what matters):
SELECT u.id, u.onoma_u, u.name_u,
coalesce(u.programa, aa.programa) as programa,
coalesce(u.barcode, aa.barcode) as barcode,
coalesce(u.plan, aa.plan) as plan,
coalesce(u.im_exp, aa.im_exp) as im_exp,
coalesce(u.symb, aa.symb) as symb
FROM (SELECT a1.id, a1.onoma_u, a1.name_u, a1.programa, a1.barcode, a1.plan, a1.im_exp, a1.symb
FROM aitisi a1
UNION
SELECT a2.id, m.name, m.surname, NULL, NULL, NULL, NULL, NULL
FROM members m
JOIN aitisi a2 ON a2.id = m.symbid) u
JOIN aitisi aa ON aa.id = u.id;
I used aliases and NULLS as dummy fields to fill in the blanks.