Join tables keeping empty results - mysql

I have two tables that I need to cross and return as many results as the ids of one of them.
The first is a table of roles/tasks:
id | rolename
---+---------
1 | check_in
2 | cleaning
3 | taxi
4 | guide
5 | car_rental
6 | meals
7 | house_owner
20 | custom
and another table that has the columns:
id | client_booking_id | staff_role_id | confirmed | staff_cost
I need a query that always gives me as many results as nr of columns in the first table. Because for each unique client_booking_id there will be only one (if any) of those tasks/roles.
So if I do:
SELECT sr.role_name, sr.id, ss.staff_cost, ss.confirmed
FROM staff_role AS sr
LEFT JOIN staff_schedule AS ss ON sr.id=ss.staff_role_id
I get a result with the nr of rows I want. Now I need to match it with a specific client_booking_id so I did
SELECT sr.role_name, sr.id, ss.staff_cost, ss.confirmed
FROM staff_role AS sr
LEFT JOIN staff_schedule AS ss ON sr.id=ss.staff_role_id
WHERE ss.client_booking_id=1551 // <-- this is the new line
And this gives me only 2 results because in the second table I have only booked 2 tasks to a id.
But I need a result with all tasks even those that do not match, with NULL values. How can I do this?

With your query (without where clause) you get rows with null and non-null values for client_booking_id. You want to match specific client_booking_id and at the same time leave all records with null values, so you add additional condition with specific client_booking_id to left join.
Moving condition to left join:
select sr.role_name
, sr.id
, ss.staff_cost
, ss.confirmed
from staff_role sr
left join staff_schedule ss on sr.id = ss.staff_role_id
and ss.client_booking_id = 1551

Related

MySQL left join with where condition

I have two tables like this
rooms
id | number
1 | 111
2 | 112
occupied_rooms
id | check_in | check_out | room_id
1 | 2017-10-01 | 2017-10-04 | 1
I want to get all the unoccupied rooms according to date check_in and check_out for this I tried
select r.id
, r.number
from rooms r
left join occupied_rooms o
on r.id = o.room_id
where (o.check_in not between "2017-10-05" and "2017-10-08" )
or (o.check_in >= "2017-10-05" and o.check_out <= "2017-10-08"))
but this query giving me result like this. which is incorrect.
id | number
1 | 111
What is wrong with this query?
Thank you for your any help and suggestions
Just join the two tables on the condition that the id matches and the range of intended stay overlaps with the range in the occupied_rooms table.
SELECT r.*
FROM rooms r
LEFT JOIN occupied_rooms t
ON r.id = t.id AND
('2017-10-02' <= t.check_out AND '2017-10-03' >= t.check_in)
WHERE
t.id IS NULL; -- NULL value indicates the room did not overlap
-- with any existing reservations
You can also check out this great reference question on how to deal with overlapping ranges in SQL queries. It makes the problem you are facing much simpler.
Demo
Your data in table occupied_rooms meets the first condition in "where";
check_in date(2017-10-01) is not between "2017-10-05" and "2017-10-08" and your where is or.
Thus, the result is included this data.
Can you tell us what output you expect?

Select all values from table B where matches table A

