MySQL multiple table joins
I. Intro
I am building a support system, where a user from a certain country raises an issue on a certain category, and experts from that country, administrative division, and category get assigned the issue.
E.x. User from country Germany
with zip code 1000
rises an issue on category Software
. Experts from country Germany
, and/or province with zip code boundaries MIN_PROVINCE_ZIPCODE <= 1000 >= MAX_PROVINCE_ZIPCODE
, and/or region with zip code boundaries MIN_REGION_ZIPCODE <= 1000 >= MAX_REGION_ZIPCODE
, and category Software
get assigned the issue.
I.e.: Select all issues where issue country is equal to expert country, and issue category is equal to one of experts' categories, and/or issue zip code is greater or equal to minimum province zip code and smaller or equal to maximum province zip code, and/or issue zip code is greater or equal to region minimum zip code and issue code is smaller or equal to zip codes.
"and/or" means if experts are assigned to take issues from specific administrative division(s), if they are not, then assign them everything that matches their country and category
II. Database Schemas & Records
*Keep in mind!*
a) Experts can be part of...
- one, multiple, or no category(ies)
- one, multiple, or no province(s)
- one, multiple, or no region(s)
b) If experts are NOT part of...
- a category, then no issues will be assigned to them
- a province, then all issues for their country and category will be assigned to them
- a region, then all issues for their province(s) will be assigned to them
1. Schemas
CREATE TABLE IF NOT EXISTS `categories` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(300) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `provinces` (
`id` bigint(11) unsigned NOT NULL AUTO_INCREMENT,
`country` varchar(300) NOT NULL,
`province` varchar(300) NOT NULL,
`min_zipcode` int(5) unsigned NOT NULL,
`max_zipcode` int(5) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `regions` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`provinceid` int(11) unsigned NOT NULL,
`region` varchar(300) NOT NULL,
`min_zipcode` int(5) unsigned NOT NULL,
`max_zipcode` int(5) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `issues` (
`id` bigint(11) unsigned NOT NULL AUTO_INCREMENT,
`categoryid` int(11) unsigned NOT NULL,
`country` varchar(150) NOT NULL,
`zipcode` int(5) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `experts` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`country` varchar(150) NOT NULL DEFAULT 'none',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `experts_categories` (
`expertid` int(11) unsigned NOT NULL,
`categoryid` int(11) unsigned NOT NULL,
PRIMARY KEY (`expertid`,`categoryid`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `experts_provinces` (
`expertid` int(11) unsigned NOT NULL,
`provinceid` int(11) unsigned NOT NULL,
PRIMARY KEY (`expertid`,`provinceid`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `experts_regions` (
`expertid` int(11) NOT NULL,
`regionid` int(11) NOT NULL,
PRIMARY KEY (`expertid`,`regionid`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
2. Records
INSERT INTO `categories` (`id`, `name`) VALUES
(1, 'Software'),
(2, 'Hardware');
INSERT INTO `experts` (`id`, `country`) VALUES
(1, 'Germany'),
(2, 'France'),
(3, 'Germany');
INSERT INTO `experts_categories` (`expertid`, `categoryid`) VALUES
(1, 1),
(1, 2),
(2, 1),
(3, 1);
INSERT INTO `experts_provinces` (`expertid`, `provinceid`) VALUES
(1, 4),
(2, 6),
(2, 7);
INSERT INTO `experts_regions` (`expertid`, `regionid`) VALUES
(1, 8),
(1, 10);
INSERT INTO `issues` (`id`, `categoryid`, `country`, `zipcode`) VALUES
(1, 2, 'Germany', 2100),
(2, 1, 'France', 1900),
(3, 1, 'Germany', 1500),
(4, 2, 'Germany', 2800),
(5, 2, 'France', 1850);
INSERT INTO `provinces` (`id`, `country`, `province`, `min_zipcode`, `max_zipcode`) VALUES
(1, 'Germany', 'Province One', 1000, 1299),
(2, 'Germany', 'Province Two', 1300, 1499),
(3, 'Germany', 'Province Three', 1500, 1999),
(4, 'Germany', 'Province Four', 2000, 2899),
(5, 'France', 'Province One', 1000, 1799),
(6, 'France', 'Province Two', 1800, 2199),
(7, 'France', 'Province Three', 2200, 2399);
INSERT INTO `regions` (`id`, `provinceid`, `region`, `min_zipcode`, `max_zipcode`) VALUES
(1, 1, 'Region One', 1000, 1099),
(2, 1, 'Region Two', 1100, 1159),
(3, 1, 'Region Three', 1160, 1299),
(4, 2, 'Region One', 1300, 1400),
(5, 2, 'Region Two', 1401, 1499),
(6, 3, 'Region One', 1500, 1699),
(7, 3, 'Region Two', 1700, 1999),
(8, 4, 'Region One', 2000, 2299),
(9, 4, 'Region Two', 2300, 2599),
(10, 4, 'Region Three', 2600, 2699),
(11, 4, 'Region Four', 2700, 2899),
(12, 5, 'Region One', 1000, 1699),
(13, 5, 'Region Two', 1700, 1799),
(14, 6, 'Region One', 1800, 2000),
(15, 6, 'Region Two', 2001, 2199),
(16, 7, 'Region One', 2200, 2299),
(17, 7, 'Region Two', 2300, 2399);
3. Visual Schemas
mysql> DESC `categories`;
+-------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+----------------+
| id | int(11) unsigned | NO | PRI | NULL | auto_increment |
| name | varchar(300) | NO | | NULL | |
+开发者_如何学JAVA-------+------------------+------+-----+---------+----------------+
mysql> DESC `provinces`;
+-------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------------------+------+-----+---------+----------------+
| id | bigint(11) unsigned | NO | PRI | NULL | auto_increment |
| country | varchar(300) | NO | | NULL | |
| province | varchar(300) | NO | | NULL | |
| min_zipcode | int(5) unsigned | NO | | NULL | |
| max_zipcode | int(5) unsigned | NO | | NULL | |
+-------------+---------------------+------+-----+---------+----------------+
mysql> DESC `regions`;
+-------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+------------------+------+-----+---------+----------------+
| id | int(11) unsigned | NO | PRI | NULL | auto_increment |
| provinceid | int(11) unsigned | NO | | NULL | |
| region | varchar(300) | NO | | NULL | |
| min_zipcode | int(5) unsigned | NO | | NULL | |
| max_zipcode | int(5) unsigned | NO | | NULL | |
+-------------+------------------+------+-----+---------+----------------+
mysql> DESC `issues`;
+------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+---------------------+------+-----+---------+----------------+
| id | bigint(11) unsigned | NO | PRI | NULL | auto_increment |
| categoryid | int(11) unsigned | NO | | NULL | |
| country | varchar(150) | NO | | NULL | |
| zipcode | int(5) | NO | | NULL | |
+------------+---------------------+------+-----+---------+----------------+
mysql> DESC `experts`;
+---------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+------------------+------+-----+---------+----------------+
| id | int(11) unsigned | NO | PRI | NULL | auto_increment |
| country | varchar(150) | NO | | none | |
+---------+------------------+------+-----+---------+----------------+
mysql> DESC `experts_categories`;
+------------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------------+------+-----+---------+-------+
| expertid | int(11) unsigned | NO | PRI | NULL | |
| categoryid | int(11) unsigned | NO | PRI | NULL | |
+------------+------------------+------+-----+---------+-------+
mysql> DESC `experts_provinces`;
+------------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------------+------+-----+---------+-------+
| expertid | int(11) unsigned | NO | PRI | NULL | |
| provinceid | int(11) unsigned | NO | PRI | NULL | |
+------------+------------------+------+-----+---------+-------+
mysql> DESC `experts_regions`;
+----------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+---------+------+-----+---------+-------+
| expertid | int(11) | NO | PRI | NULL | |
| regionid | int(11) | NO | PRI | NULL | |
+----------+---------+------+-----+---------+-------+
4. Visual Records
mysql> SELECT * FROM `categories`;
+----+----------+
| id | name |
+----+----------+
| 1 | Software |
| 2 | Hardware |
+----+----------+
mysql> SELECT * FROM `provinces`;
+----+---------+----------------+-------------+-------------+
| id | country | province | min_zipcode | max_zipcode |
+----+---------+----------------+-------------+-------------+
| 1 | Germany | Province One | 1000 | 1299 |
| 2 | Germany | Province Two | 1300 | 1499 |
| 3 | Germany | Province Three | 1500 | 1999 |
| 4 | Germany | Province Four | 2000 | 2899 |
| 5 | France | Province One | 1000 | 1799 |
| 6 | France | Province Two | 1800 | 2199 |
| 7 | France | Province Three | 2200 | 2399 |
+----+---------+----------------+-------------+-------------+
mysql> SELECT * FROM `regions`;
+----+------------+--------------+-------------+-------------+
| id | provinceid | region | min_zipcode | max_zipcode |
+----+------------+--------------+-------------+-------------+
| 1 | 1 | Region One | 1000 | 1099 |
| 2 | 1 | Region Two | 1100 | 1159 |
| 3 | 1 | Region Three | 1160 | 1299 |
| 4 | 2 | Region One | 1300 | 1400 |
| 5 | 2 | Region Two | 1401 | 1499 |
| 6 | 3 | Region One | 1500 | 1699 |
| 7 | 3 | Region Two | 1700 | 1999 |
| 8 | 4 | Region One | 2000 | 2299 |
| 9 | 4 | Region Two | 2300 | 2599 |
| 10 | 4 | Region Three | 2600 | 2699 |
| 11 | 4 | Region Four | 2700 | 2899 |
| 12 | 5 | Region One | 1000 | 1699 |
| 13 | 5 | Region Two | 1700 | 1799 |
| 14 | 6 | Region One | 1800 | 2000 |
| 15 | 6 | Region Two | 2001 | 2199 |
| 16 | 7 | Region One | 2200 | 2299 |
| 17 | 7 | Region Two | 2300 | 2399 |
+----+------------+--------------+-------------+-------------+
mysql> SELECT * FROM `issues`;
+----+------------+---------+---------+
| id | categoryid | country | zipcode |
+----+------------+---------+---------+
| 1 | 2 | Germany | 2100 |
| 2 | 1 | France | 1900 |
| 3 | 1 | Germany | 1500 |
| 4 | 2 | Germany | 2800 |
| 5 | 2 | France | 1850 |
+----+------------+---------+---------+
mysql> SELECT * FROM `experts`;
+----+---------+
| id | country |
+----+---------+
| 1 | Germany |
| 2 | France |
| 3 | Germany |
+----+---------+
mysql> SELECT * FROM `experts_categories`;
+----------+------------+
| expertid | categoryid |
+----------+------------+
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 3 | 1 |
+----------+------------+
mysql> SELECT * FROM `experts_provinces`;
+----------+------------+
| expertid | provinceid |
+----------+------------+
| 1 | 4 |
| 2 | 6 |
| 2 | 7 |
+----------+------------+
mysql> SELECT * FROM `experts_regions`;
+----------+----------+
| expertid | regionid |
+----------+----------+
| 1 | 8 |
| 1 | 10 |
+----------+----------+
III. Solution
I have managed to come up with half of the solution.
1. My half solution
a) Query:
SELECT
`i`.`id` `issue_id`,
`e`.`id` `expert_id`
FROM `issues` `i`
INNER JOIN `experts` `e`
ON `i`.`country` = `e`.`country`
INNER JOIN `experts_categories` `ec`
ON `e`.`id` = `ec`.`expertid`
AND `i`.`categoryid` = `ec`.`categoryid`
ORDER BY `e`.`id`, `ec`.`categoryid` ASC
b) Result:
+----------+-----------+
| issue_id | expert_id |
+----------+-----------+
| 3 | 1 |
| 1 | 1 |
| 4 | 1 |
| 2 | 2 |
| 3 | 3 |
+----------+-----------+
c) Accurate result would have been:
+----------+-----------+
| issue_id | expert_id |
+----------+-----------+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
+----------+-----------+
Explanation, as to why the above visual result is the accurate one.
First, let's get a "full join" so we can make the comparisons:
d) Query:SELECT
`i`.`id` `issue_id`, `e`.`id` `expert_id`, `i`.`categoryid` `issue_category_id`, `ec`.`categoryid` `expert_category_id`,
`i`.`country` `issue_country`, `e`.`country` `expert_country`,
`i`.`zipcode` `issue_zipcode`,
`p`.`id` `province_id`, `p`.`min_zipcode` `province_min_zipcode`, `p`.`max_zipcode` `province_max_zipcode`,
`r`.`id` `region_id`, `r`.`min_zipcode` `region_min_zipcode`, `r`.`max_zipcode` `region_max_zipcode`
FROM `issues` `i`
INNER JOIN `experts` `e`
ON `i`.`country` = `e`.`country`
INNER JOIN `experts_categories` `ec`
ON `ec`.`expertid` = `e`.`id`
AND `i`.`categoryid` = `ec`.`categoryid`
LEFT JOIN `experts_provinces` `ep`
ON `e`.`id` = `ep`.`expertid`
LEFT JOIN `provinces` `p`
ON `ep`.`provinceid` = `p`.`id`
LEFT JOIN `experts_regions` `er`
ON `e`.`id` = `er`.`expertid`
LEFT JOIN `regions` `r`
ON `er`.`regionid` = `r`.`id`
AND `p`.`id` = `r`.`provinceid`
ORDER BY `e`.`id`,`ec`.`categoryid` ASC
e) Result:
+----------+-----------+-------------------+--------------------+---------------+----------------+---------------+-------------+----------------------+----------------------+-----------+--------------------+--------------------+
| issue_id | expert_id | issue_category_id | expert_category_id | issue_country | expert_country | issue_zipcode | province_id | province_min_zipcode | province_max_zipcode | region_id | region_min_zipcode | region_max_zipcode |
+----------+-----------+-------------------+--------------------+---------------+----------------+---------------+-------------+----------------------+----------------------+-----------+--------------------+--------------------+
| 3 | 1 | 1 | 1 | Germany | Germany | 1500 | 4 | 2000 | 2899 | 10 | 2600 | 2699 |
| 3 | 1 | 1 | 1 | Germany | Germany | 1500 | 4 | 2000 | 2899 | 8 | 2000 | 2299 |
| 1 | 1 | 2 | 2 | Germany | Germany | 2100 | 4 | 2000 | 2899 | 10 | 2600 | 2699 |
| 1 | 1 | 2 | 2 | Germany | Germany | 2100 | 4 | 2000 | 2899 | 8 | 2000 | 2299 |
| 4 | 1 | 2 | 2 | Germany | Germany | 2800 | 4 | 2000 | 2899 | 10 | 2600 | 2699 |
| 4 | 1 | 2 | 2 | Germany | Germany | 2800 | 4 | 2000 | 2899 | 8 | 2000 | 2299 |
| 2 | 2 | 1 | 1 | France | France | 1900 | 7 | 2200 | 2399 | NULL | NULL | NULL |
| 2 | 2 | 1 | 1 | France | France | 1900 | 6 | 1800 | 2199 | NULL | NULL | NULL |
| 3 | 3 | 1 | 1 | Germany | Germany | 1500 | NULL | NULL | NULL | NULL | NULL | NULL |
+----------+-----------+-------------------+--------------------+---------------+----------------+---------------+-------------+----------------------+----------------------+-----------+--------------------+--------------------+
So comparing (b) query result with (c), manually fixed result, we can notice...
issue_id
number3
can NOT be assigned toexpert_id
number1
, becauseissue_id
number1
is from countryGermany
, just like the expert, is assigned on thecategory_id
number2
, just like the expert, BUT has a zip code1500
, andexpert_id
number1
is assigned to takeissues
only fromprovince_id
number4
andregions_id
number8
and10
within thatprovince
. So theregions
zip codes range from2000
to2299
and from2600
to2699
, where ourissue
zip code does not belong.issue_id
number1
can be assigned toexpert_id
number1
, becauseissue_id
number1
is from countryGermany
, just like the expert, is assigned on thecategory_id
number2
, just like the expert, has a zip code2100
, which is between the boundaries of theprovince_id
number4
andregion_id
number8
within the province, which is assigned to be covered byexpert_id
number1
.issue_id
number4
can NOT be assigned toexpert_id
number1
, becauseissue_id
number4
is from countryGermany
, just like the expert, is assigned on thecategory_id
number4
, BUT has a zip code2800
, which is within the boundaries of theprovince_id
number4
, but it is not within the boundaries of theregion_id
number8
and10
, which is assigned to be covered byexpert_id
number1
issue_id
number2
can be assigned toexpert_id
number2
, becauseissue_id
number2
is from countryFrance
, just like the expert, is assigned on thecategory_id
number1
, just like the expert, has a zip code1900
, which is within the boundaries of theprovince_id
number6
, assigned to be covered by this expert.issue_id
number3
can be assigned toexpert_id
number3
, becauseissue_id
number3
is from countryGermany
, just like the expert, is assigned on thecategory_id
number1
, just like the expert. Furthermore, this expert does not have any administrative division restriction. That is, this expert can takeissues
from allGermany
Thus, we have listed all issues
that can be assigned to experts
.
2. Missing half solution
As you can see, my half solution does not take into account the administrative division restrictions.
I can not use procedure(s) or views to achieve this, though, I can split it up in multiple queries if that would help. Database is MySQL (5.0.1 - MySQL Community Server (GPL)) and programming language is PHP (5.1).I just modify the answer from @krubo.
If you want sub-query, the query would be:
SELECT
tis.id AS issue_id,
tex.id AS expert_id,
tis.categoryid AS issue_category_id,
tex.categoryid AS expert_category_id,
tis.country AS issue_country,
tex.country AS expert_country,
tis.zipcode AS issue_zipcode,
tis.provinceid AS province_id,
tis.province_minzipcode AS province_minzipcode,
tis.province_maxzipcode AS province_maxzipcode,
tis.regionid AS region_id,
tis.region_minzipcode AS region_minzipcode,
tis.region_maxzipcode AS region_maxzipcode
FROM
(
SELECT
i.id, categoryid, i.country, zipcode,
provinces.id AS provinceid, provinces.min_zipcode AS province_minzipcode,
provinces.max_zipcode AS province_maxzipcode, regions.id AS regionid,
regions.min_zipcode AS region_minzipcode,
regions.max_zipcode AS region_maxzipcode
FROM
issues AS i
LEFT JOIN provinces ON i.country=provinces.country
AND i.zipcode BETWEEN provinces.min_zipcode AND provinces.max_zipcode
LEFT JOIN regions on provinces.id=regions.provinceid
AND i.zipcode BETWEEN regions.min_zipcode AND regions.max_zipcode
) AS tis
JOIN
(
SELECT
e.id, country, categoryid, provinceid, regionid
FROM
experts e
JOIN experts_categories ON e.id=experts_categories.expertid
LEFT JOIN experts_provinces ON e.id=experts_provinces.expertid
LEFT JOIN experts_regions ON e.id=experts_regions.expertid
) AS tex
WHERE
tis.country=tex.country
AND tis.categoryid=tex.categoryid
AND (tis.provinceid IS NULL
OR tex.provinceid IS NULL
OR tis.provinceid=tex.provinceid)
AND (tis.regionid IS NULL
OR tex.regionid IS NULL
OR tis.regionid=tex.regionid);
The result is:
+----------+-----------+-------------------+--------------------+---------------+----------------+---------------+-------------+----------------------+----------------------+-----------+--------------------+--------------------+
| issue_id | expert_id | issue_category_id | expert_category_id | issue_country | expert_country | issue_zipcode | province_id | province_min_zipcode | province_max_zipcode | region_id | region_min_zipcode | region_max_zipcode |
+----------+-----------+-------------------+--------------------+---------------+----------------+---------------+-------------+----------------------+----------------------+-----------+--------------------+--------------------+
| 1 | 1 | 2 | 2 | Germany | Germany | 2100 | 4 | 2000 | 2899 | 8 | 2000 | 2299 |
| 2 | 2 | 1 | 1 | France | France | 1900 | 6 | 2000 | 2199 | 14 | 1800 | 2000 |
| 3 | 3 | 1 | 1 | Germany | Germany | 1500 | 3 | 2000 | 1999 | 6 | 1500 | 1699 |
Your SQL will be very complex if you don't use at least one view to organize it. Try this view to match each issue with a province and region:
create view viewissues as
select issues.id, categoryid, issues.country, zipcode,
provinces.id as provinceid, regions.id as regionid
from issues
left join provinces on issues.country=provinces.country
and issues.zipcode between provinces.min_zipcode and provinces.max_zipcode
left join regions on provinces.id=regions.provinceid
and issues.zipcode between regions.min_zipcode and regions.max_zipcode;
and this view to list experts according to their categories, provinces if any, and regions if any:
create view viewexperts as
select experts.id, country, categoryid, provinceid, regionid
from experts
join experts_categories on experts.id=experts_categories.expertid
left join experts_provinces on experts.id=experts_provinces.expertid
left join experts_regions on experts.id=experts_regions.expertid;
Now your final query can be more manageable, by selecting based on these views:
select distinct viewissues.id, viewexperts.id
from viewissues join viewexperts
where viewissues.country=viewexperts.country
and viewissues.categoryid=viewexperts.categoryid
and (viewissues.provinceid is null
or viewexperts.provinceid is null
or viewissues.provinceid=viewexperts.provinceid)
and (viewissues.regionid is null
or viewexperts.regionid is null
or viewissues.regionid=viewexperts.regionid);
精彩评论