Multiple joins in mysql tables with union - mysql

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

Related

How to count in mysql

I have this query in mySQL where I would like to sum the line product of each doctor but I dont know how to do it.
use avant_medical;
select
sales.doctor_id as DoctorID,
line_products.id as LineProductID,
line_products.name as LineProductName
from `doctors`
inner join `sales` on `doctors`.`id` = `sales`.`doctor_id`
inner join `inventories` on `sales`.`id` = `inventories`.`sale_id`
inner join `products` on `inventories`.`product_id` = `products`.`id`
inner join `line_products` on `products`.`lineProduct_id` = `line_products`.`id`
order by `doctors`.`id` asc;
lPID= lineProductID
|DrID|lPID |
| -- | ----|
| 1 | 7 |
| 1 | 6 |
| 1 | 6 |
| 1 | 7 |
| 1 | 7 |
| 1 | 7 |
| 1 | 6 |
This is how I want:
Doctor 1
lineID | quantity
7 | 4
6 | 3
I try this query only in mySQL
The keyword you are looking for is count, not sum. Summing would add up every lineProductID as if they where regular mathematical values, while counting will add up how many times a given lineProductID is found.
select
sales.doctor_id as DoctorID,
line_products.id as LineProductID,
line_products.name as LineProductName,
-- We count the number of occurrences of each line_product.id
COUNT(line_products.id) as LineProductQty
from `doctors`
inner join `sales` on `doctors`.`id` = `sales`.`doctor_id`
inner join `inventories` on `sales`.`id` = `inventories`.`sale_id`
inner join `products` on `inventories`.`product_id` = `products`.`id`
inner join `line_products` on `products`.`lineProduct_id` = `line_products`.`id`
-- Never forget to properly GROUP your aggregate functions, such as COUNT() or SUM()!
GROUP BY sales.doctor_id, line_products.id, line_products.name
order by `doctors`.`id` asc;
Since you didn't provided full schema to test this, I made a small, very artificial demo, but should be representative of how the query above works.

Using Count with Count Distinct and Group By

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

Get total count of records with a mysql join and 2 tables

I have 2 tables that I am trying to join but I am not sure how to make it the most time efficient.
Tasks Table:
nid | created_by | claimed_by | urgent
1 | 11 | 22 | 1
2 | 22 | 33 | 1
3 | 33 | 11 | 1
1 | 11 | 43 | 0
1 | 11 | 44 | 1
Employee Table:
userid | name
11 | EmployeeA
22 | EmployeeB
33 | EmployeeC
Result I am trying to get:
userid | created_count | claimed_count | urgent_count
11 | 3 | 1 | 3
22 | 1 | 1 | 2
33 | 1 | 1 | 2
created_account column will show total # of tasks created by that user.
claimed_count column will show total # of tasks claimed by that user.
urgent_count column will show total # of urgent tasks (created or claimed) by that user.
Thanks in advance!
I would start by breaking this up into pieces and then putting them back together. You can get the created_count and claimed_count using simple aggregation like this:
SELECT created_by, COUNT(*) AS created_count
FROM myTable
GROUP BY created_by;
SELECT claimed_by, COUNT(*) AS claimed_count
FROM myTable
GROUP BY claimed_by;
To get the urgent count for each employee, I would join the two tables on the condition that the employee is either the created_by or claimed_by column, and group by employee. Instead of counting, however, I would use SUM(). I am doing this because it appears each row will be either 0 or 1, so SUM() will effectively count all non-zero rows:
SELECT e.userid, SUM(t.urgent)
FROM employee e
JOIN task t ON e.userid IN (t.created_by, t.claimed_by)
GROUP BY e.userid;
Now that you have all the bits of data you need, you can use an outer join to join all of those subqueries to the employees table to get their counts. You can use the COALESCE() function to replace any null counts with 0:
SELECT e.userid, COALESCE(u.urgent_count, 0) AS urgent_count, COALESCE(crt.created_count, 0) AS created_count, COALESCE(clm.claimed_count, 0) AS claimed_count
FROM employee e
LEFT JOIN(
SELECT e.userid, SUM(t.urgent) AS urgent_count
FROM employee e
JOIN task t ON e.userid IN (t.created_by, t.claimed_by)
GROUP BY e.userid) u ON u.userid = e.userid
LEFT JOIN(
SELECT claimed_by, COUNT(*) AS claimed_count
FROM task
GROUP BY claimed_by) clm ON clm.claimed_by = e.userid
LEFT JOIN(
SELECT created_by, COUNT(*) AS created_count
FROM task
GROUP BY created_by) crt ON crt.created_by = e.userid;
Here is an SQL Fiddle example.

MySQL Booking system: Getting available rooms

