Using Count with Count Distinct and Group By - mysql

I have two tables, one for employees which has id, name and company columns and another table for survey results which has employee_id, questions_id, answer as one employee to many results.
|---------------------|
| id | name | company |
|---------------------|
|-----------------------------------------|
| id | employee_id | question_id | answer |
|-----------------------------------------|
I want to Select total number of employees for each company, and total participants from each company in the survey.
I tried the following query, but it's taking too much time to execute:
SELECT employees.company as x, COUNT(DISTINCT (results.employee_id)) "Total Surveys", (SELECT COUNT(employees.id) FROM employees WHERE company = x) "Headcount"
FROM results
JOIN employees ON results.employee_id = employees.id
GROUP BY employees.company
Result
|--------------------------------|
| x | Total Surveys | Headcount |
|--------------------------------|
| C1 | 15 | 3 |
| C2 | 10 | 5 |
|--------------------------------|
SQL Fiddle
Any recommendations?

You can get the results you want by a LEFT JOIN from employees to results; then you can count both values without a subquery:
SELECT e.company,
COUNT(DISTINCT r.employee_id) AS `Total Surveys`,
COUNT(DISTINCT e.id) AS `HeadCount`
FROM employees e
LEFT JOIN results r ON r.employee_id = e.id
GROUP BY e.company
Demo on SQLFiddle

Related

INNER JOIN combining 2 columns

I have a regular table with employee info:
ID int
NAME varchar
Besides that I have a table with purchases. The employee id is listed here
again either in the column seller or contractor. It is possible that a employee
has not done any sales. It is also possible that no contractor or no seller is involved.
EMPLOYEES
ID NAME
1 Bill
2 Cliff
3 Mary
4 Jon
PURCHASES
ID SELLER CONTRACTOR
1 1 2
2 1
3 2 1
4 2 3
I want to get the list with the employee id and name and information if
this employee is listed in the seller and/or contractor columns. So basically
if this employee has done any sales.
ID NAME SALES
1 Bill 1
2 Cliff 1
3 Mary 1
4 Jon 0
What I get is double lines when employees are listed in multiple sales. I have tries numerous LEFT JOIN statements.
You can do it with EXISTS:
select e.*,
exists (select 1 from purchases where e.id in (seller, contractor)) sales
from employees e
See the demo.
Or with a LEFT JOIN and aggregation:
select e.id, e.name, max(p.id) is not null sales
from employees e left join purchases p
on e.id in (p.seller, p.contractor)
group by e.id, e.name
See the demo.
Results:
| ID | NAME | sales |
| --- | ----- | ----- |
| 1 | Bill | 1 |
| 2 | Cliff | 1 |
| 3 | Mary | 1 |
| 4 | Jon | 0 |
You could use LEFT JOIN and COUNT based on column from "outer table":
SELECT e.ID, e.name, COUNT(p1.seller) AS cnt_seller, COUNT(p2.contractor) AS cnt_contractor
FROM Employees e
LEFT JOIN Purchases p1
ON e.ID = p1.seller
LEFT JOIN Purchases p2
ON e.ID = p2.contractor
GROUP BY e.ID, e.name

MySQL count function with a join?

I've two tables.
User(id,name)
Finance(id,item_id,amount,user_id)
My use case is
users are the employees (sales guys) of the organization.
When they sell an item finance table get updated with a new record of that sold item's serial id.
I want to get the user names along with the total value of the sales they made.
User
id | name
1 | Dinesh
2 | Pathum
3 | Naveed
Finance
id | item_id | amount | user_id
1 | 1 | 2000 | 1
2 | 2 | 2000 | 1
3 | 3 | 1000 | 3
4 | 4 | 500 | 3
Expected output
Dinesh 4000
Pathum 0
Naveed 1500
How do I achieve this using MySQL?
The query is like the following:
SELECT u.name as 'Agent Name',
if(sum(f.amount) IS NULL, 0,sum(f.amount)) as Total,
f.createdAt
FROM users u LEFT JOIN finance f
ON u.id = f.user_id
GROUP BY u.id, u.name, f.createdAt
ORDER BY f.createdAt DESC
Here is a working SQL Fiddle.
Join em, group em, sum em.
SELECT usr.name AS UserName, COALESCE(SUM(fin.amount),0) AS TotalAmount
FROM `User` usr
LEFT JOIN `Finance` fin ON fin.user_id = usr.id
GROUP BY usr.id, usr.name
ORDER BY usr.id;
Test on db<>fiddle here
Another way:
SELECT Name,SUM(IFNULL(amount,0)) AS "Total" FROM (SELECT Name,amount FROM user LEFT JOIN finance ON user.id=finance.user_id) a GROUP BY Name;

Multiple joins in mysql tables with union

