LEFT JOIN to INNER JOIN multiple tables [duplicate] - mysql

This question already has answers here:
LEFT JOIN query not returning all rows in first table
(1 answer)
Left Outer Join doesn't return all rows from my left table?
(3 answers)
Closed 3 years ago.
After a day of digging around trying to get this query to work, I've had to resort to asking for help. This is my first venture into JOINs so please treat me gently ;)
I've got a query producing a timetable based on data across 6 tables.
Database relationship diagram
My query is:
SELECT
course.CourseName,
course.CourseID,
timetablepaeriods.PeriodName,
subject.SubjectName,
Subject.SubjectColour,
Room.RoomName
FROM
TimetablePeriods
LEFT JOIN Timetable ON
TimetablePeriods.PeriodID = Timetable.Period_ID
INNER JOIN Course ON
Timetable.Course_ID = Course.CourseID
INNER JOIN Subject ON
Course.Subject_ID = Subject.SubjectID
INNER JOIN CourseMembership ON
CourseMembership.Course_ID = Course.CourseID
INNER JOIN Room ON
Timetable.Room_ID = Room.RoomID
WHERE CourseMembership.Student_ID = 123
ORDER BY TimetablePeriods.SortOrder ASC
This is returning all of the results that match but not the rows where there is a value in TimetablePeriods but nothing else.
CourseName | CourseID | PeriodName | SubjectName | etc . . .
-----------|----------|------------|-------------|
y7Ma3 | 19 | MonP1 | Maths |
y7Hist4 | 16 | MonP2 | History |
y7Geog1 | 30 | MonP3 | Geography |
y7Eng3 | 28 | MonP5 | English |
I was expecting to get a row with blank values for MonP4. This exists in the database and if I run the same query against a student who has a blank against MonP5 it skips that instead.
As I said at the top this is my first attempt at using the JOIN statement if theres a better way of approaching this I'd love to hear it.
Thanks in advance for any help.

As explained by #Madhur Bhaiya the WHERE statement in my original query was changing everything to an INNER JOIN
My solution
SELECT
r.CourseName,
r.CourseID,
r.SubjectName,
r.SubjectColour,
r.RoomName,
TimetablePeriods.PeriodName
FROM
(SELECT
Course.CourseName,
Course.CoureID,
Subject.SubjectName,
Subject.Colour,
Room.RoomName,
Timetable.Period_ID
FROM
Course,
Timetable,
Subject,
CourseMembership,
Room
WHERE
Course.CourseID = Timetable.Course_ID AND
Course.Subject_ID = Subject.SubjectID AND
Timetable.Room_ID = Room.Room_ID AND
CourseMembership.Course_ID = Course.CourseID AND
CourseMembership.Student_ID = 123) r
RIGHT JOIN TimetablePeriods ON
TimetablePeriods.PeriodID = r.Period_ID
ORDER BY TimetablePeriods.SortOrder ASC

Related

SQL Distinct based on different colum

I have problem to distinct values on column based on other column. The case study is:
Table: List
well | wbore | op|
------------------
wella|wbore_a|op_a|
wella|wbore_a|op_b|
wella|wbore_a|op_b|
wella|wbore_b|op_c|
wella|wbore_b|op_c|
wellb|wbore_g|op_t|
wellb|wbore_g|op_t|
wellb|wbore_h|op_k|
So, I want the output to be appear in different field/column like:
well | total_wbore | total_op
----------------------------
wella | 2 | 3
---------------------------
wellb | 2 | 2
the real study case come from different table but to simplify it I just assume this case happened in 1 table.
The sql query that I tried:
SELECT well.well_name, wellbore.wellbore_name, operation.operation_name, COUNT(*)
FROM well
INNER JOIN wellbore ON wellbore.well_uid = well.well_uid
INNER JOIN operation ON wellbore.well_uid = operation.well_uid
GROUP BY well.well_name,wellbore.wellbore_name
HAVING COUNT(*) > 1
But this query is to calculate the duplicate row which not meet the requirement. Anyone can help?
you need to use count distinct
SELECT
count(distinct wellbore.wellbore_name) as total_wbore
count(distinct operation.operation_name) as total_op
FROM well
INNER JOIN wellbore ON wellbore.well_uid = well.well_uid
INNER JOIN operation ON wellbore.well_uid = operation.well_uid
Final query:
SELECT
well.well_name,
COUNT(DISTINCT wellbore.wellbore_name) AS total_wbore,
COUNT(DISTINCT operation.operation_name) AS total_op
FROM well
INNER JOIN wellbore ON wellbore.well_uid = well.well_uid
INNER JOIN operation ON wellbore.well_uid = operation.well_uid
GROUP BY well.well_name

