MySQL Query to determine field relationship - mysql

I have a table with 3 fields. principal, associate, status.
How can I determine whether the logged-in user is either a principal or an associate, and with how many people he is associated? I would also like to determine the status of the relationship.
query = "SELECT M.id, M.surname, M.firstname, R.principal_id
, R.associate_id, R.status
FROM tbl_members M, tbl_relationship R WHERE
-- ---------------------------------------------
-- to make sure user exists in the members table
-- ---------------------------------------------
(R.principal_id = M.id OR R.associate_id = M.id)
AND (logged-in-user = R.associate_id OR logged-in-user = R.principal_id)
AND R.status =1"
ERROR:- THIS LISTS times 2 of everybody in the relationship table.
Tables invloved:-
tbl_members (id, surname, firstname)
tbl_relationship (id, associate_id[FK tbl_member id], principal_id[FK tbl_member id])
associate_id | principal_id | status
1 3 1
1 4 1
2 1 0
2 3 0
5 1 1
6 1 1
From the above how many people are associated with logged-in-user(1)?

Try:
SELECT
m.id, m.surname, m.firstname,
COUNT(assoc.id) AS relationships_as_associate,
COUNT(princ.id) AS relationships_as_principal
FROM tbl_members m
LEFT JOIN tbl_relationship assoc
ON assoc.associate_id = m.id AND assoc.status = 1
LEFT JOIN tbl_relationship princ
ON princ.principal_id = m.id AND princ.status = 1
WHERE m.id = logged-in-user
GROUP BY m.id
(guessing that relationships with status = 0 can be ignored)
If you only want the number of relationships this user is in (no matter the user's role):
SELECT
m.id, m.surname, m.firstname,
COUNT(rel.id) AS active_relationships
FROM tbl_members m
LEFT JOIN tbl_relationship rel
ON (rel.associate_id = m.id OR rel.principal_id = m.id)
AND rel.status = 1
WHERE m.id = logged-in-user
GROUP BY m.id
EDIT:
According to your comment, this is a query that will get all the users who are in relationship with the logged in user, with their details, and the role they are playing in the relationship (some of them are principals, others are associates):
SELECT m.id, m.firstname, m.surname, temp.role
FROM tbl_members m
JOIN ((SELECT rel.principal_id as id, 'Principal' as role
FROM tbl_relationship rel
WHERE rel.associate_id = logged-in-user
AND rel.status = 1)
UNION
(SELECT rel.associate_id as id, 'Associate' as role
FROM tbl_relationship rel
WHERE rel.principal_id = logged-in-user
AND rel.status = 1)
) as temp
ON temp.id = m.id

I'm still not 100% sure how your system is suppose to work, but this is what I came up with.
Select M.id, surname, firstname, NumPrincipal, NumAssociate, 'Type' =
Case
When NumPrincipal > 0 Then 'Principal'
Else 'Associate'
End
from
(Select * From tbl_members) AS M
Left Join
(Select M.id, COUNT(M.id) 'NumPrincipal' from
tbl_members M inner join tbl_relationship R on M.id = R.principal_id
Group By M.Id) AS P
On M.id = P.id
Left Join
(Select M.id, COUNT(M.id) 'NumAssociate' from
tbl_members M inner join tbl_relationship R on M.id = R.associate_id
Group By M.Id) AS A
On M.id = A.id
Where M.id = logged_in_user_id
The part that still seems weird to me is if they initiate an relationship then they will be a principal, but they can also be an associate in another case, it depends on a specific relationship between two users. My code is just if a user logs in and they have been principal before then they are a principal. Not sure if you want code that is like the current user is looking at a specific relationship they have with another user, find who is principal in that relationship?
Edit: Heres the code to see if the current user is the principal when looking at a single relationship between the current user and another user. This assumes there is only one relationship between two users or you will get multiple records back.
Select *,
'type' =
case
when principal_id = currentUserId then 'Principal'
else 'Associate'
end
from tbl_relationship where
(associate_id = currentUserId or principal_id = currentUserId )
and
(associate_id = OtherUserId or principal_id = OtherUserId )
Edit Again, Here is the number of associates regardless of Principal/Associate:
Select M.id, surname, firstname, NumAssociates
From
tbl_members M
Left Join
(Select id, count(NumAssociate) 'NumAssociates' From
((Select M.id, M.id 'NumAssociate' from
tbl_members M inner join tbl_relationship R on M.id = R.principal_id where status = 1)
Union All
(Select M.id, M.id 'NumAssociate' from
tbl_members M inner join tbl_relationship R on M.id = R.associate_id where status = 1)) AS T
Group By Id) AS N
on M.id = N.Id

Related

conditional join between several tables of moodle's database

I need to create a join query between several tables based on the output of some previous table. I am trying to use case when statement as follows but its showing error! any kind of help is appreciated
SELECT completion.userid, completion. coursemoduleid, completion. timemodified,
module.course, user. idnumber as student_id, m.name as module_name, activity.name as activity_name
FROM `mdl_course_modules_completion` as completion
join mdl_course_modules as module
on completion. coursemoduleid = module.id
join mdl_user as user on user.id = completion.userid
join mdl_modules as m on completion. coursemoduleid = m.id
join
CASE WHEN module_name = 'assign'THEN 'mdl_assign'
WHEN module_name = 'assignment' THEN 'mdl_assignment'
ELSE "quiz"
END AS activity
on activity.id = m.id LIMIT 0, 30
There is no such thing as "conditional join". Instead you can use two LEFT JOINs and COALESCE():
SELECT completion.userid,
...
COALESCE(activity1.name, activity2.name) as activity_name -- SELECT first non NULL value
FROM `mdl_course_modules_completion` as completion
...
LEFT JOIN mdl_assign activity1
ON activity1.id = m.id -- JOIN condition
AND module_name = 'assign' -- CASE condition
LEFT JOIN mdl_assignment activity2
ON activity2.id = m.id -- JOIN condition
AND module_name = 'assignment' -- CASE condition
Hope this will help you -
SELECT completion.userid, completion. coursemoduleid, completion. timemodified,
module.course, user. idnumber as student_id, m.name as module_name,(CASE WHEN (a.name != '') THEN a.name ELSE (CASE WHEN (ass.name != '') THEN ass.name ELSE q.name END) END) as activity_name
FROM `mdl_course_modules_completion` as completion
join mdl_course_modules as module
on completion. coursemoduleid = module.id
join mdl_user as user on user.id = completion.userid
join mdl_modules as m on completion. coursemoduleid = m.id
left join mdl_assign as a on a.id = m.id
left join mdl_assignment as ass on ass.id = m.id
left join mdl_quiz as q on q.id = m.id
where m.name in ('assign','assignment','quiz')
LIMIT 0, 30

MySQL Column 'id' in IN/ALL/ANY subquery is ambiguous

I am trying to do a search functionalities that involves three tables.
Searching for users and returning wheather the user id 1 is a friend of the returned users. Also The returned users is being filtered from a third table where it checks tag of that users.
So I can say, "Return users who has tag 'Programming', 'Php'
in userinterests table and also if the returned user is a friend of usr id 1 or not "
I am trying to use the bellow query but getting Column 'id' in IN/ALL/ANY subquery is ambiguous
If I remove the left join then it works.
SELECT n.id, n.firstName, n.lastName, t.id, t.tag, t.user_id, if(id in (
SELECT u.id as id from friends f, users u
WHERE CASE
WHEN f.following_id=1
THEN f.follower_id = u.id
WHEN f.follower_id=1
THEN f.following_id = u.id
END
AND
f.status= 2
), "Yes", "No") as isFriend
FROM users n
LEFT JOIN userinterests t on n.id = t.id
WHERE t.tag in ('Programming', 'Php')
Thank you for your time :)
Qualify all your column names. You seem to know this, because all other column names are qualified.
I'm not sure if your logic is correct, but you can fix the error by qualifying the column name:
SELECT . . .
(CASE WHEN n.id IN (SELECT u.id as id
FROM friends f CROSS JOIN
users u
WHERE CASE WHEN f.following_id=1
THEN f.follower_id = u.id
WHEN f.follower_id=1
THEN f.following_id = u.id
END
) AND
f.status= 2
THEN 'Yes' ELSE 'No'
END) as isFriend
. . .
This is the way I will go for your approach:
1) I used INNER JOIN instead of LEFT JOIN for skip users that are not related to tags: Programming and Php.
2) I replaced the logic to find the set of friends related to user with id equal to 1.
SELECT
n.id,
n.firstName,
n.lastName,
t.id,
t.tag,
t.user_id,
IF(
n.id IN (SELECT follower_id FROM friends WHERE status = 2 AND following_id = 1
UNION
SELECT following_id FROM friends WHERE status = 2 AND follower_id = 1),
"Yes",
"No"
) AS isFriend
FROM
users n
INNER JOIN
userinterests t ON n.id = t.id AND t.tag IN ('Programming', 'Php')
Just curious, whats is the meaning of status = 2 ?

