select the last study (max) course according the user - php

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
)

Related

select the candidates with all the skills using GROUP_CONCAT

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)

Stock with slow SQL query

I have been working with the same SQL query for a couple of hours and it is finally working. But, it is very slow.. I have been trying to optimize it, but no luck, any help. Here is the query (Lots of left joins...):
$sql ="SELECT u.id, u.display_name, IFNULL(SUM(r.total_rating)/COUNT(r.total_rating), 0) AS avg_rating, s.title AS study FROM users u
LEFT JOIN rating r ON u.id = r.user_id
LEFT JOIN usermeta m ON u.id = m.user_id
LEFT JOIN usermeta m1 ON u.id = m1.user_id
LEFT JOIN studies s ON m.meta_value = s.id
WHERE m.meta_key = 'study' AND m1.meta_key = 'subjects' AND m1.meta_value REGEXP '$subjectsvalues'
GROUP BY u.id, r.total_rating
ORDER BY avg_rating DESC
LIMIT 10";
Table structure for user table:
id | display_name | email
-------------------------
1 | Khar | ...
2 | SantaCruz | ...
Table structure for rating table:
id | rating_title | total_rating | user_id
-------------------------------------------
1 | dffd | 5 | 1
2 | fddfdffdd | 4 | 1
Table structure for usermeta table:
id | user_id | meta_key | meta_value
-------------------------------------
1 | 1 | study | 132
2 | 1 | subjects | 121,231
Table structure for studies table:
id | title
----------
1 | dsdsf
2 | sdfdf
Subject values are handled like so:
$subjectsvalues = '';
$subjects = explode(",", $subjects);
foreach($subjects as $val) {
$subjectsvalues = $subjectsvalues.",".$val.",|";
}
$subjectsvalues = $subjectsvalues."notdata";
First, left joins are unnecessary. So try this:
SELECT u.id, u.display_name, AVG(r.total_rating) AS avg_rating, s.title AS study
FROM users u JOIN
rating r
ON u.id = r.user_id JOIN
usermeta m
ON u.id = m.user_id JOIN
usermeta m1
ON u.id = m1.user_id JOIN
studies s
ON m.meta_value = s.id
WHERE m.meta_key = 'study' AND m1.meta_key = 'subjects' AND m1.meta_value REGEXP '$subjectsvalues'
GROUP BY u.id, r.total_rating
ORDER BY avg_rating DESC
LIMIT 10;
Then, I would be inclined to try indexes on usermeta(meta_key, user_id, meta_value). I assume the main ids in the tables are all primary keys.
I can't suggest an edit to Gordon Linoff's answer, so here's an improved version. You don't really need to join with usermeta twice unless usermeta is being compared with itself.
SELECT u.id, u.display_name, AVG(r.total_rating) AS avg_rating, s.title AS study
FROM users u JOIN
rating r
ON u.id = r.user_id JOIN
usermeta m
ON u.id = m.user_id JOIN
studies s
ON m.meta_value = s.id
WHERE m.meta_key = 'study' OR (m.meta_key = 'subjects' AND m.meta_value REGEXP '$subjectsvalues')
GROUP BY u.id, r.total_rating
ORDER BY avg_rating DESC
LIMIT 10;
Also, could you please explain the utility of your regular expression? It most likely won't match anything as $ indicating end of the pattern is placed at start of regex.

mysql count number of votes in votes table

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

Join Three tables in mysql with weird requirement

I have three tables in my db.
Table A has the fields
KEYID | KeyName
27 | Income
28 | Account Number
Table B has the fields
UserID | Email | Name | Phone
481 | test#gmail.com | test | 99999999
Table C has the fields
ID | KEYID | UserID | Value
1 | 27 | 481 | 10,000
I need to display the table fields headers are:
UserID | Email | Name | Phone | Income
and the table values should be like this:
481 | test#gmail.com | test | 99999999 | 10,000
I can get the KeyIDs which should be displayed in the table. In this example the KeyIDs string is '27' . I tried with joining and i can fetch & display the value in the table. but i dont know how i can show the key name as table header.
Any Idea.?
You can use a pair of inner join
select b.UserID, b.Email , b.Name, c.value as income
from tableB as b inner join tableC as C on b.userID = c.userId
inner join tableA as a on a.keyID = c.keyID
and a.keyname = 'Income';
and the query you provided in comment
select
b.UserID
, b.Email
, b.Name
, Group_Concat(Distinct Concat(c.keyID,’^:^’,c.value)
Order By c.id Separator ‘;’) As Keyvalues
from tableB as b
inner join tableC as C on b.userID = c.userId
inner join tableA as a on a.keyID = c.keyID;
and with CASE should be
select
b.UserID
, b.Email
, b.Name
, Group_Concat(Distinct CASE
WHEN c.keyID IN ('1,23,10') THEN Concat(c.keyID,’^:^’,c.value) END
Order By c.id Separator ‘;’) As Keyvalues
from tableB as b
inner join tableC as C on b.userID = c.userId
inner join tableA as a on a.keyID = c.keyID;
This query should help to get your desire result.
select b.UserID, b.Email, b.Name, b.Phone, c.Value as Income
from table_b as b
JOIN table_c as c ON (b.UserID = c.UserID)
where c.KEYID = 27
Try this:
SELECT b.userid, b.email, b.name, b.phone, c.value as income
FROM a
LEFT JOIN c on c.keyid = a.keyid
LEFT JOIN b ob b.userid = c.userid