Static SQL query replace to dynamic column

I have following query:
http://www.sqlfiddle.com/#!9/752e34/3
This query use SELECT in SELECT queries.
"SELECT a.*
,(SELECT s.value FROM tbl_scd AS s WHERE s.tag_id = 1 AND s.main_id = a.id ORDER BY s.date_time DESC LIMIT 1) AS title
,(SELECT s.value FROM tbl_scd AS s WHERE s.tag_id = 2 AND s.main_id = a.id ORDER BY s.date_time DESC LIMIT 1) AS alt
FROM tbl_main AS a
WHERE 1;"
Now I'm looking for a solution to add a new row into tbl_tag without change the above query (that the SELECT in SELECT part will be dynamic) to get a reference to tbl_tag
To get this:
+----+---------------+-----------+-----------+--------------+
| id | date | title | alt | new_column |
+----+---------------+-----------+-----------+--------------+
| 1 | 2018-10-10 | test1-1 | test1-3 | NULL |
| 2 | 2018-10-11 | test2-1 | test2-1 | NULL |
+----+---------------+-----------+-----------+--------------+
It would be great to get an idea or help.
Thanks
Your last comment on your question about using JOIN makes it clearer to me (I think) what you are after. JOINs will definitely help you a lot here, in place of the rather cumbersome query you are currently using.
Try this:
SELECT
tbl_main.date,
tblA.value AS title,
tblB.value AS alt
FROM
tbl_main
INNER JOIN (SELECT main_id, tag_id, value
FROM tbl_scd
INNER JOIN tbl_tag ON (tbl_scd.tag_id = tbl_tag.id)
WHERE tbl_tag.name = 'title') tblA
ON (tbl_main.id = tblA.main_id)
INNER JOIN (SELECT main_id, tag_id, value
FROM tbl_scd
INNER JOIN tbl_tag ON (tbl_scd.tag_id = tbl_tag.id)
WHERE tbl_tag.name = 'alt') tblB
ON (tbl_main.id = tblB.main_id);
I think this will get you much closer to a general solution to what it looks like you are trying to achieve, or at least point you in a good direction with using JOINs.
I also think you might benefit from re-thinking your database design, because this kind of pivoting rows from one table into columns in a query output can be an indicator that the data might be better off structured differently.
In any case, I hope this helps.

sql query seems to return no result

Certain queries that work fine in the mysql command line client don't seem to return
anything at all (no error, no result) in phpMyAdmin.
I get the impression it's related to nested queries.
Here's an example:
select * from
(select
(select min(data) from census2 where
census2.monkey=samplecollection.monkeyid and
date(collectiontime)=date(census2.timestamp)) census
from samplecollection,biograph,hormone,plate
where plate.hormone='Testosterone' and hormone.plateid=plate.plateid and
not specialcontentcode like '%UR%' and thermos!='F' and
biograph.id=monkeyid and samplecollection.sampleid=hormone.sampleid)
t1 limit 3;
+--------+
| census |
+--------+
| GFF |
| GRF |
| GRF |
+--------+
3 rows in set (5.09 sec)
If I extract the inner query (and put the limit on it) then I get a result.
The structure of your query if too complex and clearly not optimized and it may cause the problem you've releved.
Here is the same query with a bit of refactoring:
SELECT *
FROM samplecollection SC
INNER JOIN (SELECT C2.monkeyid
,MIN(C2.data) AS [census]
FROM census2 C2
INNER JOIN samplecollection SC2 ON SC2.monkeyid = C2.monkey
AND DATE(SC2.collectiontime) = DATE(C2.timestamp)
AND SC2.thermos != 'F'
AND SC2.specialcontentcode NOT LIKE '%UR%'
GROUP BY C2.monkeyid) T ON T.monkeyid = SC.monkeyid
INNER JOIN biograph B ON B.id = SC.monkeyid
INNER JOIN hormone H ON H.sampleid = SC.sampleid
INNER JOIN plate P ON P.plateid = H.plateid
AND P.hormone = 'Testosterone'
LIMIT 3
The answer comes late but it may be useful for some people to see how some very complex query structures can be simplified when using JOIN clauses.
Hope this will help.