I have this schema:
items | taxonomy | subjects
| |
ID headline | item_id subject_id | subject_id subject
-------------------------------------------------------------------------
1 information | 1 1 | 1 cities
2 here we are | 2 1 | 2 towns
3 more things | 3 2 | 3 water
4 doo dah | 3 4 | 4 telephones
| 4 1 |
| 4 3 |
I would like to select a single row from "items" and with it, include all the rows from "subjects" which are joined by the "taxonomy" table. So for example, getting item.ID=3 would result in something like:
items.ID = 3
items.headline = "more things"
subjects.subject = "towns"
subjects.subject = "telephones"
I've started with this query
SELECT
i.ID,
i.headline,
s.subject_name
FROM items i
JOIN taxonomy t
on i.ID=t.item_id
JOIN subjects s
on t.subject_id=s.subject_id
WHERE i.ID = 3
But this only returns a single value from subject_name even if there are multiple values associated with that item_id.
EDIT
I actually had a LIMIT 1 on the query which was causing (as #Gordon Linoff said) only one row to be returned, even though there were multiple rows in the result set corresponding to the multiple subjects. His solution still does nicely, because I only want to return a single row.
Your query returns the subjects on multiple rows. If you want the subjects on a single row, then you need some form of concatenation:
SELECT i.ID, i.headline, GROUP_CONCAT(s.subject_name) as subjects
FROM items i JOIN
taxonomy t
ON i.ID = t.item_id JOIN
subjects s
ON t.subject_id = s.subject_id
WHERE i.ID = 3
GROUP BY i.ID, i.headline;
For one item, the GROUP BY is optional, but it is good form in case you modify the query to handle multiple items.
I would suggest you the "union all" clause (or "union", if you are not needing the duplicates).
(SELECT
"taxonomy" As Name,
i.headline As Value
FROM items i
JOIN taxonomy t
on i.ID=t.item_id
WHERE i.ID = 3)
Union All
(SELECT
"subject" As Name,
s.subject_name As Value
FROM items i
JOIN subjects s
on t.subject_id=s.subject_id
WHERE i.ID = 3)
You can add a 2nd field in each select to indicate type of item selected ("headline", "subjects", etc).

Combine rows from two tables with different columns?

I'm having a hard time wrapping my head around this one. I believe it's happening because I am joining the two separate tables based on the same column (user_id), but I don't know how to fix it because the only thing in common between the two tables IS the user_id column.
Here is the query.
SELECT users_data_existing.`date`,`message`,`action`,`status`,`data`,
users_data_new.`date`,`data_new`
FROM users_data_existing
INNER JOIN users_data_action USING (action_id)
INNER JOIN users_data_status_user USING (status_user_id)
INNER JOIN `users` USING (user_id)
INNER JOIN users_data_new USING (user_id)
INNER JOIN data ON users_data_existing.`data_id` = data.`id`
WHERE users_data_existing.`user_id` = 2
ORDER BY users_data_existing.`date`,users_data_new.`date` DESC
The result, is that the users_data_new.date and data_new columns, are concatenated or "appended" to the previous rows.
+----------+-----------+-----------+-----------+-----------+----------+-----------+
| date | message | action | status | data | date | data_new |
+----------+-----------+-----------+-----------+-----------+----------+-----------+
|2011-01-01| data | data | data | data |2011-01-02| data_new |
-----------------------------------------------------------------------------------
|2011-01-01| data | data | data | data |2011-01-03| data_new1 |
-----------------------------------------------------------------------------------
REPEATS PATTERN FOR TOTAL RECORDS IN users_data_new TABLE
+----------+-----------+-----------+-----------+-----------+----------+-----------+
| date | message | action | status | data | date | data_new |
+----------+-----------+-----------+-----------+-----------+----------+-----------+
|2011-01-01| data1 | data1 | data1 | data1 |2011-01-02| data_new |
-----------------------------------------------------------------------------------
|2011-01-01| data1 | data1 | data1 | data1 |2011-01-03| data_new1 |
-----------------------------------------------------------------------------------
But that's not what I need. How can I get the last two columns into a separate row? I think a UNION would resolve this but I can't do that because the tables are almost identical but don't share the message column.
As suspected in the question, it was a UNION that I needed. The trick was to create an empty column in users_data_new to match users_data_existing. I also had a challenge with sorting it so I will include that here as well.
(SELECT data_existing.date AS submitdate,status_user.status,action.action,
data.data,data_existing.message
FROM users_data_existing AS data_existing
INNER JOIN users_requested_status_user status_user
ON data_existing.status_user_id = status_user.status_user_id
INNER JOIN users_requested_action action
ON data_existing.action_id = action.action_id
INNER JOIN websites data
ON data_existing.data_id = data.id
ORDER BY data_existing.date DESC) //sorts sub-query
UNION ALL
(SELECT data_new.date AS submitdate,status_user.status,
action.action,data_new.data_new,'' message //needed to add this last empty column
FROM users_data_new AS data_new
INNER JOIN users_requested_status_user status_user
ON data_new.status_user_id = status_user.status_user_id
INNER JOIN users_requested_action action
ON data_new.action_id = action.action_id
ORDER BY data_new.date DESC) //sorts sub-query
ORDER BY submitdate DESC"; //sorts the entire result
Keep in mind that with the alias for the date, the associative array key will be whatever alias name you use. i.e. $result['submitdate']

MySQL left join COUNT() and SUM() with CASE from same table

I have the following tables in a succession of 1-to-many relationships:
company_company, company_portfolio, building_site and statistics_meter. The area of difficulty I am having is the final table, statistics_meter.
For the benefit of this exercise, it's structure is as follows:
Records are related within the same table, with some being parent meters, and some being child meters. Where a record is a child, it will have parent_meter_id set, and building_id, which crucially, is how the table is LEFT JOIN'ed set to NULL.
id | parent_meter_id | site_ref | building_id
1 | NULL | some building | 45
2 | NULL | some other building | 45
3 | 1 | and another | NULL
4 | 1 | one another one | NULL
5 | 2 | final one | NULL
I have two requirements:
1 - count the number of parent meters where the building_id is set (which I am doing successfully)
2 - count the number of meters where the parent_meter_id matches the meter_id of those counted in (1)
Thus I would expect a result whereby (1) = 2 and (2) = 3.
Here is the SQL I've got so far...I tried fiddling around with a SUM case when but I think it's totally wrong. Is this even possible within one query?
Thanks for the help.
SELECT
building_site.id as site_id,
building_site.site_ref as building_name,
COUNT(statistics_meter.id) AS meter_count,
SUM(CASE WHEN statistics_meter.parent_meter_id = [???] THEN 1 ELSE 0 END) AS check_meter_count
FROM company_company
LEFT JOIN company_portfolio ON company_portfolio.company_id=company_company.id
LEFT JOIN building_site ON building_site.portfolio_id=company_portfolio.id
LEFT JOIN statistics_meter ON statistics_meter.building_id=building_site.id
WHERE company_company.id=41
GROUP BY building_site.id
Well if I understand you, you'll need to use a subquery to get the parent meters with a building id, and then join that to your main table.
SQL Fiddle
select
sm.id,
sm.parent_meter_id,
sm2.id as ID2,
sm.site_ref,
sm.building_id
from
statistics_meter sm
inner join (
select
id,
parent_meter_id
from
statistics_meter
where
building_id is not null) sm2
on sm.parent_meter_id = sm2.id
Not sure if this is the most efficient way to do it, but in the end I performed a left join and subquery as below and performed two counts, one COUNT() for total number to answer my requirement (2) and a COUNT(distinct) to answer my requirement (1)
SELECT
count(distinct statistics_meter.id) as meter_count,
count(statistics_meter.id) as check_meter_count
FROM company_company
LEFT JOIN company_portfolio ON company_portfolio.company_id=company_company.id
LEFT JOIN building_site ON building_site.portfolio_id=company_portfolio.id
LEFT JOIN statistics_meter ON statistics_meter.building_id=building_site.id
LEFT JOIN (select * from statistics_meter where parent_meter_id is not NULL) sm2 on sm2.parent_meter_id = statistics_meter.id

How to filter duplicates within row using Distinct/group by with JOINS

For simplicity, I will give a quick example of what i am trying to achieve:
Table 1 - Members
ID | Name
--------------------
1 | John
2 | Mike
3 | Sam
Table 1 - Member_Selections
ID | planID
--------------------
1 | 1
1 | 2
1 | 1
2 | 2
2 | 3
3 | 2
3 | 1
Table 3 - Selection_Details
planID | Cost
--------------------
1 | 5
2 | 10
3 | 12
When i run my query, I want to return the sum of the all member selections grouped by member. The issue I face however (e.g. table 2 data) is that some members may have duplicate information within the system by mistake. While we do our best to filter this data up front, sometimes it slips through the cracks so when I make the necessary calls to the system to pull information, I also want to filter this data.
the results SHOULD show:
Results Table
ID | Name | Total_Cost
-----------------------------
1 | John | 15
2 | Mike | 22
3 | Sam | 15
but instead have John as $20 because he has plan ID #1 inserted twice by mistake.
My query is currently:
SELECT
sq.ID, sq.name, SUM(sq.premium) AS total_cost
FROM
(
SELECT
m.id, m.name, g.premium
FROM members m
INNER JOIN member_selections s USING(ID)
INNER JOIN selection_details g USING(planid)
) sq group by sq.agent
Adding DISTINCT s.planID filters the results incorrectly as it will only show a single PlanID 1 sold (even though members 1 and 3 bought it).
Any help is appreciated.
EDIT
There is also another table I forgot to mention which is the agent table (the agent who sold the plans to members).
the final group by statement groups ALL items sold by the agent ID (which turns the final results into a single row).
Perhaps the simplest solution is to put a unique composite key on the member_selections table:
alter table member_selections add unique key ms_key (ID, planID);
which would prevent any records from being added where the unique combo of ID/planID already exist elsewhere in the table. That'd allow only a single (1,1)
comment followup:
just saw your comment about the 'alter ignore...'. That's work fine, but you'd still be left with the bad duplicates in the table. I'd suggest doing the unique key, then manually cleaning up the table. The query I put in the comments should find all the duplicates for you, which you can then weed out by hand. once the table's clean, there'll be no need for the duplicate-handling version of the query.
Use UNIQUE keys to prevent accidental duplicate entries. This will eliminate the problem at the source, instead of when it starts to show symptoms. It also makes later queries easier, because you can count on having a consistent database.
What about:
SELECT
sq.ID, sq.name, SUM(sq.premium) AS total_cost
FROM
(
SELECT
m.id, m.name, g.premium
FROM members m
INNER JOIN
(select distinct ID, PlanID from member_selections) s
USING(ID)
INNER JOIN selection_details g USING(planid)
) sq group by sq.agent
By the way, is there a reason you don't have a primary key on member_selections that will prevent these duplicates from happening in the first place?
You can add a group by clause into the inner query, which groups by all three columns, basically returning only unique rows. (I also changed 'premium' to 'cost' to match your example tables, and dropped the agent part)
SELECT
sq.ID,
sq.name,
SUM(sq.Cost) AS total_cost
FROM
(
SELECT
m.id,
m.name,
g.Cost
FROM
members m
INNER JOIN member_selections s USING(ID)
INNER JOIN selection_details g USING(planid)
GROUP BY
m.ID,
m.NAME,
g.Cost
) sq
group by
sq.ID,
sq.NAME