Error with ORDER BY clause using UNION in MySQL

I have the following query in MySQL:
(SELECT ue.id, ue.userid, ue.status, ue.timestart, ue.timeend, e.courseid,
e.id AS enrolid, ra.roleid
FROM user_enrolments ue
JOIN enrol e ON e.id = ue.enrolid
JOIN course c ON c.id = e.courseid
JOIN user u ON u.id = ue.userid
JOIN context ct ON ct.instanceid = c.id
LEFT JOIN role_assignments ra ON ra.userid = u.id AND
ra.contextid = ct.id AND
ra.itemid = e.id
WHERE e.customint1 = 1 AND u.deleted = 0 AND
ct.contextlevel = 50 AND (ue.status = 0 OR ue.status = 1))
UNION
(SELECT de.enrolid AS id, de.userid, de.status, de.date_ini, de.date_fin,
de.courseid, de.enrolid, de.roleid
FROM deleted_enrols de
JOIN user u ON u.id = de.userid
WHERE userid = ANY (SELECT userid FROM local_users WHERE clientid = 1))
ORDER BY u.firstname, u.lastname, c.fullname LIMIT 0, 100
If I delete ORBER BY and LIMIT, this query works fine... but the ORDER BY clause gives an error:
Table 'u' from one of the SELECTs cannot be used in global ORDER clause
If I delete the parentheses of both SELECT querys, the error is different:
Table 'u' from one of the SELECTs cannot be used in field list
I have also tried with UNION ALL, but it does not work either.
Any suggestion or clue? Thanks in advance for your time...
The results of your UNION do not include any fields from table 'u', so those results cannot be sorted by table 'u' fields.
You could perhaps perform the UNION and then re-join the results to table 'u', and then use that to sort the results by table 'u' fields. A similar issue exists for sorting on
course.fullname, so that would need to be joined back in, too.
SELECT x.id, x.userid, x.status, x.timestart, x.timeend, x.courseid, x.enrolid, x.roleid
FROM ((SELECT ue.id, ue.userid, ue.status, ue.timestart, ue.timeend, e.courseid,
e.id AS enrolid, ra.roleid
FROM user_enrolments ue
JOIN enrol e ON e.id = ue.enrolid
JOIN course c ON c.id = e.courseid
JOIN user u ON u.id = ue.userid
JOIN context ct ON ct.instanceid = c.id
LEFT JOIN role_assignments ra ON ra.userid = u.id
AND ra.contextid = ct.id
AND ra.itemid = e.id
WHERE e.customint1 = 1 AND u.deleted = 0
AND ct.contextlevel = 50 AND (ue.status = 0 OR ue.status = 1))
UNION
(SELECT de.enrolid AS id, de.userid, de.status, de.date_ini, de.date_fin,
de.courseid, de.enrolid, de.roleid
FROM deleted_enrols de
JOIN user u ON u.id = de.userid
WHERE userid = ANY (SELECT userid FROM local_users WHERE clientid = 1))
) x
JOIN user z ON z.id = x.userid
JOIN course d ON d.id = x.courseid
ORDER BY z.firstname, z.lastname, d.fullname LIMIT 0, 100
Assuming you want to sort the whole lot, try parentheses round the whole query with the ORDER BY done afterwards:
select id, userid, status, timestart, timeend, courseid, enrolid, roleid from
((SELECT ue.id, ue.userid, ue.status, ue.timestart, ue.timeend, e.courseid,
e.id AS enrolid, ra.roleid, u.firstname, u.lastname, c.fullname
FROM user_enrolments ue
JOIN enrol e ON e.id = ue.enrolid
JOIN course c ON c.id = e.courseid
JOIN user u ON u.id = ue.userid
JOIN context ct ON ct.instanceid = c.id
LEFT JOIN role_assignments ra ON ra.userid = u.id AND
ra.contextid = ct.id AND
ra.itemid = e.id
WHERE e.customint1 = 1 AND u.deleted = 0 AND
ct.contextlevel = 50 AND (ue.status = 0 OR ue.status = 1))
UNION
(SELECT de.enrolid AS id, de.userid, de.status, de.date_ini, de.date_fin,
de.courseid, de.enrolid, de.roleid, u.firstname, u.lastname, ' ' as fullname
FROM deleted_enrols de
JOIN user u ON u.id = de.userid
WHERE userid = ANY (SELECT userid FROM local_users WHERE clientid = 1))) s1
ORDER BY firstname, lastname, fullname LIMIT 0, 100
(obviously fullname in the second SELECT statement would be populated however seems sensible)
You need to include the data to be ordered by in the selects of the unioned queries; an ORDER BY following a UNION is handled as if it were SELECT * FROM (unions) ORDER BY ... so anything not coming out of the union cannot be used for ordering.
Ironically, a query similar to that is the key to getting what you want though, with something like
SELECT x, y, z
FROM (
SELECT x, y, z, somethingIdontactuallywant
FROM blah
UNION
SELECT a, b, c, somethingIdontactuallywant
FROM blah2
) AS u
ORDER BY u.somethingIdontactuallywant
As mysql documentation on union says:
This kind of ORDER BY cannot use column references that include a
table name (that is, names in tbl_name.col_name format). Instead,
provide a column alias in the first SELECT statement and refer to the
alias in the ORDER BY. (Alternatively, refer to the column in the
ORDER BY using its column position. However, use of column positions
is deprecated.)
Also, if a column to be sorted is aliased, the ORDER BY clause must
refer to the alias, not the column name.
So, do not refer to any table names and use columns that are actually in the resultset of the union.