How would I execute this complex conditional multi-table MySQL join (queries provided)?

Ok I have a few tables tables. I am only showing relevant fields:
items:
----------------------------------------------------------------
name | owner_id | location_id | cab_id | description |
----------------------------------------------------------------
itm_A | 11 | 23 | 100 | Blah |
----------------------------------------------------------------
.
.
.
users:
-------------------------
id | name |
-------------------------
11 | John |
-------------------------
.
.
.
locations
-------------------------
id | name |
-------------------------
23 | Seattle |
-------------------------
.
.
.
cabs
id | location_id | name
-----------------------------------
100 | 23 | Cool |
-----------------------------------
101 | 24 | Cool |
-----------------------------------
102 | 24 |thecab |
-----------------------------------
I am trying to SELECT all items (and their owner info) that are from Seattle OR Denver, but if they are in Seattle they can only be in the cab NAMED Cool and if they are in Denver they can only be in the cab named 'thecab' (not Denver AND cool).
This query doesn't work but I hope it explains what I am trying to accomplish:
SELECT DISTINCT
`item`.`name`,
`item`.`owner_id`,
`item`.`description`,
`user`.`name`,
IF(`loc`.`name` = 'Seattle' AND `cab`.`name` = 'Cool',1,0) AS `cab_test_1`,
IF(`loc`.`name` = 'Denver' AND `cab`.`name` = 'thecab',1,0) AS `cab_test_2`,
FROM `items` AS `item`
LEFT JOIN `users` AS `user` ON `item`.`owner_id` = `user`.`id`
LEFT JOIN `locations` AS `loc` ON `item`.`location_id` = `loc`.`location_id`
LEFT JOIN `cabs` AS `cab` ON `item`.`cab_id` = `cabs`.`id`
WHERE (`loc`.`name` IN ("Seattle","Denver")) AND `cab_test_1` = 1 AND `cab_test_2` = 1
I'd rather get rid of the IFs is possible. It seems inefficent, looks clunky, and is not scalable if I have a lot of location\name pairs
Try this:
SELECT DISTINCT
item.name,
item.owner_id,
item.description,
user.name
FROM items AS item
LEFT JOIN users AS user ON item.owner_id = user.id
LEFT JOIN locations AS loc ON item.location_id = loc.id
LEFT JOIN cabs AS cab ON item.cab_id = cabs.id
WHERE ((loc.name = 'Seattle' AND cab.name = 'Cool')
OR (loc.name = 'Denver' AND cab.name = 'thecab'))
My first thought is to store the pairs of locations and cab names in a separate table. Well not quite a table, but a derived table generated by a subquery.
You still have the problem of pivoting the test results into separate columns. The code can be simplified by making use of mysql boolean expressions, which get rid of the need for a case or if.
So, the approach is to use the same joins you have (although left join is not needed because the comparison on cab.name turns them in to inner joins). Then add a table of the pairs you are looking for, along with the "test name" for the pair. The final step is an explicit group by and a check whether conditions are met for each test:
SELECT i.`name`, i.`owner_id`, i.`description`, u.`name`,
max(pairs.test_name = 'test_1') as cab_test_1,
max(pairs.test_name = 'test_2') as cab_test_2
FROM `items` i LEFT JOIN
`users` u
ON i.`owner_id` = u.`id` LEFT JOIN
`locations` l`
ON i.`location_id` = l.`location_id` left join
`cabs` c
ON i.`cab_id` = c.`id` join
(select 'test_1' as testname, 'Seattle' as loc, 'cool' as cabname union all
select 'test_2', 'Denver', 'thecab'
) pairs
on l.name = pairs.name and
l.cabname = c.name
group by i.`name`, i.`owner_id`, i.`description`, u.`name`;
To add in additional pairs, add them into the pairs table along, and add an appropriate line in the select for the test flag.

Joining two Tables in SQL, but using an earler Row if no Result on the right Table

I have got these three tables in MySQL:
+----------------------------------------------+
| ComputerConfigs |
+----------+-----------------------------------+
| ConfigID | ComputerID | LastChangeDate | ... |
+----------+-----------------------------------+
+-------------------+
| Software |
+------------+------+
| SoftwareID | Name |
+------------+------+
+-----------------------------------------------------+
| ComputerHasSoftware |
+------------+----------------------------------------+
| ComputerID | ConfigID | SoftwareID | LastChangeDate |
+------------+----------------------------------------+
Each time, a ComputerConfig changes, there will be written a new row into ComputerConfigs. So I am able to see all changes from the past, too.
Now I am trying to also track the changes of the software. This does not happen that often, so I only want to save new rows into the ComputerHasSoftware table, if somebody has really added or deleted something.
I would like to write a query, which returns all ComputerConfigs (from the past till now) with the software that has been installed. So if there wasn't added a row into ComputerHasSoftware belonging to a ComputerConfig, then it should take the latest one before the timestamp of the ComputerConfig.
I thought about doing a query like this, which simply joins the three tables with each other:
SELECT
FROM ComputerConfigs
LEFT OUTER JOIN ComputerHasSoftware
ON ComputerHasSoftware.ComputerID = ComputerConfigs.ComputerID
LEFT OUTER JOIN Software
ON Software.SoftwareID = ComputerHasSoftware.ComputerID
But as you can imagine, this query leads to a wrong result, because it relates old software to computers, which does not belong there. Changing the join condition to ComputerHasSoftware.ConfigID = ComputerConfigs.ConfigID also won't work, because then it misses the ones where no data has been provided and instead it should fall back to the latest one available.
How can I modify my query in order to achieve this? Or is there even a better solution using subqueries?
MySQL is limitted in the tricks it can employ to speed this up. Using a correlated sub-query will work, but you'll need to check performance.
SELECT
*
FROM
ComputerConfigs
LEFT OUTER JOIN
ComputerHasSoftware
ON ComputerHasSoftware.ComputerID = ComputerConfigs.ComputerID
AND ComputerHasSoftware.LastChangeDate = (
SELECT MAX(LastChangeDate)
FROM ComputerHasSoftware
WHERE ComputerID = ComputerConfigs.ComputerID
AND LastChangeDate <= ComputerConfigs.LastChangeDate
)
LEFT OUTER JOIN
Software
ON Software.SoftwareID = ComputerHasSoftware.ComputerID
You need to select gap date periods:
select
ConfigID
,f.LastChangeDate as fromDate
,coalesce( min( t.LastChangeDate ), now() ) as toDate
from
ComputerConfigs f
left outer join
ComputerConfigs t
on f.ComputerID = t.ComputerID
where
t.LastChangeDate > f.LastChangeDate
group by
ConfigID, f.LastChangeDate
Then join it in this way:
SELECT *
FROM ComputerConfigs
LEFT OUTER JOIN ComputerHasSoftware
ON ComputerHasSoftware.ComputerID = ComputerConfigs.ComputerID
LEFT OUTER JOIN ( above subquery) S
ON S.ConfigID = ComputerConfigs.ConfigID
LEFT OUTER JOIN Software
ON Software.ComputerID = ComputerHasSoftware.ComputerID and
Software.LastChangeDate between S.fromDate and S.toDate
Notice that you should replace above subquery by my first query. In your question I have not understand if you need a between condition or a < or > condition. Be free to accommodate query to your needs.
Enjoy.