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 | |
+-------+------------------+------+-----+---------+----------------+
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_idnumber3can NOT be assigned toexpert_idnumber1, becauseissue_idnumber1is from countryGermany, just like the expert, is assigned on thecategory_idnumber2, just like the expert, BUT has a zip code1500, andexpert_idnumber1is assigned to takeissuesonly fromprovince_idnumber4andregions_idnumber8and10within thatprovince. So theregionszip codes range from2000to2299and from2600to2699, where ourissuezip code does not belong.issue_idnumber1can be assigned toexpert_idnumber1, becauseissue_idnumber1is from countryGermany, just like the expert, is assigned on thecategory_idnumber2, just like the expert, has a zip code2100, which is between the boundaries of theprovince_idnumber4andregion_idnumber8within the province, which is assigned to be covered byexpert_idnumber1.issue_idnumber4can NOT be assigned toexpert_idnumber1, becauseissue_idnumber4is from countryGermany, just like the expert, is assigned on thecategory_idnumber4, BUT has a zip code2800, which is within the boundaries of theprovince_idnumber4, but it is not within the boundaries of theregion_idnumber8and10, which is assigned to be covered byexpert_idnumber1issue_idnumber2can be assigned toexpert_idnumber2, becauseissue_idnumber2is from countryFrance, just like the expert, is assigned on thecategory_idnumber1, just like the expert, has a zip code1900, which is within the boundaries of theprovince_idnumber6, assigned to be covered by this expert.issue_idnumber3can be assigned toexpert_idnumber3, becauseissue_idnumber3is from countryGermany, just like the expert, is assigned on thecategory_idnumber1, just like the expert. Furthermore, this expert does not have any administrative division restriction. That is, this expert can takeissuesfrom 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).