how to match in mysql - mysql

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.

Related

Select value and count from different tables

I have 2 tables, one stores the user's information with a referral ID.
The other table collects records and applies the referral ID from which I arrived at that form.
I have made 2 queries that I want to join in a single query.
Example query for ID = 205391
---- Select email from refer 205391
SELECT email FROM mautic_leads WHERE refer_id IN
(SELECT refer_id FROM mautic_form_results_64_form_db_te WHERE refer_id = "205391")
---- Count results of refer 205391
(SELECT COUNT(*) AS `count` FROM `mautic_form_results_64_form_db_te` where ref = 205391)
I'm looking for the result to be:
Email | Count Ref
---------------
test#test.com 5
Table Structure:
CREATE TABLE `mautic_form_results_64_form_db_te` (
`submission_id` int(11) NOT NULL,
`form_id` int(11) NOT NULL,
`nombre` longtext COLLATE utf8_unicode_ci,
`apellido` longtext COLLATE utf8_unicode_ci,
`correo_electronico` longtext COLLATE utf8_unicode_ci,
`ref` longtext COLLATE utf8_unicode_ci
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `mautic_form_results_64_form_db_te` (`submission_id`, `form_id`, `nombre`, `apellido`, `correo_electronico`, `ref`) VALUES
(7699, 64, 'test', 'test', 'test#test.net', '201374'),
(7700, 64, 'test2', 'test2', 'test#test.net', '205391'),
(7701, 64, 'test3', 'test3', 'test#test.net', '205391'),
(7704, 64, 'test4', 'test4', 'test#test.net', '205391'),
(7705, 64, 'test5', 'test5', 'test#test.net', '205391'),
(7706, 64, 'test6', 'test6', 'test#test.net', '201374'),
(7707, 64, 'test7', 'test7', 'test#test.net', '201374'),
(7708, 64, 'test8', ' test8 ', 'test#test.net', '205391');
CREATE TABLE `mautic_leads` (
`email` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`refer_id` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`referido_por_usuario` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `mautic_leads` (`email`, `refer_id`, `referido_por_usuario`) VALUES
('test1#test.com', '201374', NULL),
('test2#test.com', '205388', NULL),
('test3#test.com', '205389', NULL),
('test3#test.com', '205390', NULL),
('test2#test.com', '205391', NULL);
Some help?
Thanks
You can try below query:
SELECT mautic_leads.email as email, (SELECT count(*) as count from \
mautic_form_results_64_form_db_te where ref = "205391") as count \
from mautic_leads where refer_id = "205391";
You will get the result like:
+----------------+-------+
| email | count |
+----------------+-------+
| test2#test.com | 5 |
+----------------+-------+
Now that I see your table structure, use this:
SELECT b.email,COUNT(a.submission_id) as `count`
FROM mautic_form_results_64_form_db_te a
JOIN mautic_leads b on b.refer_id = a.ref
WHERE ref = 205391
How about this?
SELECT (SELECT email
FROM mautic_leads
WHERE refer_id IN (SELECT refer_id
FROM mautic_form_results_64_form_db_te
WHERE refer_id = "205391")) as email,
Count(*) AS `count`
FROM `mautic_form_results_64_form_db_te`
WHERE ref = 205391

sum of values based on condition

Iam new to mysql
I have two tables,
CREATE TABLE `tab1` (
`tid1` int(2) NOT NULL auto_increment,
`payer` varchar(100) default NULL,
`receiver` varchar(100) default NULL,
`payAmt` decimal(20,2) default '0.00',
`recAmt` decimal(20,2) default '0.00',
PRIMARY KEY (`tid1`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;
--
-- Dumping data for table tab1
INSERT INTO `tab1` (`tid1`, `payer`, `receiver`, `payAmt`, `recAmt`) VALUES
(1, 'aaa', 'bbb', 100.00, -100.00),
(2, 'aaa', 'ccc', 200.00, -200.00),
(3, 'bbb', 'aaa', 150.00, -150.00),
(4, 'ccc', 'aaa', 175.00, -175.00);
--
-- Table structure for table tab2
CREATE TABLE `tab2` (
`tid2` int(2) NOT NULL auto_increment,
`payer` varchar(100) default NULL,
`receiver` varchar(100) default NULL,
`payAmt` decimal(20,2) default '0.00',
`recAmt` decimal(20,2) default '0.00',
PRIMARY KEY (`tid2`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;
--
-- Dumping data for table tab2
INSERT INTO `tab2` (`tid2`, `payer`, `receiver`, `payAmt`, `recAmt`) VALUES
(1, 'ddd', 'aaa', 223.00, -223.00),
(2, 'aaa', 'bbb', 429.00, -429.00),
(3, 'ccc', 'aaa', 102.00, -102.00);
I want the result as shown below
name payAmtTotal recAmtTotal payAmtTotal-recAmtTotal
aaa 729 650 79
bbb 150 529 -379
ccc 277 -277 554
ddd 223 0 223
Not sure why there is more then 1 table with this information, first thing that comes up is trying to turn this into 1 table.
Anyway, not sure if this is the best and cleanest way bu if the tables aren't to large and you have to option of creating temporary tables then here's one way of getting the desired results.
CREATE TEMPORARY TABLE IF NOT EXISTS temp1 AS (SELECT * FROM `tab1`);
INSERT INTO temp1 SELECT * FROM `tab2`;
CREATE TEMPORARY TABLE IF NOT EXISTS temp2 AS (SELECT * FROM `temp1`);
CREATE TEMPORARY TABLE IF NOT EXISTS temp3 AS (SELECT * FROM `temp1`);
SELECT
`payer` AS `name`,
(SELECT SUM(payAmt) AS `p` FROM temp2 AS t WHERE t.payer = temp1.payer) AS `payAmtTotal` ,
(SELECT SUM(payAmt) AS `r` FROM temp3 AS t WHERE t.receiver = temp1.payer) AS `recAmtTotal`
FROM
temp1
GROUP BY `name`;
Basically you create a singe (temporary) table and fill this table with the data from the two existing tables.
Since MySQL won't let you use the same temporary table more then once in a single query you need to create a few more :-) they can be copies of the first one though.
After that the selection query on itself isn't that complicated.

SQL - how to get all subcategory products incl. self products

I have a problem with my database and I hope you can help me with this.
I have tables like this:
categories
--------------------
|id | name | parent|
____________________
categories_x_products (m:n)
---------------------
|id | ctg_id | p_id |
---------------------
products
------------
| id, name |
------------
And my question is: "how to get all my & subcategories product count?"
For example:
categories:
id = 1, name = computers, parent = 0 (1 product)
id = 2, name = notebooks, parent = 1 (2 products)
and I want to get
computers : 3
notebooks : 2
I try this but does not working
select a.name, count(b.id)
FROM categories a
LEFT JOIN categories x ON x.id=a.parent
LEFT JOIN category_x_product b ON b.ctg_id=a.id
group by a.id
Thank you for answers.
Here is some example data:
-- Adminer 4.2.3 MySQL dump
SET NAMES utf8;
SET time_zone = '+00:00';
SET foreign_key_checks = 0;
SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO';
DROP TABLE IF EXISTS `categories`;
CREATE TABLE `categories` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`parent` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `categories` (`id`, `name`, `parent`) VALUES
(1, 'computers', 0),
(2, 'notebooks', 1),
(3, 'lenovo', 2);
DROP TABLE IF EXISTS `products`;
CREATE TABLE `products` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `products` (`id`, `name`) VALUES
(1, 'lenovo thinkpad 001'),
(2, 'lenovo thinkpad 002'),
(3, 'lenovo thinkpad 003'),
(4, 'lenovo thinkpad 004'),
(5, 'lenovo thinkpad 005'),
(6, 'random comp.');
DROP TABLE IF EXISTS `product_x_category`;
CREATE TABLE `product_x_category` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`product_id` int(11) DEFAULT NULL,
`category_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `product_x_category` (`id`, `product_id`, `category_id`) VALUES
(1, 1, 3),
(2, 2, 3),
(3, 3, 3),
(4, 4, 3),
(5, 5, 3),
(6, 6, 1);
-- 2016-02-23 08:16:30
I've tried to run your SQL Query (Create table and insert value).
And I run this query
SELECT a.name, COUNT( b.id )
FROM categories a
LEFT JOIN product_x_category b ON b.category_id = a.id
GROUP BY a.id
And it returns this
This is your product_x_Category table (where category_id --> 3 = lenovo and category_id --> 1 = computer)
I think the result is what you want, isn't it?

Alternative to subquery

Given the following tables:
CREATE TABLE IF NOT EXISTS `rank` (
`rank_id` bigint(20) NOT NULL AUTO_INCREMENT,
`rank` int(10) NOT NULL DEFAULT '0',
`subject_id` int(10) NOT NULL DEFAULT '0',
`title_id` int(10) NOT NULL DEFAULT '0',
`source_id` int(10) NOT NULL DEFAULT '0'
PRIMARY KEY (`rank_id`)
) ENGINE=MyISAM;
INSERT INTO `rank` (`rank_id`, `rank`, `subject_id`, `title_id`, `source_id`) VALUES
(23, 0, 2, 1, 1),
(22, 0, 1, 1, 1),
(15, 0, 2, 2, 2),
(14, 0, 2, 2, 1),
(20, 0, 1, 3, 2),
(18, 0, 1, 4, 2),
(19, 0, 1, 5, 2),
(21, 0, 1, 3, 1),
(24, 0, 1, 6, 2);
CREATE TABLE IF NOT EXISTS `title` (
`title_id` bigint(20) NOT NULL AUTO_INCREMENT,
`title` varchar(255) DEFAULT NULL,
`description` text,
`pre` varchar(255) DEFAULT NULL,
`last_modified_by` varchar(50) DEFAULT NULL,
`last_modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`title_id`)
) ENGINE=MyISAM;
INSERT INTO `title` (`title_id`, `title`, `last_modified`) VALUES
(1, 'new item', ' ', '2011-10-20 19:10:48'),
(2, 'another test', '2011-10-20 19:10:48'),
(3, 'and yet another', '2011-10-20 19:10:48'),
(4, 'one more', ' ', '2011-10-20 19:10:48'),
(5, 'adding more', ' ', '2011-10-20 19:10:48'),
(6, 'yes, another', ' ', '2011-10-20 19:10:48'),
(7, 'well, let''s see', ' ', '2011-10-20 19:10:48');
My need is for a query to select all titles that are not connected to a given subject in the rank table.
I have this working via a subquery:
SELECT title_id, title FROM title
WHERE title_id NOT IN (SELECT title_id FROM rank WHERE subject_id=2)
This returns the desired list:
+----------+-----------------+
| title_id | title |
+----------+-----------------+
| 3 | and yet another |
| 4 | one more |
| 5 | adding more |
| 6 | yes, another |
| 7 | well, let's see |
+----------+-----------------+
However, it gets a little slow when a large set of data is queried.
My question is if there is a way to return this result without the use of a subquery and if this alternative is any speedier.
Thanks in advance.
MySQL is usually faster with joins, though faster sub-queries are work in progress.
SELECT t.*
FROM title AS t
LEFT JOIN rank AS r ON (t.title_id = r.title_id AND r.subject_id = 2)
WHERE r.title_id IS NULL
As usual, you'll need to set up indexes on the foreign key (rank.title_id) and probably on the queried key (rank.subject_id).
You should read the MySQL documentation on [LEFT JOIN][1] if you want more details. There's also a nice trick with ONthat makes it different from WHERE.
The MySQL EXPLAIN command will tell you where your query needs help. e.g. EXPLAIN select title_id, title FROM title WHERE title_id NOT IN (select title_id from rank where subject_id=2)
My guess is that the true problem is that you don't have an index on rank.subject_id and that's causing a table scan (when rank has lots of rows).
Please create two indexes on subject_id and title_id and try the same.

MySQL filter query with relation

I'm having the following problem with 2 MySQL tables that have a relation:
I can easily query table 1 (address) when I want a full list or filter the result by name or email or such. But now I need to query table 1 and filter it based on the relational content of table 2 (interests). So, I need to find a row (usually many rows) in table 1 only if a (or more) conditions are met in table 2.
Here are the tables:
CREATE TABLE IF NOT EXISTS `address` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`email` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`countryCode` char(2) COLLATE utf8_unicode_ci DEFAULT NULL,
`languageCode` char(2) COLLATE utf8_unicode_ci DEFAULT NULL,
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `emailUnique` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
INSERT INTO `address` (`id`, `name`, `email`, `countryCode`, `languageCode`, `timestamp`) VALUES
(1, '', 'dummy#test.com', 'BE', 'nl', '2010-07-16 14:07:00'),
(2, '', 'test#somewhere.com', 'BE', 'fr', '2010-07-16 14:10:25');
CREATE TABLE IF NOT EXISTS `interests` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`address_id` int(11) unsigned NOT NULL,
`cat` char(2) COLLATE utf8_unicode_ci NOT NULL,
`subcat` char(2) COLLATE utf8_unicode_ci NOT NULL,
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `address_id` (`address_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
INSERT INTO `interests` (`id`, `address_id`, `cat`, `subcat`, `timestamp`) VALUES
(1, 1, 'aa', 'xx', '2010-07-16 14:07:00'),
(2, 1, 'aa', 'yy', '2010-07-16 14:07:00'),
(3, 2, 'aa', 'xx', '2010-07-16 14:07:00'),
(4, 2, 'bb', 'zz', '2010-07-16 14:07:00')
(5, 2, 'aa', 'yy', '2010-07-16 14:07:00');
ALTER TABLE `interests`
ADD CONSTRAINT `interests_ibfk_1` FOREIGN KEY (`address_id`) REFERENCES `address` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION;
For example, I need to find the address(es) that has (have) as interest cat=aa and subcat=xx. Or, another example, I need the address(es) with as interest both cat=aa and subcat=xx AND cat=aa and subcat=yy. Specially the latter is important and one has to keep in mind that both the address and the interest tables will be long lists and that the amount of cat/subcat combinations will vary. I'm working with reference queries through Zend_Db_Table (findDependentRowset) at the moment but that solution is way to slow for address lists numbering 100s and even 1000s of hits.
Thank you for your help.
SELECT a.name FROM address a
INNER JOIN interests i ON (a.id = i.address_id)
WHERE i.cat = "aa" AND i.subcat IN ('xx', 'yy')
I added another row in your interests table, to demonstrate a different result set between the two examples:
INSERT INTO interests VALUES (6, 2, 'aa', 'vv', '2010-07-16 14:07:00');
Then you may want to try using correlated subqueries as follows:
SELECT *
FROM address a
WHERE EXISTS (SELECT id
FROM interests
WHERE address_id = a.id AND
(cat = 'aa' and subcat = 'xx'));
Result:
+----+------+--------------------+-------------+--------------+---------------------+
| id | name | email | countryCode | languageCode | timestamp |
+----+------+--------------------+-------------+--------------+---------------------+
| 1 | | dummy#test.com | BE | nl | 2010-07-16 14:07:00 |
| 2 | | test#somewhere.com | BE | fr | 2010-07-16 14:10:25 |
+----+------+--------------------+-------------+--------------+---------------------+
2 rows in set (0.00 sec)
For the second example, we're testing for the new row added previously in order not to have the same result as above:
SELECT *
FROM address a
WHERE EXISTS (SELECT id
FROM interests
WHERE address_id = a.id AND
(cat = 'aa' and subcat = 'xx')) AND
EXISTS (SELECT id
FROM interests
WHERE address_id = a.id AND
(cat = 'aa' and subcat = 'vv'));
Result:
+----+------+--------------------+-------------+--------------+---------------------+
| id | name | email | countryCode | languageCode | timestamp |
+----+------+--------------------+-------------+--------------+---------------------+
| 2 | | test#somewhere.com | BE | fr | 2010-07-16 14:10:25 |
+----+------+--------------------+-------------+--------------+---------------------+
1 row in set (0.00 sec)
Using correlated subqueries is easy and straightforward. However keep in mind that it might not be the best in terms of performance, because the correlated subqueries will be executed once for each address in the outer query.