Mysql Inner Join a table with matching key column but null - mysql

I am trying to join 4 tables.
registration_mt, admission_mt, student_mt, and schoolyear_student_lt
Currently registration_mt, admission_mt and student_mt tables have 1 record each.
There are no problems inner-joining all three of them except for schoolyear_student_lt which has no records yet.
I want to be able to get the columns of schoolyear_student_lt and join it with the result set of the 3 other tables even when the matching key student_id HAS OR HAS-NO records
I want to add schoolyear_id, student_id, gradelevel_id, section_id, passedfrom schoolyear_student_lt to the result set I get from my join query where isActive = 0;
CREATE table statements
CREATE TABLE `registration_mt` (
`registration_id` int(11) NOT NULL AUTO_INCREMENT,
`student_type` varchar(45) NOT NULL,
PRIMARY KEY (`registration_id`)
) ;
CREATE TABLE `admission_mt` (
`admission_id` int(11) NOT NULL AUTO_INCREMENT,
`registration_id` int(11) NOT NULL,
`isComplete` bit(1) NOT NULL DEFAULT b'0',
`completion_date` datetime DEFAULT NULL,
PRIMARY KEY (`admission_id`),
UNIQUE KEY `registration_id_UNIQUE` (`registration_id`),
CONSTRAINT `fk_admission_mtTABLE_registration_idCOL` FOREIGN KEY (`registration_id`) REFERENCES `registration_mt` (`registration_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ;
CREATE TABLE `schoolyear_student_lt` (
`schoolyear_id` int(11) NOT NULL,
`student_id` int(11) NOT NULL,
`gradelevel_id` int(11) NOT NULL,
`section_id` int(11) DEFAULT NULL,
`passed` bit(1) DEFAULT b'0',
UNIQUE KEY `uk_schoolyear_idCOL_student_idCOL` (`schoolyear_id`,`student_id`)
);
CREATE TABLE `student_mt` (
`student_id` int(11) NOT NULL AUTO_INCREMENT,
`registration_id` int(11) NOT NULL,
`entry_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`isGraduated` bit(1) NOT NULL DEFAULT b'0',
`date_graduated` datetime DEFAULT NULL,
`isActive` bit(1) DEFAULT b'0' ,
PRIMARY KEY (`student_id`),
UNIQUE KEY `registration_id_UNIQUE` (`registration_id`),
KEY `fk_student_mtTABLE_registration_idCOL_idx` (`registration_id`),
CONSTRAINT `fk_student_mtTABLE_registration_idCOL` FOREIGN KEY (`registration_id`) REFERENCES `registration_mt` (`registration_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
);
My INNER-JOIN statement
SELECT
a.admission_id,
a.isComplete,
a.completion_date,
s.student_id,
s.entry_date,
s.isGraduated,
s.date_graduated,
s.isActive
FROM admission_mt a
INNER JOIN registration_mt r ON a.registration_id = r.registration_id
INNER JOIN student_mt s ON s.registration_id = a.registration_id
-- INNER JOIN schoolyear_student_lt sslt ON s.student_id = sslt.student_id
-- LEFT JOIN schoolyear_student_lt sslt ON s.student_id = sslt.student_id
WHERE
s.isActive = 0 ;
INSERT statements to registration_mt, admission_mt, and student_mt tables
START TRANSACTION;
INSERT INTO registration_mt(student_type) VALUES('New'); -- insert to registration_mt
INSERT INTO admission_mt(registration_id)
VALUES(LAST_INSERT_ID()); --insert to admissiont_mt
COMMIT;
START TRANSACTION;
UPDATE admission_mt
SET isComplete = 1
WHERE registration_id = 1;
INSERT INTO student_mt(registration_id)
VALUES(1); --insert to student_mt
COMMIT;
I commented out the -- INNER JOIN schoolyear_student_lt sslt ON s.student_id = sslt.student_id because I get no results when I include it in my select query since there are no records yet in schoolyear_student_lt table.
This is the current resultset I get. There are no columns from schoolyear_student_lt table which I wish to add even when NULL and even when NOT NULL on schoolyear_student_lt.student_id
LEFT-JOIN doesn't display the columns from schoolyear_student_lt when NULL
Thanks.

You should try the LEFT OUTER JOIN
The idea of an left outer join is that it takes the rows of left relation and fills the fields of the right relation with NULL if there is no join
partner.
There is also a right outer join...

Related

Unknown column in 'on clause' after MySQL upgrade

I've been updating some old code which used PHP4 and MySQL 4.1 up to MySQL 5.6 / MariaDB 10. I've had a few issues with SQL JOINs
and precedence but this one has really stumped me and is giving me the error -
#1054 - Unknown column 'grouping_id' in 'on clause'
I've tried messing about with the order of the JOIN statements in the query below but I haven't had any success yet, as mentioned this query worked fine on MySQL 4.
SELECT
team.team_id,
team.team_name,
competition.rel_sport_id,
country.country_name
FROM
team
LEFT JOIN team_grouping ON(
rel_team_id = team_id AND team_grouping.rel_grouping_id = grouping_id
)
LEFT JOIN grouping ON grouping_id = team_grouping.rel_grouping_id
LEFT JOIN country ON team.rel_country_id = country_id
LEFT JOIN sport ON team.rel_sport_id = sport_id
LEFT JOIN competition_country ON(
rel_competition_id = competition_id AND competition_country.rel_country_id = country_id
)
LEFT JOIN competition ON competition_id = '985'
WHERE
team.rel_country_id = competition_country.rel_country_id AND team.rel_sport_id = competition.rel_sport_id AND grouping_id = '3'
ORDER BY
team_name
Can anyone help with what could be wrong with the above query?
EDIT - Added table schemas:
CREATE TABLE `grouping` (
`grouping_id` int(11) NOT NULL AUTO_INCREMENT,
`grouping_name` varchar(100) DEFAULT NULL,
PRIMARY KEY (`grouping_id`)
) ENGINE=MyISAM AUTO_INCREMENT=6 DEFAULT CHARSET=latin
CREATE TABLE `team` (
`team_id` int(11) NOT NULL AUTO_INCREMENT,
`team_name` varchar(200) DEFAULT NULL,
`image` varchar(100) DEFAULT NULL,
`rel_country_id` int(11) DEFAULT NULL,
`rel_sport_id` int(11) DEFAULT NULL,
`modified_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`team_id`),
KEY `IDX_team_1` (`rel_country_id`),
KEY `IDX_team_2` (`rel_sport_id`)
) ENGINE=MyISAM AUTO_INCREMENT=11918 DEFAULT CHARSET=latin1
CREATE TABLE `country` (
`country_id` int(11) NOT NULL AUTO_INCREMENT,
`country_name` varchar(40) DEFAULT NULL,
`image` varchar(40) DEFAULT NULL,
`rel_geographic_id` int(11) DEFAULT NULL,
PRIMARY KEY (`country_id`),
KEY `IDX_country_2` (`rel_geographic_id`)
) ENGINE=MyISAM AUTO_INCREMENT=237 DEFAULT CHARSET=latin1
CREATE TABLE `competition` (
`competition_id` int(11) NOT NULL AUTO_INCREMENT,
`competition_name` varchar(200) DEFAULT NULL,
`rel_sport_id` int(11) DEFAULT NULL,
`rel_grouping_id` int(11) DEFAULT NULL,
`rel_competition_tz_id` int(11) NOT NULL DEFAULT '2',
`modified_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`competition_id`),
KEY `IDX_competition_1` (`rel_sport_id`),
KEY `IDX_competition_2` (`rel_grouping_id`)
) ENGINE=MyISAM AUTO_INCREMENT=1133 DEFAULT CHARSET=latin1
CREATE TABLE `sport` (
`sport_id` int(11) NOT NULL AUTO_INCREMENT,
`sport_name` varchar(40) DEFAULT NULL,
`image` varchar(40) DEFAULT NULL,
`modified_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`sport_id`)
) ENGINE=MyISAM AUTO_INCREMENT=29 DEFAULT CHARSET=latin1
CREATE TABLE `competition_country` (
`rel_competition_id` int(11) NOT NULL DEFAULT '0',
`rel_country_id` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`rel_competition_id`,`rel_country_id`),
KEY `IDX_competition_country_1` (`rel_competition_id`),
KEY `IDX_competition_country_2` (`rel_country_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1
Table names in an ON clause can only refer to tables preceding it in the query. When you're joining through a relationship table, the first ON clause just relates with the table before it, you relate to the second table in the next ON clause.
So the ON clause for team_grouping should only have rel_team_id = team_id, and the ON clause for competition_country should only have rel_country_id = country_id.
I don't think you need the WHERE clause at the end. Those relationships should be implied already by the earlier joins. And since you're doing a LEFT JOIN with grouping, you should put restrictions on that table in the ON clause; otherwise, the null values from non-matching rows will be filtered out by the WHERE clause.
SELECT
team.team_id,
team.team_name,
competition.rel_sport_id,
country.country_name
FROM team
LEFT JOIN team_grouping ON rel_team_id = team_id
LEFT JOIN grouping ON grouping_id = team_grouping.rel_grouping_id AND grouping_id = 3
LEFT JOIN country ON team.rel_country_id = country_id
LEFT JOIN sport ON team.rel_sport_id = sport_id
LEFT JOIN competition_country ON competition_country.rel_country_id = country_id
LEFT JOIN competition ON competition_id = '985' AND competition_id = competition_country.rel_competition_id
ORDER BY team_name

inner join not giving results as expected

Product_table
Product_table_link
This be the product_table data that is stored inside the database.
This is the product_table_link data that is present inside the DB.I was trying to join these two tables where the product code=something.For instance let us take xyz as the product.
I was hoping to get the combined results of the two without any nulls present.
I tried :
SELECT s1.* FROM (SELECT p1.* FROM product_table p1 INNER JOIN product_table_link p2
ON p1.product_code=p2.product_code ) s1 WHERE product_code="xyz"
But the result is not the combination of the both tables rather it shows me the product_table.
CREATE TABLE `product_table` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`product_name` varchar(60) NOT NULL,
`product_code` varchar(60) NOT NULL,
`product_description` text,
`product_type` varchar(20) NOT NULL,
`product_image_path` varchar(60) NOT NULL,
`product_company_name` varchar(20) NOT NULL,
`product_company_id` varchar(60) NOT NULL,
`product_landing_page` varchar(15) NOT NULL,
`product_shape` varchar(20) DEFAULT NULL,
`product_flavour` varchar(20) NOT NULL,
`product_veg_mark` varchar(8) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `unique` (`product_code`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1
CREATE TABLE `product_table_link` (
`product_code` varchar(60) NOT NULL,
`product_weight` varchar(5) NOT NULL,
`product_price` int(5) NOT NULL,
`product_quantity` int(5) NOT NULL,
PRIMARY KEY (`product_code`,`product_weight`),
CONSTRAINT `product_table_link_ibfk_1` FOREIGN KEY (`product_code`) REFERENCES `product_table` (`product_code`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
How do i get the combined results of the two tables?
Change your subquery to this
SELECT * FROM product_table p1 INNER JOIN product_table_link p2
ON p1.product_code=p2.product_code
You were selecting just from p1.
You were doing select p1.* which will only select rows from p1. Inner join means you want to select rows from different tables on the basis of some conditions. You need to do select * which will select all the rows filtered from the join condition.
Your simplified final query.
SELECT s1.* FROM (SELECT p1.*,p2.product_weight,p2.product_price,p2.product_quantity
FROM product_table p1 INNER JOIN product_table_link p2
ON p1.product_code=p2.product_code)
s1 WHERE s1.product_code="xyz"
SELECT p1.* FROM product_table p1 INNER JOIN product_table_link p2
ON p1.product_code=p2.product_code
In the above subquery you are selecting values from p1 only. So, you have to change your query to what FallAndLearn has written.

How can I query for rows with latest date and do an inner join on a second table?

All the examples I've seen show how to do an inner join using an alias to get rows with the latest date. I can do that with my data but I also want to do an inner join on another table and can't figure how to do both with the same query.
Here are the two tables:
CREATE TABLE `titles` (
`titleID` int(11) unsigned NOT NULL AUTO_INCREMENT,
`titlename` tinytext NOT NULL,
`url` varchar(255) DEFAULT '',
`category` int(2) unsigned NOT NULL,
`postdate` date NOT NULL,
PRIMARY KEY (`titleID`),
KEY `category` (`category`),
CONSTRAINT `titles_ibfk_1` FOREIGN KEY (`category`) REFERENCES `categories` (`catid`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1;
CREATE TABLE `stats` (
`statid` int(11) unsigned NOT NULL AUTO_INCREMENT,
`score` decimal(3,2) DEFAULT NULL,
`views` int(11) unsigned DEFAULT NULL,
`favs` int(11) DEFAULT NULL,
`comments` int(11) DEFAULT NULL,
`updatedate` date NOT NULL,
`title` int(11) unsigned NOT NULL,
PRIMARY KEY (`statid`),
KEY `title` (`title`),
CONSTRAINT `stats_ibfk_1` FOREIGN KEY (`title`) REFERENCES `titles` (`titleID`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=latin1;
My goals:
1) I want a query that gives me all the latest stats for each title.
2) I want to see the text name of the title (from the titles table).
I can use this query to get the latest score for each title.
select t.score, t.views, t.favs, t.comments, t.updatedate, t.title
from stats t
inner join (
select title, max(updatedate) as updatedate
from stats
GROUP BY title
) tm on t.title = tm.title and t.updatedate = tm.updatedate
But the problem with this query is that it displays the title column from stats which is an int. I want the text name of the title.
I can do this to get the title name and the score, but then I'm not getting the row with the latest date.
select titlename, score, updatedate
from stats
inner join titles
on titleid = title
How can I write a query that achieves both my goals?
You need to join the title table in this case as
select
s1.score,
s1.views,
s1.favs,
s1.comments,
s1.updatedate,
t.titlename
from titles t
join stats s1 on s1.title = t.titleID
join (
select title, max(updatedate) as updatedate
from stats
GROUP BY title
) s2 on s2.title = s1.title and s1.updatedate = s2.updatedate

How to optimize this query as the in array seems to slow things down significantly

I am looking to find out the best way to optimize a query like this:
SELECT
a.ID,
a.ECPCodeID,
a.RegDate,
a.BusName,
a.City,
a.AccountNum,
b.ID as RepCodeID,
b.RepCode
FROM ECPs_Registration a,
Reps_Codes b
WHERE (SUBSTR(a.PostalCode,1,5)IN(SELECT
SUBSTR(Zip,1,5)
FROM Reps_Zip
WHERE RepCodeID = b.ID)
AND a.AccountNum NOT IN(SELECT
ShipTo
FROM Reps_ShipTo))
OR a.AccountNum IN(SELECT
ShipTo
FROM Reps_ShipTo
WHERE RepCodeID = b.ID)
ORDER BY b.RepCode,a.BusName,a.City
I know there are more factors involved such as indexes and such, I just am asking about the query part of it for now. Mainly, since I have to go through the Reps_ShipTo and Reps_Zip tables for tons of records. I thought about changing something like:
a.AccountNum NOT IN (SELECT ShipTo FROM Reps_ShipTo)
INTO
(SELECT count(*) FROM Reps_ShipTo WHERE a.AccountNum = ShipTo) = 0
Not sure if that is proper or if there is a better way. Any help would be appreciated. Thanks.
EDIT:
Schema:
CREATE TABLE IF NOT EXISTS `ECPs_Codes` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`ECPCode` char(4) NOT NULL,
PRIMARY KEY (`ID`),
KEY `ECPCode` (`ECPCode`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 ;
CREATE TABLE IF NOT EXISTS `ECPs_Registration` (
`RegDate` datetime NOT NULL,
`ID` int(10) NOT NULL AUTO_INCREMENT,
`ECPCodeID` int(11) NOT NULL,
`FirstName` varchar(200) NOT NULL,
`LastName` varchar(200) NOT NULL,
`BusName` varchar(200) NOT NULL,
`Address` varchar(200) NOT NULL,
`Address2` varchar(200) NOT NULL,
`City` varchar(100) NOT NULL,
`Province` char(2) NOT NULL,
`Country` varchar(100) NOT NULL,
`PostalCode` varchar(10) NOT NULL,
`Email` varchar(200) NOT NULL,
`AccountNum` int(8) NOT NULL,
PRIMARY KEY (`ID`),
KEY `ECPCodeID` (`ECPCodeID`),
KEY `PostalCode` (`PostalCode`),
KEY `AccountNum` (`AccountNum`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
CREATE TABLE IF NOT EXISTS `Reps_Codes` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`Name` varchar(50) NOT NULL,
`RepCode` varchar(16) NOT NULL,
`AllAccess` tinyint(4) NOT NULL,
PRIMARY KEY (`ID`),
KEY `RepCode` (`RepCode`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
CREATE TABLE IF NOT EXISTS `Reps_ShipTo` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`RepCodeID` int(11) NOT NULL,
`ShipTo` varchar(20) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`ID`),
KEY `RepID` (`RepCodeID`),
KEY `ShipTo` (`ShipTo`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE IF NOT EXISTS `Reps_Zip` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`RepCodeID` int(11) NOT NULL,
`Zip` varchar(10) NOT NULL,
PRIMARY KEY (`ID`),
KEY `RepCodeID` (`RepCodeID`),
KEY `Zip` (`Zip`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
There are two things that massively hurt performance on your query.
You are joining two tables by combining multiple conditions, each needing subqueries
You're doing a join on two tables using SUBSTR(Zip,1,5)=SUBSTR(postalcode,1,5)
The logic behind your query seems to be something like:
For every ECPs_Registration find the matching record in Rep_Codes
using the following rules:
If there is a matching record in Reps_ShipTo, to for that registration, use that table to look it up (primary match)
If there isn't a matching record in Reps_ShipTo, seek through Reps_Zip for a matching RepCode by Zipcode-match (secondary)
Now if the above fully describes your situation, you should probably start off by redesigning your database.
The Reps_ShipTo table creates a 0:N relationship between ECPs_Registration and Rep_Codes. Such relations don't need an extra table - they can simply be stored as nullable foreign keys - in your case a RepCodeId in ECPs_Registration would do the trick, and would remove the entire Reps_ShipTo table from the database.
You should probably also create (yes, redundant) extra columns that only store the first 5 letters of the zip codes in both ECPs_Registration and Reps_Zip. This will allow simple equality matches instead of the SUBSTR-functions. Or, you might decide to do this match only once for every record, and store the result in above RepCodeId, which totally eliminates the dual join.
The following query assumes you for some reason don't want to or can't change your database:
SELECT
a.ID, a.ECPCodeID, a.RegDate, a.BusName, a.City, a.AccountNum,
CASE (b1.ID IS NOT NULL, b1.ID, b2.ID) as RepCodeID,
CASE (b1.ID IS NOT NULL, b1.RepCode, b2.RepCode) as MyRepCode
FROM ECPs_Registration a
LEFT JOIN Reps_ShipTo ON (Reps_ShipTo.Shipto=a.AccountNum)
LEFT JOIN Rep_Codes b1 ON (b1.ID=Reps_ShipTo.RepCodeId)
LEFT JOIN Reps_Zip ON (SUBSTR(Zip,1,5)=SUBSTR(a.postalcode,1,5))
LEFT JOIN Rep_Codes b2 ON (b2.ID=Reps_Zip.RepCodeID)
ORDER BY MyRepCode,a.BusName,a.City
Without your database schema and sample data, I have no way to test if above query actually works and has the same result as your original.
SELECT
a.ID,
a.ECPCodeID,
a.RegDate,
a.BusName,
a.City,
a.AccountNum,
b.ID as RepCodeID,
b.RepCode
FROM ECPs_Registration a, Reps_Codes b
INNER JOIN Reps_Zip as r on SUBSTR(a.PostalCode,1,5) = SUBSTR(r.Zip,1,5)
LEFT JOIN Reps_ShipTo as rs on a.AccountNum = rs.ShipTo
LEFT JOIN ShipTo as s on a.AccountNum = s.ShipTo
WHERE (s.id is null or rs.id is null)
ORDER BY b.RepCode,a.BusName,a.City

How can I select the current holder for each championship?

I want to select the current holders for each championship in a championships table, and return NULL for championships that have not had any winners yet.
Here are the create statements for the two tables:
CREATE TABLE `championships` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`friendly_name` varchar(255) NOT NULL,
`rank` int(2) unsigned NOT NULL DEFAULT '1',
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`),
UNIQUE KEY `friendly_name` (`friendly_name`)
) ENGINE=InnoDB;
CREATE TABLE `title_history` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`championship` int(10) unsigned NOT NULL,
`winner` varchar(255) NOT NULL,
`date_from` date NOT NULL,
`location` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
KEY `championship` (`championship`)
) ENGINE=InnoDB;
ALTER TABLE `title_history` ADD CONSTRAINT `title_history_ibfk_1` FOREIGN KEY (`championship`) REFERENCES `championships` (`id`) ON UPDATE CASCADE;
What MySQL statement would return the data set I wanted?
Assuming you're storing the winner of a championship as the primary key/id of the holder, something like this should work. You might want to add in another join to get the actual name of the team from another table though.
Because LEFT join will only select rows from the 'right' table when there is a match, everything that doesn't have one should come back as NULL.
SELECT name, [holder]
FROM championships AS c
LEFT JOIN title_history AS h ON c.winner = h.id
EDITED VERSION:
With further insight into your tables and from your comment, maybe try this subselect:
SELECT friendly_name,
(SELECT winner FROM title_history WHERE championship = c.id ORDER BY date_from DESC LIMIT 1)
FROM championships AS c
ORDER BY name
If I understand your structure correctly, that ought to get the last winner of each championship?