mysql query within a query with privacy condition check

I have 3 tables.
myMembers
------------------------------------
id | username | privacy
------------------------------------
1 | userA | 0
2 | userB | 1
3 | userC | 0
4 | userD | 1
following
--------------------------------
id | user_id | follower_id
--------------------------------
1 | 2 | 1
posts
-------------------------------------
id | userID | username | statusMsg
--------------------------------------
1 | 4 | userD | Issac Newton is genius
2 | 2 | userB | Newton Saw apple
3 | 3 | userC | Newtonian Physics
4 | 1 | userA | Calculus came from Sir Newton
There is a search field. When a logged in user searches for 'keyword' in table 'posts', I want to omit results from those users who has set his privacy to '1' and WHERE searcher is not following user B.
The query should logically do this.
SELECT * from posts WHERE (match the keyword)
AND (
if (poster's privacy (which is set in myMembers)==1){
if (seacher is following poster){
select this post
}
}
else { select this post
}
)
LIMIT results to 5 rows
So for a keyword "Newton",
if userA is searching, rows 2,3,4 from 'posts' should be returned.
if userD is searching, only rows 1, 3 and 4 from 'posts' should be returned,
based on privacy and following
Edit: Tagging for future searches: IF condition within WHERE Clause in mySql
Please, try this query (also on SQL Fiddle):
SELECT p.id, p.user_id, m.username, m.privacy,
searcher.username "Searcher", p.status_msg
FROM posts p
JOIN members m ON m.id = p.user_id
LEFT JOIN following f ON p.user_id = f.user_id
JOIN members searcher ON searcher.username = 'userA'
WHERE (m.privacy = 0 OR (m.privacy = 1 AND f.follower_id = searcher.id)
OR m.id = searcher.id)
AND p.status_msg LIKE '%New%'
ORDER BY p.id
LIMIT 5;
I removed username field from posts table, as it is redundant. Also, I named tables and columns slightly different, so query might need cosmetic changes for your schema.
The first line in the WHERE clause is the one that you're looking for, it selects posts in the following order:
First posts from members without privacy;
Then posts from members that are followed by the current searcher;
Finally, posts of the member himself.
EDIT:
This query is using original identifiers:
SELECT p.id, p.`userID`, m.username, m.privacy,
searcher.username "Searcher", p.`statusMsg`
FROM posts p
JOIN `myMembers` m ON m.id = p.`userID`
LEFT JOIN following f ON p.`userID` = f.user_id
JOIN `myMembers` searcher ON searcher.username = 'userD'
WHERE (m.privacy = 0 OR f.follower_id = searcher.id OR m.id = searcher.id)
AND p.`statusMsg` LIKE '%New%'
ORDER BY p.id
LIMIT 5;
EDIT 2:
To avoid duplicates in case there're several followers for the user from the posts table, join and filtering conditions should be changed the following way (on SQL Fiddle):
SELECT p.id, p.user_id, m.username, m.privacy,
searcher.username "Searcher", p.status_msg
FROM posts p
JOIN members m ON m.id = p.user_id
JOIN members searcher ON searcher.username = 'userC'
LEFT JOIN following f ON p.user_id = f.user_id
AND follower_id = searcher.id
WHERE (m.privacy = 0 OR (m.privacy = 1 AND f.id IS NOT NULL)
OR m.id = searcher.id)
ORDER BY p.id
LIMIT 5;
Try the following:
SET #my_user_id= 1;
SELECT * FROM posts p
INNER JOIN myMembers m ON p.user_id= m.id
WHERE statusMsg LIKE '%'
AND privacy=0
AND user_id IN (SELECT follower_id FROM following f WHERE f.user_id=#my_user_id)
LIMIT 5
try this:
SELECT a.*
FROM posts a
LEFT JOIN (SELECT user_id
FROM following a1
INNER JOIN myMembers b1
ON a1.follower_id = b1.id
WHERE a1.follower_id = 1 AND
b1.privacy = 1
) b
ON a.userID = b.user_id AND
WHERE a.statusMsg LIKE '%search%' AND
b.user_id IS NULL
LIMIT 5;
or better approach without subquery:
SELECT a.*
FROM posts a
LEFT JOIN myMembers b
ON a.userID = b.id AND
b.privacy = 1
LEFT JOIN following c
ON a.userID = c.user_id AND
c.follower_id = 1
WHERE a.statusMsg LIKE '%search%' AND
b.id IS NULL AND
c.user_id IS NULL
LIMIT 5;
See: A Visual Explanation of SQL Joins

Categories