mysql query not giving accurate result

I am working on a query whose purpose is to get the records of all the students whose financialyear_id!=4 and don't dispaly records even if he/she has finacialyear_id other then 4 exist.
I have written a query but it gives me the record of that student whose finacialyear_id!=4 but I want to achieve that no records will be shown if financialyear_id=4 exist for any student.
SELECT a.id aid
, s.id sid
, s.name
, s.father_name
, s.cnic
, f.financialyear_id
FROM student s
JOIN academic_info a
ON a.s_id = s.id
LEFT
JOIN fee_issued f
ON a.id = f.academic_info_id
WHERE f.financialyear_id != 4
AND a.is_data_locked = 0
AND a.university_id = 60;
Foreign Key: s_id in both tables academic_info and fee_issued,academic_info_id in fee_issued table.
You used a keyword in your request: "but I want to achieve that no records will be shown if financialyear_id=4 exist for any student." So use EXISTS (or IN which does about the same) to check for existence.
As you are using MySQL you must write the select condition for academic_info twice. Other DBMS handle this more elegantly.
select a.id as aid, s.id as sid, s.name, s.father_name, s.cnic, f.financialyear_id
from student s
join academic_info a on a.s_id = s.id and a.is_data_locked = 0 and a.university_id = 60
left join fee_issued f on f.academic_info_id = a.id
where s.id not in
(
select ai.s_id
from academic_info ai
join fee_issued fi on fi.academic_info_id = ai.id and fi.financialyear_id != 4
where ai.is_data_locked = 0 and ai.university_id = 60
);
Above query also gets you students that have no fee_issued at all. If you want these removed, change the left join to an inner join.
EDIT: Here is the same with NOT EXISTS.
select a.id as aid, s.id as sid, s.name, s.father_name, s.cnic, f.financialyear_id
from student s
join academic_info a on a.s_id = s.id and a.is_data_locked = 0 and a.university_id = 60
left join fee_issued f on f.academic_info_id = a.id
where not exists
(
select *
from academic_info ai
join fee_issued fi on fi.academic_info_id = ai.id and fi.financialyear_id != 4
where ai.is_data_locked = 0 and ai.university_id = 60
and ai.s_id = s.id
);