I have a problem with my query for locating available rooms in a simple hotel booking system.
My table structure looks like the following:
hotels
hotelroomtypes (intersects a hotel, with a room and a roomtype)
bookings
orders
There is more to it, but the use of the other tables sums themselves up in the query, which looks like the following:
SELECT DISTINCT
hotels.HotelName
,hotels.HotelID
,hotels.Address
,hotels.Description
,images.URL
,roomtypes.Price
,roomtypes.RoomtypeName
,roomtypes.RoomtypeID
,COUNT(DISTINCT hotelroomtypes.HRID) AS AvailableRooms
FROM hotelroomtypes
INNER JOIN hotels ON (
hotelroomtypes.HotelID = hotels.HotelID
AND
hotels.CountryID = 1 // e.g. United States
)
INNER JOIN roomtypes ON (
hotelroomtypes.RoomtypeID = roomtypes.RoomtypeID
AND
roomtypes.RoomtypeID = 1 // e.g. Suite
)
RIGHT OUTER JOIN bookings ON (
hotelroomtypes.HRID NOT IN (
SELECT HRID FROM bookings
WHERE bookings.From BETWEEN '2015-06-13' and '2015-06-16'
OR bookings.To BETWEEN '2015-06-13' and '2015-06-16'
)
)
INNER JOIN images ON (
hotels.ImageID = images.ImageID
)
GROUP BY
hotels.HotelName
HAVING COUNT(hotelroomtypes.HRID) > 0
ORDER BY hotels.HotelName ASC, AvailableRooms ASC
Now my problems lay with getting the correct number of booked rooms, and also hiding the hotel from the query if all the rooms are taken.
I used to do this by having an INNER JOIN on bookings, but when I then had an empty bookings table, the query failed 100% and didn't show any hotels.
With LEFT joins, I had some false positives at times, but currently using these types of joins all return EVERY room the hotel has or none at all.
INNER naturally returns no hotels if there are no bookings present, as expected. Which is what I was using earlier.
How can I correcly structure this query to give me an exact amount of available rooms on each hotel, as well as only returning hotels which have available rooms, even if there are no bookings to search through?
edit for table structures:
Hotels:
_________________________________________________________________
| hotelid | hotelname | countryid | description | otherfieldshere |
|_________|___________|___________|_____________|_________________|
| 1 | example | 1 | something | something |
|_________|___________|___________|_____________|_________________|
Hotelroomtypes:
______________________________________
| hrid | hotelid | roomtypeid | roomid |
|______|_________|____________|________|
| 1 | 1 | 1 | 1 |
|______|_________|____________|________|
Bookings:
_______________________________________________
| bookingid | from | to | orderid |
|___________|____________|____________|_________|
| 1 | 2015-05-13 | 2015-05-16 | 1 |
|___________|____________|____________|_________|
Orders:
________________________________________
| orderid | reference | email |
|_________|___________|__________________|
| 1 | 12345 | some#example.com |
|_________|___________|__________________|
Rooms:
_____________________
| roomid | roomnumber |
|________|____________|
| 1 | 500 |
|________|____________|
Unfortunately it seems alcohol and problem solving isn't really an option when you've been working for 7 hours straight without a break.
I restructured my query to the following:
SELECT DISTINCT
hotels.HotelName
,hotels.HotelID
,hotels.Address
,hotels.Description
,images.URL
,roomtypes.Price
,roomtypes.RoomtypeName
,roomtypes.RoomtypeID
,COUNT(DISTINCT hotelroomtypes.HRID) AS AvailableRooms
FROM hotels
INNER JOIN hotelroomtypes ON (
hotelroomtypes.HotelID = hotels.HotelID
AND
hotels.CountryID = ?
AND
hotelroomtypes.HRID NOT IN (
SELECT HRID FROM bookings
WHERE bookings.From BETWEEN ? and ?
OR bookings.To BETWEEN ? and ?
)
)
INNER JOIN roomtypes ON (
hotelroomtypes.RoomtypeID = roomtypes.RoomtypeID
AND
roomtypes.RoomtypeID = ?
)
INNER JOIN images ON (
hotels.ImageID = images.ImageID
)
GROUP BY
hotels.HotelName
HAVING COUNT(hotelroomtypes.HRID) > 0
ORDER BY hotels.HotelName ASC, AvailableRooms ASC
Which actually solved my problem.

join one row to all row and returning all row

can I get data like this from my table
| id_outlet| date | count(msisdn) |
| 34.10.1 | 2014-08 | 0 |
| 34.10.1 | 2014-09 | 3 |
| 34.10.1 | 2014-10 | 2 |
| 34.10.2 | 2014-08 | 1 |
| 34.10.2 | 2014-09 | 0 |
| 34.10.2 | 2014-10 | 0 |
So I have 2 tables
1. table outlet (unique)
2. table sales (detail of table outlet)
As u see in my second table there are 3 periode (2014-08, 2014-09, 2014-10)
I want join that periode with id_outlet in first table like that example.
Can I?
Please Help me
Using a CROSS JOIN:-
SELECT
o.id_outlet,
s_main.periode,
o.branch,
count(msisdn)
FROM
(
SELECT DISTINCT SUBSTRING(date,1,7) AS periode
FROM sales
) s_main
CROSS JOIN outlet o
LEFT OUTER JOIN sales s
ON s_main.periode = SUBSTRING(s.date,1,7)
AND o.id_outlet = s.id_outlet
WHERE (o.STATUS LIKE 'STREET%')
GROUP BY s_main.periode, o.branch, o.id_outlet
If you have a table of dates then you can just use that rather than the sub query to get the dates (which also avoids the potential problem of not having a date in the results for a month where there has been zero sales for any outlet).
Don't worry, be happy!
SELECT
o.id_outlet,
SUBSTRING(s.date,1,7) AS periode,
o.branch
FROM outlet o LEFT JOIN sales s ON o.id_outlet = s.id_outlet
WHERE (o.STATUS LIKE 'STREET%')
ORDER BY o.id_outlet, YEAR(s.DATE), MONTH(s.DATE), branch
You need this query:
SELECT
o.id_outlet,
d.period AS periode,
o.branch,
count(msisdn)
FROM dates d LEFT JOIN outlet o ON d.period = SUBSTRING(o.date,1,7) LEFT JOIN sales s ON o.id_outlet = s.id_outlet
WHERE (o.STATUS LIKE 'STREET%')
GROUP BY CONCAT(d.period, '#', s.id_outlet)
ORDER BY o.id_outlet, d.period, branch