In mysql, I am having an issue trying to get the right data. I think I have to use union to get all the results from both tables, but not sure how to do it.
Description of tables:
Order holds order numbers
Employee holds the employee
Zone holds the zones names
Actual time has the zone id, the order id and the amount of hours it should take to deliver
Deliver details contains the employee id, the zone the employee delivered and the amount of hours it took to deliver
Order
| id | number |
|----|--------|
| 1 | 0001 |
employees
| id | name |
|----|------|
| 1 | Jon |
zones
| id | name |
|----|-------|
| 1 | ZoneA |
| 2 | ZoneB |
actual_times
| id | zone_id | eta_hours | order_id |
|----|---------|-----------|----------|
| 1 | 1 | 5 | 1 |
| 2 | 2 | 4 | 1 |
deliver_details
| id | order_id | employee_id | zone_id | hours |
|----|----------|-------------|---------|-------|
| 1 | 1 | 1 | 1 | 3 |
| 2 | 1 | 1 | 1 | 1 |
What I am hoping to get is the zone name, the amount of hours it takes to deliver and the sum of hours the employee took deliver. If the employee did not deliver to that zone then show 0
Expected output
| zone_name | hours | eta_hours | employee_name |
|-----------|-------|-----------|---------------|
| ZoneA | 4 | 5 | Jon |
| ZoneB | 0 | 4 | Jon |
I tried making a union all on the actual time but I am not getting it right.
This is something I tried (note that this was just to get the right zones with deliver times and actual times).
SELECT deliver_details.zone_id, actual_times.zone_id, zones.zone_name FROM actual_times
RIGHT JOIN deliver_details ON actual_times.order_id = deliver_details.order_id
INNER JOIN zones ON zones.id = deliver_details.zone_id
WHERE deliver_details.order_id = 1
GROUP BY deliver_details.zone_id
UNION ALL
SELECT deliver_details.zone_id, actual_times.zone_id, zones.zone_name FROM actual_times
LEFT JOIN deliver_details ON actual_times.order_id = deliver_details.order_id
INNER JOIN zones ON zones.id = actual_times.zone_id
WHERE actual_times.order_id = 1
group by actual_times.zone_id
I am pretty much trying to get all of this in one query. Is there a way to do this?
Please note that this is a simplification to a more complex problem I am having. If you need more explanation or something does not make sense, please let me know.
No need to use UNION.
Start from table employees, then CROSS JOIN with zones and actual_times to get a simple cartesian products. Then search the deliver_details for deliveries performed by each employee on each zone ; use a LEFT JOIN for that. If an epmplyee did not deliver on a given zone, use COALESCE to return 0 instead of NULL.
Query :
select
z.name,
coalesce(sum(dd.hours), 0),
at.eta_hours,
e.name
from
employees e
cross join zones z
inner join actual_times at on at.zone_id = z.id
left join deliver_details dd on dd.employee_id = e.id and dd.id = at.zone_id
group by
z.name, at.eta_hours, e.name
I came up with a simillar solution to GMB, but using UNION to get rows with 0 hours...:
SELECT z.name, sum(dd.hours), at.eta_hours, e.name
FROM zones z JOIN deliver_details dd ON z.id = dd.zone_id
JOIN actual_times at ON z.id = at.zone_id
JOIN employees e ON dd.employee_id = e.id
GROUP BY z.name, at.eta_hours, e.name
UNION
SELECT z.name, 0, at.eta_hours, e.name
FROM zones z JOIN actual_times at ON z.id = at.zone_id,
employees e
WHERE e.id NOT IN (SELECT employee_id FROM deliver_details WHERE zone_id = z.id)
If you have only one employee for each zone the following query should work for you:
SELECT Z.name AS zone_name
,DT.hours_total
,ACT.eta_hours_total
,E.name AS employee_name
FROM zones Z
INNER JOIN (SELECT zone_id
, SUM(eta_hours) eta_hours_total
FROM actual_times
GROUP BY zone_id) ACT ON Z.zone_id = ACT.zone_id
INNER JOIN (SELECT zone_id
, employee_id
, SUM(hours) hours_total
FROM deliver_details
GROUP BY zone_id, employee_id) DT ON Z.zone_id = DT.zone_id
INNER JOIN employees E ON DT.employee_id = E.employee_id

how to perform average for similar group of entries in mysql

+----------+--------+
| emp_name | rating |
+----------+--------+
| Sameer | 4 |
| Sameer | 9.8 |
| Sameer | 9 |
| Sameer | 7 |
| Sameer | 8.2 |
| Sameer | 9.5 |
| Sameer | 10 |
| Ashwath | 9 |
| Ashwath | 4 |
| Ashwath | 9 |
+----------+--------+
I just started learning SQL and I wrote a query and got the above output but i want to display the averege rating of Sameer and Ashwath instead of rating, how can i do it?
Query:
SELECT
emp_name,
bus.rating
FROM employees
JOIN drives
ON employees.emp_id = drives.emp_id
JOIN bus
ON bus.bus_no = drives.bus_no
WHERE
drives.emp_id IN (select emp_id from drives group by emp_id having count(bus_no) > 2);
Just aggregate by employee and take the average rating:
SELECT
emp_name,
AVG(bus.rating) AS avg_rating
FROM employees
INNER JOIN drives
ON employees.emp_id = drives.emp_id
INNER JOIN bus
ON bus.bus_no = drives.bus_no
WHERE
drives.emp_id IN (SELECT emp_id FROM drives
GROUP BY emp_id HAVING COUNT(bus_no) > 2)
GROUP BY
emp_name;
As you are learning SQL, it's better to do the right things :
Try to always use INNER JOIN OR LEFT JOIN. Using IN is more "expensive" than doing an INNER JOIN or LEFT JOIN.
Last, when you use an aggregate function (COUNT, SUM, AVG) you need to use a GROUP BY
If I rewrite your query here is what I would do :
SELECT
emp_name,
AVG(bus.rating)
FROM employees
INNER JOIN drives on employees.emp_id=drives.emp_id
INNER JOIN
(select emp_id, count(bus_no) from drives
group by emp_id having count(bus_no) > 2 ) AS d
ON drives.emp_id = d.emp_id
INNER JOIN bus
ON bus.bus_no = drives.bus_no
GROUP BY emp_name
Here is a famous image to explain all the joins
SELECT emp_name, avg(rating) FROM table GROUP BY emp_name
Try this -
$sql = "SELECT emp_name, AVG(rating) as avg_rating FROM table_name GROUP BY emp_name";

left join, return non matching rows, where clause on right table, group by

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.