MySQL query optimization

Just wondering what's a better way to write this query. Cheers.
SELECT r.user_id AS ID, m.prenom, m.nom
FROM `0_rank` AS l
LEFT JOIN `0_right` AS r ON r.rank_id = l.id
LEFT JOIN `0_user` AS m ON r.user_id = m.id
WHERE r.section_id = $section_id
AND l.rank = '$rank_name' AND depart_id IN
(SELECT depart_id FROM 0_depart WHERE user_id = $user_id AND section_id = $section_id)
GROUP BY r.user_id
Here are the table structures:
0_rank: id | section_id | rank_name |
other_stuffs
0_user: id | prenom | nom | other_stuffs
0_right: id | section_id | user_id |
rank_id | other_stuffs
0_depart: id | section_id | user_id | depart_id
| other_stuffs
The idea is to use the same in a function like:
public function usergroup($section_id,$rank_name,$user_id) {
// mysql query goes here to get a list of appropriate users
}
Update: I think I have not been able to express myself clearly earlier. Here is the most recent query that seems to be working.
SELECT m.id, m.prenom, m.nom,
CAST( GROUP_CONCAT( DISTINCT d.depart ) AS char ) AS deps,
CAST( GROUP_CONCAT( DISTINCT x.depart ) AS char ) AS depx
FROM `0_rank` AS l
LEFT JOIN `0_right` AS r ON r.rank_id = l.id
LEFT JOIN `0_member` AS m ON r.user_id = m.id
LEFT JOIN `0_depart` AS d ON m.id = d.user_id
LEFT JOIN `0_depart` AS x ON x.user_id = $user_id
WHERE r.section = $section_id
AND l.rank = '$rank_name'
GROUP BY r.user_id ORDER BY prenom, nom
Now I want to get only those result, where all entries of deps are present in entries in depx.
In other term, every user is associated with some departs. $user_id is also an user is associated with some departs.
I want to get those users whose departs are common to the departs of $user_id.
Cheers.
Update
I'm not sure without being able to see the data but I believe this query will give you the results you want the fastest.
SELECT m.id, m.prenom, m.nom,
CAST( GROUP_CONCAT( DISTINCT d.depart ) AS char ) AS deps,
FROM `0_rank` AS l
LEFT JOIN `0_right` AS r ON r.rank_id = l.id and r.user_id = $user_id
LEFT JOIN `0_member` AS m ON r.user_id = m.id
LEFT JOIN `0_depart` AS d ON m.id = d.user_id
WHERE r.section = $section_id
AND l.rank = '$rank_name'
GROUP BY r.user_id ORDER BY prenom, nom
Let me know if this works.
Try this:
(By converting the functionality of the IN (SELECT...) to an inner join, you get exactly the same results but it might be the optimizer will make better choices.)
SELECT r.user_id AS ID, m.prenom, m.nom
FROM `0_rank` AS l
LEFT JOIN `0_right` AS r ON r.rank_id = l.id and r.section_id = 2
LEFT JOIN `0_user` AS m ON r.user_id = m.id
INNER JOIN `0_depart` AS x ON l.section_id = x.section_id and x.user_id = $user_id AND x.section_id = $section_id
WHERE l.rank = 'mod'
GROUP BY r.user_id
I also moved the constraints on 0_right to the join statement because I think that is clearer -- presumably this change won't matter to the optimizer.
I know nothing about your DB structure but your subselect looks like it can be replaced with a simple INNER JOIN against whatever table has the depart column. MySQL is well known for its poor subquery optimization.
Without knowing the structures or indexes, I would first add "STRAIGHT_JOIN" if the critical criteria is in-fact from the 0-rank table. Then, ensure 0_rank has an index on "rank". Next, ensure the 0_right has an index on rank_id at a minimum, but rank_id, section to take advantage of BOTH your criteria. Index on 0_member on id.
Additionally, do you mean left-join (ie: record only required in the 0_rank or 0_member) on the respective 0_right and 0_member tables instead of a normal join (where BOTH tables must match on their IDs).
Finally, ensure index on the depart table on user_id.
SELECT STRAIGHT_JOIN
r.user_id AS ID,
m.prenom,
m.nom
FROM
0_rank AS l
LEFT JOIN `0_right` AS r
ON l.id = r.rank_id
AND r.section = 2
LEFT JOIN `0_member` AS m
ON r.user_id = m.id
WHERE
l.rank = 'mod'
AND depart IN (SELECT depart
FROM 0_depart
WHERE user_id = 2
AND user_sec = 2)
GROUP BY
r.user_id
---- revised post from feedback.
From the parameters you are listing, you are always including the User ID... If so, I would completely restructure it to get whatever info is for that user. Each user should apparently can be associated to multiple departments and may or may NOT match the given rank / department / section you are looking for... I would START the query with the ONE USER because THAT will guarantee a single entry, THEN tune-down to the other elements...
select STRAIGHT_JOIN
u.id,
u.prenom,
u.nom,
u.other_stuffs,
rank.rank_name
from
0_user u
left join 0_right r
on u.id = r.user_id
AND r.section_id = $section_id
join 0_rank rank
on r.rank_id = rank.id
AND rank.rank_name = '$rank_name'
left join 0_dept dept
on u.id = dept.user_id
where
u.id = $user_id
Additionally, I have concern about your table relationships and don't see a legit join to the department table...
0_user
0_right by User_ID
0_rank by right.rank_id
0_dept has section which could join to rank or right, but nothing to user_id directly
Run explain on the query - it will help you find where the caveats are:
EXPLAIN SELECT r.user_id AS ID, m.prenom, m.nom
FROM 0_rank AS l
LEFT JOIN `0_right` AS r ON r.rank_id = l.id
LEFT JOIN `0_member` AS m ON r.user_id = m.id
WHERE r.section = 2
AND l.rank = 'mod' AND depart IN
(SELECT depart FROM 0_depart WHERE user_id = 2 AND user_sec = 2)
GROUP BY r.user_id\G