I have the MySql tables for candidates, candidate-skills, and skills.
Which is the best way to select the candidates with all the skills
I tried using the following query. But it is not accurate.
Select `t`.*, GROUP_CONCAT(DISTINCT(s.name)) as skills,
GROUP_CONCAT(DISTINCT(s.id)) as skill_ids
FROM `candidates` `t`
LEFT JOIN `candidate-skills` `cs` ON `t`.`id` = `cs`.`can_id`
LEFT JOIN `skills` `s` ON `cs`.`skill_id` = `s`.`id`
where s.id in ('8','10')
GROUP BY `t`.`id`
ORDER BY `t`.`id` desc
The two points what I want are:
All the skills should be shown (when commenting the where conditions in the )
Records with all skills are to be shown. (The records with one skill is also showing as I am using the where in array)
I am using codeigniter framework.
http://sqlfiddle.com/#!9/b75c3/49
instead of where use having clause.
select `t`.*, GROUP_CONCAT(DISTINCT(s.name)) as skills,
GROUP_CONCAT(DISTINCT(s.id)) as skill_ids
FROM `candidates` `t`
LEFT JOIN `candidate-skills` `cs` ON `t`.`id` = `cs`.`can_id`
LEFT JOIN `skills` `s` ON `cs`.`skill_id` = `s`.`id`
GROUP BY `t`.`id`
having find_in_set ('8', skill_ids) and find_in_set ('10', skill_ids)
ORDER BY `t`.`id` desc
in Codeigniter
//take all skill ids in array
$ids=['8','10'];
$this->db->select("t.*");
$this->db->select("GROUP_CONCAT(DISTINCT(s.name)) as skills");
$this->db->select("GROUP_CONCAT(DISTINCT(s.id)) as skill_ids");
$this->db->from("candidates t");
$this->db->join("candidate-skills cs","t.id = cs.can_id");
$this->db->join("skills s","cs.skill_id = s.id");
$this->db->group_by("t.id");
foreach ($ids as $id) {
$this->db->having("find_in_set ('$id', skill_ids)");
}
$this->db->order_by("t.id","desc");
$query=$this->db->get();
$candidates=$query->result();
The most flexible way is using multiple JOINs; GROUP_CONCAT and comma delimited lists are considered an antipattern and it might not work if the concatenation isn't done exactly in the correct order (skill set 1,2,5 is considered not the same as 1,5,2).
SELECT c.* FROM candidates AS c
JOIN candidateskills AS cs ON (cs.cand_id = c.id)
JOIN skills AS sk1 ON (cs.skill_id = sk1.id)
JOIN skills AS sk2 ON (cs.skill_id = sk2.id)
...other sk(N)...
WHERE (sk1.skill = 'waterskiing')
AND (sk2.skill = 'snowboarding')
...
;
This allows easy tailoring of skills if, for example, each skill has a skill level and you need for snowboarding to be skilled at or above level 5. This kind of flexibility is a hell to do with GROUP_CONCAT.
But for simple matching, you can do it faster by selecting the skills you want and just counting them:
SELECT c.* FROM candidates AS c
JOIN candidateskills AS cs ON (cs.cand_id = c.id)
WHERE cs.skill_id IN (1, 7, 24, 19, 115)
GROUP BY c.id
HAVING COUNT(1) = 5;
(In more proper SQL you'd need to indicate explicitly all fields of c instead of "c.*", and repeat them in the GROUP BY clause. More clever RDBMS servers will not care as long as you group by c's primary key. MySQL currently does not care anyway, but in strict mode, it would).
For each skill you run a single, fast query on skills to retrieve its ID and assemble the query above.
Or you can do in a single, larger query as long as you have an exact match for the skill:
SELECT c.* FROM candidates AS c
JOIN candidateskills AS cs ON (cs.cand_id = c.id)
JOIN skills AS s ON (cs.skill_id = s.id)
WHERE s.skill IN ('javascript', 'html5', 'php')
GROUP BY c.id
HAVING COUNT(1) = 3;
Since you want this in PHP:
$skills = array('javascript', 'html5', 'php');
$skno = count($skills);
$set = implode(',', array_fill('?', $skno));
$params = $skills;
$params[] = $skno;
$query = "SELECT c.* FROM candidates AS c
JOIN candidateskills AS cs ON (cs.cand_id = c.id)
JOIN skills AS s ON (cs.skill_id = s.id)
WHERE s.skill IN ({$set})
GROUP BY c.id
HAVING COUNT(1) = ?";
$stmt = $db->prepare($query);
$stmt->execute($params);
while ($candidate = $stmt->fetch(PDO::FETCH_ASSOC)) {
...
}
Maybe this
select t.* ,
s.skills,s.skills_id
FROM `candidates` `t`
join
(
select t.id tid, group_concat(s.name) skills, group_concat(s.id order by s.id) skills_id
FROM `candidates` `t`
LEFT JOIN `candidate-skills` `cs` ON `t`.`id` = `cs`.`can_id`
LEFT JOIN `skills` `s` ON `cs`.`skill_id` = `s`.`id`
group by t.id
) s
on s.tid = t.id
where instr(skills_id,'8,10') > 0
+----+----------------+---------------------+-------------+------------+-------------------------------------------+-------------+
| id | name | created_on | modified_on | is_deleted | skills | skills_id |
+----+----------------+---------------------+-------------+------------+-------------------------------------------+-------------+
| 1 | Eugine | 2017-05-23 11:44:30 | NULL | N | zend framework 2,bootstrap,wordpress | 8,10,12 |
| 2 | Frinoy Francis | 2017-05-23 16:44:29 | NULL | N | html,html5,zend framework 2,php,bootstrap | 1,4,8,10,11 |
+----+----------------+---------------------+-------------+------------+-------------------------------------------+-------------+
2 rows in set (0.03 sec)
MariaDB [sandbox]> select t.* ,
-> s.skills,s.skills_id
-> FROM `candidates` `t`
-> join
-> (
-> select t.id tid, group_concat(s.name) skills, group_concat(s.id order by s.id) skills_id
-> FROM `candidates` `t`
-> LEFT JOIN `candidate-skills` `cs` ON `t`.`id` = `cs`.`can_id`
-> LEFT JOIN `skills` `s` ON `cs`.`skill_id` = `s`.`id`
-> group by t.id
-> ) s
-> on s.tid = t.id
-> where instr(skills_id,'') > 0
-> ;
+----+----------------+---------------------+-------------+------------+-------------------------------------------+-------------+
| id | name | created_on | modified_on | is_deleted | skills | skills_id |
+----+----------------+---------------------+-------------+------------+-------------------------------------------+-------------+
| 1 | Eugine | 2017-05-23 11:44:30 | NULL | N | zend framework 2,bootstrap,wordpress | 8,10,12 |
| 2 | Frinoy Francis | 2017-05-23 16:44:29 | NULL | N | html,html5,zend framework 2,php,bootstrap | 1,4,8,10,11 |
| 3 | Arun | 2017-05-28 12:56:24 | NULL | N | bootstrap | 8 |
+----+----------------+---------------------+-------------+------------+-------------------------------------------+-------------+
3 rows in set (0.03 sec)
Related
I have two tables: Project and ProjectFieldValue
I am needing to return results from the ProjectFieldValue based on multiple different key/value options in the table. I can get it to work with one key/value pair, but once I add another AND statement to the query it returns nothing.
Here is a sample of my tables followed by my query...
Project Table
----------------------
id | name
----------------------
1 | Project #1
ProjectFieldValue Table
I have millions of records like this and they are all stored in this table and associated to a specific Project.
----------------------------------------------------------------------------------------
id | project_id | text_value | date_value | field_key
----------------------------------------------------------------------------------------
1 | 1 | Active | NULL | contract_status
2 | 1 | NULL | 2020-06-02 00:01:58 | listing_date
3 | 1 | Seller | NULL | contract_client_type
4 | 1 | Active | NULL | contract_option
Here are my queries broken down by what works and doesn't work:
This does work, but, it is searching on 1 key/value pair...
SELECT p.name, p.id
FROM ProjectFieldValue pfv
LEFT JOIN Project p
ON pfv.project_id = p.id
WHERE (pfv.text_value IN ( SELECT text_value FROM ProjectFieldValue WHERE text_value IN ('Active')) AND field_key = 'contract_status')
GROUP BY p.id
This doesn't work because it is searching on 3 key/value pairs...
SELECT p.name, p.id
FROM ProjectFieldValue pfv
LEFT JOIN Project p
ON pfv.project_id = p.id
WHERE (pfv.text_value IN ( SELECT text_value FROM ProjectFieldValue WHERE text_value IN ('Active')) AND field_key = 'contract_status')
AND (pfv.text_value IN ( SELECT text_value FROM ProjectFieldValue WHERE text_value IN ('Seller')) AND field_key = 'contract_client_type')
AND (pfv.date_value between '2020-07-08 00:00:00' AND '2020-07-11 23:59:59' AND pfv.field_key = 'listing_date')
GROUP BY p.id
Goal
Ultimately, what I would need to be able to do is search on unlimited key/value pairs in this table and return all results grouped by the p.id
Thanks for your help!
This should do the thing.
SELECT p.name, p.id
FROM ProjectFieldValue pfv
LEFT JOIN Project p
ON pfv.project_id = p.id
WHERE (field_key = 'contract_status' AND pfv.text_value = 'Active')
OR (field_key = 'contract_client_type' AND pfv.text_value = 'Seller')
OR (pfv.field_key = 'listing_date' AND pfv.date_value between '2020-07-08 00:00:00' AND '2020-07-11 23:59:59')
GROUP BY p.id;
But I have doubt why you are Left joining Project and ProjectFieldValue. A simple Inner Join should solve your purpose. As you are grouping by on p.id. You may encourage a lot of NULLed columns. So I would suggest below.
SELECT p.name, p.id
FROM Project p
JOIN ProjectFieldValue pfv
ON p.id = pfv.project_id
WHERE (field_key = 'contract_status' AND pfv.text_value = 'Active')
OR (field_key = 'contract_client_type' AND pfv.text_value = 'Seller')
OR (pfv.field_key = 'listing_date' AND pfv.date_value between '2020-07-08 00:00:00' AND '2020-07-11 23:59:59')
GROUP BY p.id;
What I am doing?
I am displaying the user according to there studied.
I want to fetch the last course studied by each user.
Problem?
I am not getting the last course name according to last studied course. I am getting the first course name.(java) but i want (oracle) as last studied course by user.
User Table
user_id | Name
====================
1 | Zishan
2 | Ellen
Course Table
course_id | course_name | user_id | course_year
==================================================
1 | java | 1 | 2015
2 | C++ | 1 | 2017
3 | oracle | 1 | 2016
4 | dot net | 2 | 2016
Result Table
Name | last_course_name | last_course_year
============================================
Zishan | java | 2017
Ellen | dot net | 2016
Expected Result
Name | last_course_name | last_course_year
============================================
Zishan | C++ | 2017
Ellen | dot net | 2016
Query
SELECT `u`.`name`, MAX(`c`.`course_year`) as last_course_year , `c`.`course_name` as last_course_name
FROM `user` as `u`
LEFT OUTER JOIN `course` as `c` ON `u`.`id` = `c`.`user_id`
GROUP BY `u`.`id`
Active Record Query:
$this->db->select('u.name','c.course_name as last_course_name');
$this->db->select_max('c.course_year as last_course_year');
$this->db->from('user as u');
$this->db->join('course as c', 'u.id = c.user_id', 'left');
$this->db->join('course as c1', 'c.user_id = c1.user_id', 'left outer');
$this->db->where('c1.user_id IS NULL', null, false);
$this->db->group_by('u.id');
$user_couse_data_query = $this->db->get();
Here you go to the latest row from course table for each student
SELECT `u`.`name`, `c`.`course_name` as last_course_name ,c.course_year
FROM `user` as `u`
LEFT JOIN `course` as `c` ON `u`.`id` = `c`.`user_id`
LEFT JOIN `course` as `c1` ON `c`.`user_id` = `c1`.`user_id` AND `c`.`course_year` < `c1`.`course_year`
WHERE `c1`.`user_id` is null
Demo
Note it may return multiple courses if they share same year
Equivalent active record query will be something like
$this->db->select('u.name','c.course_name as last_course_name','c.course_year as last_course_year');
$this->db->from('user as u');
$this->db->join('course as c', 'u.id = c.user_id', 'left');
$this->db->join('course as c1', 'c.user_id = c1.user_id AND c.course_year < c1.course_year', 'left');
$this->db->where('c1.user_id IS NULL', null, false);
$user_couse_data_query = $this->db->get();
Try like this;
select U.Name, C.course_name as last_course_name, C2.maxCourseYear as last_course_year from User U
inner join
( select user_id,max(course_year) as maxCourseYear from Course C
group by C.user_id) C
ON U.user_id = C.user_id
inner join Course C2 ON C2.course_year = C.maxCourseYear
Your query does not work because you need to give all the columns in group by on which aggregate function is not applied.
Try this out:
select c.name,b.last_course_name,a.last_course_year
from
(select user_id,max(course_year) as last_course_year
from course_table
group by user_id) a
left join
course_table b
on a.user_id = b.user_id and a.course_year = b.course_year
left join
user_table c
on a.user_id = c.user_id;
Let me know in case of any queries.
Use the following subquery
SELECT u.name,
c.course_year as last_course_year,
c.course_name as last_course_name
FROM user as u
LEFT OUTER JOIN course as c ON u.id = c.user_id and
(c.user_id, c.course_year) IN
(
SELECT c.user_id, MAX(c.course_year)
FROM course as c
GROUP BY c.user_id
)
I have a simple multiple school management system and I am trying to get total number of teachers, and total number of students for a specific school. My table structures are as follows:
teachers
--------------------------
id | schoolid | Name | etc...
--------------------------
1 | 1 | Bob |
2 | 1 | Sarah|
3 | 2 | John |
students
--------------------------
id | schoolid | Name | etc...
--------------------------
1 | 1 | Jack |
2 | 1 | David|
3 | 2 | Adam |
schools
--------------------------
id | Name | etc...
---------------------------
1 | River Park High |
2 | Stirling High |
I can count just all teachers with the following query:
SELECT COUNT(a.id) AS `totalteachers`
FROM teachers a
LEFT JOIN schools b ON a.schoolid = b.id WHERE b.id = '1'
and similarly I can count the number of teachers with the following query:
SELECT COUNT(a.id) AS `totalstudents`
FROM students a
LEFT JOIN schools b ON a.schoolid = b.id WHERE b.id = '1'
I am however struggling with trying to combine these two queries to get a simple result like this:
totalstudents | totalteachers
--------------------------------
2 | 2
I have tried the following:
SELECT COUNT(a.id) as `totalteachers`, COUNT(c.id) as `totalstudents`
FROM teachers a
LEFT JOIN schools b ON a.schoolid = b.id
LEFT JOIN students c ON c.schoolid=b.id WHERE b.id = '5'
You can do something like this
SELECT
id, name, s.total AS totalstudents, t.total AS totalteachers
FROM schools
JOIN (SELECT schoolid, COUNT(id) AS total FROM teachers GROUP BY schoolid)
AS t ON t.schoolid = id
JOIN (SELECT schoolid, COUNT(id) AS total FROM students GROUP BY schoolid)
AS s ON s.schoolid = id
then you can add where id = 2 or whatever to limit the school.
The problem with the multiple left joins is it generates additional records for each teacher to each student; artifically inflating your counts
There's four ways to solve this: (best imo is what Andrew bone did)
Simply select inline without the joins so the counts are not inflated. (most desirable in my mind as it's easy to maintain)
SELECT (SELECT COUNT(a.id) AS `totalteachers`
FROM teachers a
WHERE A.SchoolID = '1') as TotalTeachers
, (SELECT COUNT(a.id) AS `totalstudents`
FROM students a
WHERE a.SchoolID = '1') as TotalStudents
Use subqueries to get the counts first before the joins, then join. Since count will always be 1 a cross join works.
SELECT totalTeachers, totalStudents
FROM (SELECT COUNT(a.id) AS `totalteachers`
FROM teachers a
LEFT JOIN schools b
ON a.schoolid = b.id
WHERE b.id = '1')
CROSS JOIN (SELECT COUNT(a.id) AS `totalstudents`
FROM students a
LEFT JOIN schools b ON a.schoolid = b.id
WHERE b.id = '1')
Use key word distinct within the count so as not to replicate the counts and negate the artificial inflation (least desirable in my mind as this hides the artifical count increase)
SELECT COUNT(distinct a.id) as `totalteachers`, COUNT(distinct c.id) as `totalstudents`
FROM teachers a
LEFT JOIN schools b ON a.schoolid = b.id
LEFT JOIN students c ON c.schoolid=b.id WHERE b.id = '5'
Another way would be to use a window functions, however these are not available in mySQL.
SELECT COUNT(t.id) AS TotalTeachers, COUNT(st.id) AS TotalStudents
FROM schools s
INNER JOIN teachers t
ON s.id = t.schoolid
INNER JOIN students st
ON s.id = st.schoolid
Try this SQL. I havn't try it but it should work.
I have 3 tables named election_cand, candidate and votes:
election_cand table:
ele_can_id | election_id | candidate_id
candidate table:
id | canname | canadd | canphone | canmail | candes | canphoto
votes table:
voteid | candidateid | voterid | electionid
my tables with data are:
I want the desired result as
I write the mysql query as:
SELECT a.*,b.*,c.*, count(voteid) AS numrows
FROM election_cand a
left join votes b on a.election_id=b.electionid
left join candidate c on a.candidate_id=c.id
where a.election_id='$get_ele_id' group by a.candidate_id
As I see you only need this query:
SELECT
c.*, COUNT(e.ele_can_id) AS numrows
FROM candidate c
Left Join election_cand e on c.id = e.candidate_id
GROUP BY c.id
describe your schema so we can help you more.
Try this code:
SELECT
c.*, COUNT(v.candidateid) FROM candidate c
LEFT JOIN votes v ON c.id = v.candidateid
WHERE c.id IN
( SELECT candidate_id FROM election_cand WHERE election_id = 4 )
GROUP BY v.candidateid
My query is:
SELECT * FROM (SELECT user_id
FROM goals
LEFT JOIN goal_results ON goals.id = goal_results.goal_id
WHERE goals.enabled = 1 AND validation = 'accepted') AS u
My results are:
| user_id |
| 4 |
| 5 |
| 4 |
| 5 |
| 6 |
I need to get the count of each of them like:
4 > 2
5 > 2
6 > 1
I've tried many kind of queries with subqueries or using distinct but I'm lost and I don't reach my goal.
You can achieve this by using GROUP BY and COUNT. The subquery is not needed.
SELECT goal_results.user_id, COUNT(*) qty
FROM goals
LEFT JOIN goal_results ON goals.id = goal_results.goal_id
WHERE goals.enabled = 1 AND goal_results.validation = 'accepted'
GROUP BY goal_results.user_id
Adding group by will do.
SELECT u.user_id, count(*) FROM (SELECT user_id
FROM goals
LEFT JOIN goal_results ON goals.id = goal_results.goal_id
WHERE goals.enabled = 1 AND validation = 'accepted') AS u
group by u.user_id
You don't need to do this with a subquery:
SELECT g.user_id, count(*)
FROM goals g LEFT JOIN
goal_results gr
ON g.id = gr.goal_id
WHERE g.enabled = 1 AND validation = 'accepted'
GROUP BY g.user_id;
My guess, though, is that you really want:
SELECT g.user_id, count(gr.goal_id)
FROM goals g LEFT JOIN
goal_results gr
ON g.id = gr.goal_id
WHERE g.enabled = 1 AND validation = 'accepted'
GROUP BY g.user_id;
This will return 0 for users that have no goals. The first will return 1 for them.