I have the following scenario: there are 1 table with books and two couples of tables (HD/IT) with Sales Order and Purchase Order transactions connecting through Sales Order id.
The table structure follows:
CREATE TABLE `books` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`isbn` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`it_id` int(11) NOT NULL,
`kind` tinyint(4) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `books` (`id`, `isbn`, `it_id`, `kind`) VALUES
(1, '12345', 1, 1),
(2, '12345', 1, 2),
(3, '67890', 2, 1),
(4, '1111111', 2, 2);
CREATE TABLE `porders_hd` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`dt` date NOT NULL,
`so_id` int(11) DEFAULT NULL,
`customer` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `porders_hd` (`id`, `dt`, `so_id`, `customer`) VALUES
(1, '2017-07-02', 1, 1),
(2, '2017-08-03', NULL, 3);
CREATE TABLE `porders_it` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`hd_id` int(11) NOT NULL,
`isbn` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`dscr` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`qty` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `porders_it` (`id`, `hd_id`, `isbn`, `dscr`, `qty`) VALUES
(1, 1, '12345', 'Book 1', 1),
(2, 2, '1111111', 'Book 2', 1);
CREATE TABLE `sorders_hd` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`dt` date NOT NULL,
`customer` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `sorders_hd` (`id`, `dt`, `customer`) VALUES
(1, '2017-07-01', 1),
(2, '2017-08-01', 2);
CREATE TABLE `sorders_it` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`hd_id` int(11) NOT NULL,
`isbn` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`dscr` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`qty` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `sorders_it` (`id`, `hd_id`, `isbn`, `dscr`, `qty`) VALUES
(1, 1, '12345', 'Book 1', 1),
(2, 2, '67890', 'Book 2', 1);
In summary there are:
* 1 Sales Order (#1) also existing in the Purchase Order (#1)
* 1 Sales Order (#2) still pending
* 1 Purchase Order (#2) created without a Sales Order
I want to be able to grab all Sales and Purchases Order per book's isbn and the connected SO and PO must be in the same line. The output must be like the one below:
so_id so_date po_id po_date isbn dscr
NULL NULL 2 2017-08-03 1111111 Book 2
1 2017-07-01 1 2017-07-02 12345 Book 1
2 2017-08-01 NULL NULL 67890 Book 3
I tried to grab the rows using a query like the one below:
SELECT
GROUP_CONCAT(so_id) so_id,
GROUP_CONCAT(so_date) so_date,
GROUP_CONCAT(po_id) po_id,
GROUP_CONCAT(po_date) po_date,
isbn,
dscr
FROM (
SELECT
hd.so_id so_id,
NULL so_date,
hd.id po_id,
hd.dt po_date,
bk.isbn,
it.dscr
FROM porders_hd hd,
porders_it it,
books bk
WHERE it.hd_id = hd.id
AND bk.isbn = it.isbn
AND kind = 2
UNION
SELECT
hd.id so_id,
hd.dt so_date,
NULL po_id,
NULL po_date,
bk.isbn,
it.dscr
FROM sorders_hd hd,
sorders_it it,
books bk
WHERE it.hd_id = hd.id
AND bk.isbn = it.isbn
AND kind = 1
) as table1
GROUP BY isbn, so_id, po_id
but since there is info missing I get the following result:
so_id so_date po_id po_date isbn dscr
NULL NULL 2 2017-08-03 1111111 Book 2
1 2017-07-01 NULL NULL 12345 Book 1
1 NULL 1 2017-07-02 12345 Book 1
2 2017-08-01 NULL NULL 67890 Book 3
Any ideas how can I achieve this ?
I think this is what you're after, but I can;t figure out the role of kind from your code. But here is a query that for each books, gets the associated po line item, finds the corresponding so line item and joins the header rows so the dates are available. Note my assumption that a sales order can't exist with a corresponding PO.
SELECT books.isbn, books.descr, sorders_hd.id, sorders_hd.dt, porders_hd.id, porders_hd.dt
FROM book
join porders_it on porders_it.isbn = books.isbn
join porders_hd on porders_hd.id = porders_it.hd_id
left outer join sorders_it on sorders_it.hd_id=porders_hd.so_id and sorders_it.isbn = porders_it.isbn
left outer join sorders_hd on sorders_hd.id = sorders_it.hd_it
You could normalize your tables so that descr need not be repeated, and also use the book.id in the other tables rather than isbn.
I'm adding a new answer because the previous one and the comments are illustrative. Based on that discussion, this requires a FULL OUTER JOIN which must be emulated by UNION ALL in mysql (which may be what OP was attempting originally).
Here is my new code, taking that into account:
SELECT sorders_hd.id as so_id, sorders_hd.dt as so_dt,
porders_hd.id as po_id, porders_hd.dt as po_dt,
books.isbn, porders_it.dscr
from books
left outer join porders_it on porders_it.isbn=books.isbn
join porders_hd on porders_hd.id=porders_it.hd_id
left outer join sorders_it on sorders_it.isbn=books.isbn and sorders_it.hd_id=porders_hd.so_id
left outer join sorders_hd on sorders_hd.id=sorders_it.hd_id
where books.kind=2
UNION ALL
SELECT sorders_hd.id as so_id, sorders_hd.dt as so_dt,
porders_hd.id as po_id, porders_hd.dt as po_dt,
books.isbn, sorders_it.dscr
from books
left outer join sorders_it on sorders_it.isbn=books.isbn
join sorders_hd on sorders_hd.id=sorders_it.hd_id
left outer join porders_it on porders_it.isbn=books.isbn
left outer join porders_hd on porders_hd.id=porders_it.hd_id and porders_hd.so_id=sorders_hd.id
where porders_hd.id is null and books.kind=1;
The output result is:
so_id so_dt po_id po_dt isbn dscr
1 2017-07-01 1 2017-07-02 12345 Book 1
(null) (null) 2 2017-08-03 1111111 Book 2
2 2017-08-01 (null) (null) 67890 Book 2
See SqlFiddle
The "trick" is to use union all with one of the two queries excluding records that linked both sides (to get the 'right' side of the FULL OUTER JOIN)
+1 to OP for providing the DDL and sample data!
I agree that the data model could be reworked, and could be normalized. The existing model still has at least the problem of a duplicate book record when a sales order and purchase order match (one of them is ignored). It seems to me that one improvement would be to have a master book list and include the id (or isbn if that is the primary key) from that table in porders_it and sorders_it, and eliminate the current books table.
This got complicated really quickly and I'm beginning to question the database design.
The basic concept of the application is:
User accounts
Features
Access levels
So, users have different access levels for each of the features. Fairly basic and common application I would think.
Schema:
CREATE TABLE `user_accounts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_login` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`user_password` varchar(60) COLLATE utf8_unicode_ci NOT NULL,
`user_fname` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`user_lname` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`user_group` varchar(32) COLLATE utf8_unicode_ci NOT NULL DEFAULT 'Default',
PRIMARY KEY (`id`),
UNIQUE KEY `user_login` (`user_login`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ;
INSERT INTO `user_accounts` VALUES(1, 'email#example.com', 'secret', 'Example', 'Name', 'Admin');
INSERT INTO `user_accounts` VALUES(2, 'john#example.com', 'secret', 'John', 'Doe', 'Trainer');
INSERT INTO `user_accounts` VALUES(3, 'jane#example.com', 'secret', 'Jane', 'Doe', 'Default');
CREATE TABLE `user_access_meta` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`type` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `type` (`type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `user_access_meta` VALUES(1, 'type_1');
INSERT INTO `user_access_meta` VALUES(2, 'type_2');
INSERT INTO `user_access_meta` VALUES(3, 'type_3');
INSERT INTO `user_access_meta` VALUES(4, 'type_4');
INSERT INTO `user_access_meta` VALUES(5, 'type_5');
INSERT INTO `user_access_meta` VALUES(6, 'type_6');
CREATE TABLE `user_access_levels` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_login` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`type` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
`level` int(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `user_login_2` (`user_login`,`type`),
KEY `user_login` (`user_login`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ;
INSERT INTO `user_access_levels` VALUES(1, 'email#example.com', 'type_1', 1);
INSERT INTO `user_access_levels` VALUES(2, 'email#example.com', 'type_2', 1);
INSERT INTO `user_access_levels` VALUES(3, 'email#example.com', 'type_3', 0);
INSERT INTO `user_access_levels` VALUES(4, 'email#example.com', 'type_5', 2);
INSERT INTO `user_access_levels` VALUES(5, 'john#example.com', 'type_2', 1);
INSERT INTO `user_access_levels` VALUES(6, 'john#example.com', 'type_3', 1);
INSERT INTO `user_access_levels` VALUES(7, 'john#example.com', 'type_5', 3);
INSERT INTO `user_access_levels` VALUES(8, 'jane#example.com', 'type_4', 1);
These tables actually have a lot more fields and have foreign key constraints between them, but I've striped them down for this example. They are also used individually for other purposes.
I've successfully been able to join all three tables together for a single user with this:
SELECT
ua.`user_fname`,
uam.`type`,
ual.`level`
FROM `user_access_meta` uam
LEFT JOIN `user_access_levels` ual
ON ual.`user_login` = 'email#example.com'
AND uam.`type` = ual.`type`
JOIN `user_accounts` ua
ON ua.`user_login` = 'email#example.com';
Output:
| USER_FNAME | TYPE | LEVEL |
--------------------------------
| Example | type_1 | 1 |
| Example | type_2 | 1 |
| Example | type_3 | 0 |
| Example | type_4 | (null) |
| Example | type_5 | 2 |
| Example | type_6 | (null) |
Even this isn't ideal, but It's all I could come up with and it serves it's purpose.
Now, what I need to do is select all users including their access levels. It would look something like this:
| USER_FNAME | type_1 | type_2 | type_3 | type_4 | type_5 | type_6 |
--------------------------------------------------------------------------
| Example | 1 | 1 | 0 | (null) | 2 | (null) |
| John | (null) | 1 | 1 | (null) | 3 | (null) |
| Jane | (null) | (null) | (null) | 1 | (null) | (null) |
I feel this may not have been the best design, but the reason I went with this design is so that I can easily add and remove features or even temporarily disable them individually.
Should the design be rethought? Is it even possible to get the results I'm looking for with this design?
I've put this up on SQL Fiddle. http://sqlfiddle.com/#!2/bb313/2/0
I have a few suggestions on both your table design and then how to get the data in the format that you want.
First on the database design, the change I would advise is in the table user_access_levels. Alter you table to the following:
CREATE TABLE `user_access_levels` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`type_id` int(11) NOT NULL,
`level` int(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `user_id_2` (`user_id`,`type_id`),
KEY `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ;
There is no need to store the user_login and type in this table when you can just store the user_id and the type_id. Use both of these as foreign keys to their respective tables.
Then to get the data in format that you want. MySQL does not have a PIVOT function so you will want to use a CASE statement with an aggregate function.
select ua.user_fname,
MIN(CASE WHEN uam.type = 'type_1' THEN ual.level END) type_1,
MIN(CASE WHEN uam.type = 'type_2' THEN ual.level END) type_2,
MIN(CASE WHEN uam.type = 'type_3' THEN ual.level END) type_3,
MIN(CASE WHEN uam.type = 'type_4' THEN ual.level END) type_4,
MIN(CASE WHEN uam.type = 'type_5' THEN ual.level END) type_5,
MIN(CASE WHEN uam.type = 'type_6' THEN ual.level END) type_6
FROM user_accounts ua
LEFT JOIN user_access_levels ual
ON ua.id = ual.user_id
LEFT JOIN user_access_meta uam
ON ual.type_id = uam.id
group by ua.user_fname
See a SQL Fiddle with a Demo
This version will work if you know ahead of time the type columns that you want to get the values for. But if it is unknown, then you can use prepared statements to generate this dynamically.
Here is a version of the query using prepared statements:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'MIN(case when type = ''',
type,
''' then level end) AS ',
replace(type, ' ', '')
)
) INTO #sql
FROM user_access_meta;
SET #sql = CONCAT('SELECT ua.user_fname, ', #sql, ' FROM user_accounts ua
LEFT JOIN user_access_levels ual
ON ua.id = ual.user_id
LEFT JOIN user_access_meta uam
ON ual.type_id = uam.id
group by ua.user_fname');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
See a SQL Fiddle with Demo
While I'm not familiar with the specifics of MySQL, it seems to me you are describing a pretty fundamental example of a pivot table query. What you're looking for seems reasonable to me, so I don't think based on what you've shown here I'd get too concerned about revisiting the data model. You may find putting the "level" back with the "type" table, based on the ol' saw "Normalize til hit hurts, denormalize til it works :)"
Just my $0.02. Good luck.
I typically use a BIGINT column and use bit masking to set the values.
For example level1 = 2, level2=4, level3=8, level4=16, etc..
Give someone level1 and level2 access:
update user set access_level = 2 & 4
does someone have level2 access?
select 1 from user where access_level | 2 AND user_id = ?
I have two tables, one for candidates and their skills, and the other for jobs and the skills required for the job, such as the ones shown below:
CSID = candidate skill ID (PK)
CID = candidate ID (FK)
S_CODE = skill code (FK)
Here is the candidate_skill table
+------------+---------+---------+
| CSID | CID | S_CODE |
+------------+---------+---------+
| 1 | 1 | 5 |
| 2 | 1 | 9 |
| 3 | 2 | 5 |
| 4 | 2 | 10 |
+------------+---------+---------+
SJID = skill job ID (PK)
JID = job ID (FK)
S_CODE = skill code (FK)
Here is the skill_job table:
+------------+---------+---------+
| SJID | JID | S_CODE |
+------------+---------+---------+
| 12 | 50 | 5 |
| 13 | 50 | 9 |
| 14 | 51 | 1 |
| 15 | 52 | 10 |
+------------+---------+---------+
So in this example, the only candidate would be 1 for the job 50, since the skill codes (S_CODE) are 5 and 9 for both, but i also would like candidate 2 to be a match to 52, since it has the required job skill and an extra one.
I have tried several ways to match the job and candidate but I'm failing, an example of such a way is the code below:
SELECT * , COUNT(skill_job.S_CODE) AS cnt FROM candidate_skill,
skill_job WHERE candidate_skill.S_CODE = skill_job.S_CODE HAVING (cnt >=2)
the problem is, that only limits the candidate found to one and if i remove the COUNT clause, it lists all candidates that match with only one skill so candidate 2 would also be matched to the job 50.
Here is the mysql code for the tables from phpmyadmin:
-- phpMyAdmin SQL Dump
-- version 3.4.5
-- http://www.phpmyadmin.net
--
-- Host: localhost
-- Generation Time: Jul 16, 2012 at 09:27 PM
-- Server version: 5.5.16
-- PHP Version: 5.3.8
SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
/*!40101 SET #OLD_CHARACTER_SET_CLIENT=##CHARACTER_SET_CLIENT */;
/*!40101 SET #OLD_CHARACTER_SET_RESULTS=##CHARACTER_SET_RESULTS */;
/*!40101 SET #OLD_COLLATION_CONNECTION=##COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
--
-- Database: `employment`
--
-- --------------------------------------------------------
--
-- Table structure for table `candidate`
--
CREATE TABLE IF NOT EXISTS `candidate` (
`CID` int(4) NOT NULL AUTO_INCREMENT,
`title` varchar(5) NOT NULL,
`fname` varchar(30) NOT NULL,
`lname` varchar(30) NOT NULL,
`dob` date NOT NULL,
`email` varchar(50) NOT NULL,
`address` varchar(255) NOT NULL,
`city` varchar(50) NOT NULL,
`postcode` varchar(10) NOT NULL,
`phone_num` varchar(11) NOT NULL,
`username` varchar(40) NOT NULL,
`password` varchar(40) NOT NULL,
`regdate` datetime NOT NULL,
`acc_type` enum('c','s') NOT NULL DEFAULT 'c',
`emailactivate` enum('0','1') NOT NULL DEFAULT '0',
`cv_name` varchar(60) NOT NULL,
`cv` blob NOT NULL,
PRIMARY KEY (`CID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='// this is the table for the candidates' AUTO_INCREMENT=175 ;
--
-- Dumping data for table `candidate`
--
INSERT INTO `candidate` (`CID`, `title`, `fname`, `lname`, `dob`, `email`, `address`, `city`, `postcode`, `phone_num`, `username`, `password`, `regdate`, `acc_type`, `emailactivate`, `cv_name`, `cv`) VALUES
(128, 'Mr', 'clement', 'Chilingulo', '0000-00-00', 'chlngl#yahoo.com', '28 oakfield Road', 'london', 'E6 1LW', '07771611873', 'casante', 'b59c67bf196a4758191e42f76670ceba', '0000-00-00 00:00:00', 'c', '0', '', '');
INSERT INTO `candidate` (`CID`, `title`, `fname`, `lname`, `dob`, `email`, `address`, `city`, `postcode`, `phone_num`, `username`, `password`, `regdate`, `acc_type`, `emailactivate`, `cv_name`, `cv`) VALUES
(134, 'Mr', 'rverv', 'revrb', '0000-00-00', 'tdbsdrt', 'trsbrtd', 'trbtrtrb', 'tbrfbgrts', 'trfbtrgbrfg', 'clement', 'b59c67bf196a4758191e42f76670ceba', '0000-00-00 00:00:00', 's', '0', '', '');
INSERT INTO `candidate` (`CID`, `title`, `fname`, `lname`, `dob`, `email`, `address`, `city`, `postcode`, `phone_num`, `username`, `password`, `regdate`, `acc_type`, `emailactivate`, `cv_name`, `cv`) VALUES
(165, 'Mr', 'oinInINOioni', 'ioin', '0000-00-00', 'inioimn', 'in', 'oin', 'oni', 'io', 'k', '7b8b965ad4bca0e41ab51de7b31363a1', '0000-00-00 00:00:00', 'c', '0', '', ''),
(166, 'Mr', 'pjINoNlkinoinoi', 'ino', '0000-00-00', 'oimpnponi', 'inoi', 'no', 'nj', 'nio', 'nio', 'eb5bc837d01b911029ae378e8a1c9f5d', '0000-00-00 00:00:00', 'c', '0', '', ''),
(167, 'Mr', 'vrae', 'ergvaer', '0000-00-00', 'aerbg', 'aergvera', 'aergvrea', 'aergvear', 'aergarev', 'grebvarvf', '609c1b136ec8d0d2dfdf9a2105fb605f', '0000-00-00 00:00:00', 'c', '0', '', ''),
(168, 'Mr', 'vrae', 'ergvaer', '0000-00-00', 'aerbg', 'aergvera', 'aergvrea', 'aergvear', 'aergarev', 'grebvarvf', '609c1b136ec8d0d2dfdf9a2105fb605f', '0000-00-00 00:00:00', 'c', '0', '', ''),
(169, 'Mr', 'ubp', 'bu', '0000-00-00', 'ubip', ';ub', 'ubi', 'ubo', 'buo', 'ubiipbu', '9f44ce1389a3e7372834ed730b559a5e', '0000-00-00 00:00:00', 'c', '0', '', ''),
(170, 'Mr', 'rvev', 'ferhbgetb', '0000-00-00', 'tsbtrb', 'trbstrb', 'trbsfb ', 'stb stb', 'vvfs', 'csdcdsvarewdcv', 'd41d8cd98f00b204e9800998ecf8427e', '0000-00-00 00:00:00', 'c', '0', '', ''),
(171, 'Mr', 'rvev', 'ferhbgetb', '0000-00-00', 'tsbtrb', 'trbstrb', 'trbsfb ', 'stb stb', 'vvfs', 'csdcdsvarewdcv', 'd41d8cd98f00b204e9800998ecf8427e', '0000-00-00 00:00:00', 'c', '0', '', ''),
(172, '', '', '', '0000-00-00', '', '', '', '', '', '', 'd41d8cd98f00b204e9800998ecf8427e', '0000-00-00 00:00:00', 'c', '0', '', ''),
(173, '', 'dds', 'vrv av', '0000-00-00', 'rvear', 'vreverav', 'rvedfsdv', 'frvdvf', 'fveavd', 'vdrareavdv', '84c71ac76340092398a1ed4bc7d6fe19', '0000-00-00 00:00:00', 'c', '0', '', ''),
(174, '', 'dds', 'vrv av', '0000-00-00', 'rvedwear', 'vreverav', 'rvedfsdv', 'frvdvf', 'fveavd', 'vdraredfcavdv', 'd41d8cd98f00b204e9800998ecf8427e', '0000-00-00 00:00:00', 'c', '0', '', '');
-- --------------------------------------------------------
--
-- Table structure for table `candidate_skill`
--
CREATE TABLE IF NOT EXISTS `candidate_skill` (
`CSID` int(4) NOT NULL AUTO_INCREMENT,
`CID` int(4) NOT NULL,
`S_CODE` int(4) NOT NULL,
PRIMARY KEY (`CSID`),
KEY `CID` (`CID`),
KEY `S_CODE` (`S_CODE`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='//match candidate and skill' AUTO_INCREMENT=131 ;
--
-- Dumping data for table `candidate_skill`
--
INSERT INTO `candidate_skill` (`CSID`, `CID`, `S_CODE`) VALUES
(114, 134, 2),
(116, 134, 9),
(121, 134, 5),
(126, 128, 1);
-- --------------------------------------------------------
--
-- Table structure for table `job`
--
CREATE TABLE IF NOT EXISTS `job` (
`JID` int(4) NOT NULL AUTO_INCREMENT,
`job_title` varchar(40) NOT NULL,
`job_desc` varchar(255) NOT NULL,
`start_date` date NOT NULL,
`end_date` date NOT NULL,
PRIMARY KEY (`JID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='// this is the table for the job vacancies' AUTO_INCREMENT=10 ;
--
-- Dumping data for table `job`
--
INSERT INTO `job` (`JID`, `job_title`, `job_desc`, `start_date`, `end_date`) VALUES
(1, 'engineer', ' fix engineering ish that is messed ', '0000-00-00', '0000-00-00'),
(5, 'pilot', '', '0000-00-00', '0000-00-00'),
(8, 'Charity Helper', ' ', '0000-00-00', '0000-00-00'),
(9, 'accountant', ' ', '0000-00-00', '0000-00-00');
-- --------------------------------------------------------
--
-- Table structure for table `skill`
--
CREATE TABLE IF NOT EXISTS `skill` (
`S_CODE` int(4) NOT NULL AUTO_INCREMENT COMMENT '// this is the skill primary key',
`skill_name` varchar(40) NOT NULL,
`skill_desc` varchar(255) NOT NULL,
PRIMARY KEY (`S_CODE`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=11 ;
--
-- Dumping data for table `skill`
--
INSERT INTO `skill` (`S_CODE`, `skill_name`, `skill_desc`) VALUES
(1, 'speaking', 'English is your first language'),
(2, ' writing', 'You can write English very well'),
(3, ' public', ''),
(5, 'initiative', ''),
(6, 'interviewing', ''),
(7, 'negotiating', ''),
(8, 'leading', ''),
(9, ' energy', ''),
(10, ' organisation', '');
-- --------------------------------------------------------
--
-- Table structure for table `skill_job`
--
CREATE TABLE IF NOT EXISTS `skill_job` (
`SJID` int(4) NOT NULL AUTO_INCREMENT,
`JID` int(4) NOT NULL,
`S_CODE` int(4) NOT NULL,
PRIMARY KEY (`SJID`),
KEY `S_CODE` (`S_CODE`),
KEY `JID` (`JID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=106 ;
--
-- Dumping data for table `skill_job`
--
INSERT INTO `skill_job` (`SJID`, `JID`, `S_CODE`) VALUES
(91, 5, 2),
(94, 5, 5),
(95, 5, 9),
(98, 1, 1),
(102, 1, 8),
(105, 8, 8);
--
-- Constraints for dumped tables
--
--
-- Constraints for table `candidate_skill`
--
ALTER TABLE `candidate_skill`
ADD CONSTRAINT `candidate_skill_ibfk_3` FOREIGN KEY (`CID`) REFERENCES `candidate` (`CID`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `candidate_skill_ibfk_4` FOREIGN KEY (`S_CODE`) REFERENCES `skill` (`S_CODE`) ON DELETE CASCADE ON UPDATE CASCADE;
--
-- Constraints for table `skill_job`
--
ALTER TABLE `skill_job`
ADD CONSTRAINT `skill_job_ibfk_4` FOREIGN KEY (`JID`) REFERENCES `job` (`JID`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `skill_job_ibfk_5` FOREIGN KEY (`S_CODE`) REFERENCES `skill` (`S_CODE`) ON DELETE CASCADE ON UPDATE CASCADE;
/*!40101 SET CHARACTER_SET_CLIENT=#OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=#OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=#OLD_COLLATION_CONNECTION */;
Double negation is your friend (though I don't know MySQL well enough to tell whether it supports it):
SELECT DISTINCT CS.CID, SJ.JID
FROM CANDIDATE_SKILL CS
JOIN SKILL_JOB SJ ON CS.S_CODE = SJ.S_CODE
WHERE NOT EXISTS(SELECT 1
FROM SKILL_JOB SJ2
WHERE SJ.JID = SJ2.JID
AND NOT EXISTS(SELECT 1
FROM CANDIDATE_SKILL CS2
WHERE CS.CID = CS2.CID
AND SJ2.S_CODE = CS2.S_CODE))
A 'human translation' of this is to say that there should not exists any skill required for the job that the candidate doesn't have.
It seems like you'll need to use an INNER JOIN to combine your two tables and then compare their row values.
Should be something like this..
SELECT
CID
FROM
candidate_skill
WHERE
S_CODE IN (
SELECT S_CODE FROM skill_jobs where JID = 52
)
GROUP BY CID
CORRECTION: Previous query will fail to match all required S_CODE
SELECT
CID, COUNT(CSID) as cnt
FROM
candidate_skill
INNER JOIN skill_jobs
WHERE
S_CODE IN (
SELECT S_CODE FROM skill_jobs where JID = 52
)
GROUP BY CID
HAVING cnt = (
SELECT count(*) from skill_jobs where JID = 52
)
You will need a bit more complicated JOIN, and I see no easy way of simultaneously answering your request and the obvious ones "How is candidate X matched for job Y?" and "HOW MUCH does candidate X fit job Y?".
Anyway, here goes.
SELECT candidate_skill.CID, skill_job.JID, COUNT(*) AS has, sjtot.needed
FROM skill_job JOIN candidate_skill ON (candidate_skill.S_CODE = skill_job.S_CODE)
LEFT JOIN ( SELECT JID, COUNT(*) AS needed FROM skill_job GROUP BY JID ) AS sjtot
ON ( skill_job.JID = sjtot.JID )
GROUP BY CID, JID
HAVING has >= needed;
In practice I first group the required skills on skill_job, and this tells me that job 50 requires two skills.
Armed with this, I LEFT JOIN required and candidate skills; some candidates will join on all skills, some won't. It is then just a matter of counting how many do match.
+------+------+-----+--------+
| CID | JID | has | needed |
+------+------+-----+--------+
| 1 | 50 | 2 | 2 |
| 2 | 52 | 1 | 1 |
+------+------+-----+--------+
On second thought, required skill level and offered skill levels might be selected, and CASE WHEN used to extract a match (1 if offered >= required, offered/required otherwise). The average of the resulting values might then be used as a "skill match" index.
As for the "How is X matched for Y?" question, once you know that candidate CID 1 is good for job JID 50, you can run a LEFT JOIN between the candidates and the jobs. You already know that whatever the jobs asks, the candidate has, but this way you retrieve what unasked-for skills the candidate has, and the other relevant values as well.
A nested query joining the query above and this last one would perhaps tell you everything in one fell swoop, but it would be a costly swoop, I think :-)
If you don't care about cross-dbms support, you can use mysql specific group_concat to reduce the two s_code sets to strings, one per candidate/job and just compare them like this:
select * from
(select cs.cid, group_concat(cs.s_code order by cs.s_code) skills
from candidate_skill cs group by cs.cid) cs,
(select sj.jid, group_concat(sj.s_code order by sj.s_code) skills
from skill_job sj group by sj.jid) sj
where sj.skills = cs.skills
Be aware of that when your tables grown the the final where will be slower and slower and you can't create indexes on the group_concat() created fields.