Selecting rows from 3 tables - mysql

I appreciate there are many tutorials and examples out there explaining JOIN concepts- however I am struggling to apply the examples to my specific scenario. I would appreciate some help, and if it isn't too much to ask- a break down of what is going on in the solution to achieve the desired results.
3 Tables: users, assessments, assessment_log.
users
+--------+------+------+-----------+----------+-----------+-------+--------+-----------+---------------+------+----------+
| UserID | User | Pass | FirstName | LastName | LastLogin | Email | Mobile | Kenitalla | AccountStatus | Role | Operator |
+--------+------+------+-----------+----------+-----------+-------+--------+-----------+---------------+------+----------+
assessments
+--------------+------+-----------+-----------+-----------+-----------+-----------+--------------+----------+---------+----------+
| AssessmentID | Name | Criteria1 | Criteria2 | Criteria3 | Criteria4 | Criteria5 | RoleRequired | Required | Renewal | Operator |
+--------------+------+-----------+-----------+-----------+-----------+-----------+--------------+----------+---------+----------+
assessment_logs
+-----------------+------+----------------+------------+--------+-----------+----------+----------+----------+----------+----------+----------+---------+---------------+---------+
| AssessmentLogID | Date | AssessmentName | AssessedBy | UserID | StaffName | Comments | Verdict1 | Verdict2 | Verdict3 | Verdict4 | Verdict5 | Verdict | RenewalPeriod | NextDue |
+-----------------+------+----------------+------------+--------+-----------+----------+----------+----------+----------+----------+----------+---------+---------------+---------+
When a user takes an assessment, an entry goes in to the assessment_log. Some assessments are required (true or false in Required column) where as some are optional. The RoleRequired column stipulates which user Role the assessment is required for.
I would like to generate a list of users and assessments they are yet to pass. The assessments must be required, and the required role must match the users role. An absense of an entry into the assessment_log with a "Pass" in the Verdict column indicates the assessment has not yet been passed.
In plain speak, I am looking for a query that will achieve the following result:
+------------------+-----------------+----------------+
| assessments.Name | users.FirstName | users.LastName |
+------------------+-----------------+----------------+
Where the assessment is required (Required equals true), the RequiredRole matches the users.Role column, and there is no entry in the assessments_log for the assessment where the Verdict column holds a "Pass" value.
Please let me know if further clarification is required.
Thanks in advance!

I'd suggest using a LEFT JOIN.
First, we generate a cross product of assessments and users, which contains for each user, all assessments that are required for their role.
Then, a LEFT JOIN checks if these assessments were passed.
SELECT ...
FROM
users u
JOIN assessments a ON (a.RoleRequired = u.Role)
LEFT JOIN assessment_logs al ON (
al.AssessmentID = a.AssessmentID
AND al.UserID = u.UserID
AND al.Verdict='Pass'
)
WHERE
a.required
AND al.UserID IS NULL

Your attribute names are inconsistent between tables and, although I don't see any duplicate names, it's probably only a matter of time before you do, so I suggest you fix this.
In the meantime, here's how to rename the columns on the fly in order to simplify the join syntax:
SELECT AssessmentName, UserLastName, UserFirstName
FROM ( SELECT FirstName AS UserFirstName, Lastname AS UserLastName, Role
FROM users ) u
NATURAL JOIN
{ SELECT Name AS AssessmentName, RoleRequired AS Role
FROM assessments
WHERE Required ) a
NATURAL JOIN
( SELECT AssessmentName
FROM assessment_logs
WHERE Verdict = 'Pass' ) l;

Related

Mysql select distinct user with each of their own preference

Let's say I have a user and preference table, as well as a bridge table user_preference between the two:
/* user table: */
+----------+--------------+
| Field | Type |
+----------+--------------+
| id | int |
| username | varchar(255) |
+----------+--------------+
/* preference table: */
+------------+--------------+
| Field | Type |
+------------+--------------+
| preference | varchar(255) |
+------------+--------------+
/* user_preference table: */
+-----------------+--------------+
| Field | Type |
+-----------------+--------------+
| user_id | int |
| preference_name | varchar(255) |
+-----------------+--------------+
For instance there are 3 preferences to choose from: "swimming", "watching TV", "cycling". And one user can have zero or all 3 of the preferences, which is reflected on the user_preference table.
Now I want to query 10 different users, and with all of them each of their own preferences included, either null or mutiple preferences, how to construct a select statement for that?
So far I have tried something like this:
SELECT u.*, p.preference_name
FROM user u
LEFT JOIN user_preference p ON p.user_id = u.id
LIMIT 10;
/* Result: */
id | username | preference_name
1 | user1 | swimming
1 | user1 | cycling
2 | user2 | null
3 | user3 | watching TV
... /* rest of the result */
As you can see the result will return a duplicate user1, and it won't be 10 distinct users. I'm aware of the distinct and group by keywords, it doesn't solve the problem, as it will only return a single preference for a user, while the user can have multiple preferences.
How to do that with one single select statement?
Try this.
SELECT u.*,
GROUP_CONCAT(DISTINCT p.preference_name) AS prefs
FROM user u
LEFT JOIN user_preference p ON p.user_id = u.id
GROUP BY u.id
LIMIT 10;
The GROUP_CONCAT() will make a comma-separated list of preferences for each user.
Pro tip. When tables get very large, altering ENUMs to add more values gets very time-consuming. Plus, it's usually unwise to design a database so it needs lots of ALTER TABLE statements as it grows. So, the approach you have outlined is the right way to go if you want your possible preferences to be open-ended.

Get user's primary email address mysql php best practices

I have a user table and a contact info table so that each user can have multiple phone numbers and multiple email addresses. Is there a standard way to set and retrieve their primary contact info?
"humans" table
ID | name
1 | John
2 | Joan
"human_contact" table
ID | contact_type | contact_info | user_id
1 | email | john#a.com | 1
2 | email | johnny#b.net | 1
"tickets" table
ID | human_id | event_date
1 | 1 | 2017-08-01
if John usually uses johnny#b.net, how do I mark that row as his favorite and retrieve that one for most queries?
SELECT *
FROM tickets
LEFT JOIN humans
ON tickets.human_id=humans.ID
LEFT JOIN human_contact
ON human_contact.human_id=humans.ID
WHERE tickets.ID='112'
You can use an extra coloumn named as fav which will have boolean values. This can help you in first finding whether he has any fav using a check where fav==true, then going on with that contact_info , or if the user doesnt have any favourites, then go with your usual query.
That is,
if( query( fav==1 for atleast 1 tuple )){
return tuple with fav==1;
else{
query ( usual query to get contact_info );
return query_result;
}
for more easier understanding.Hope this helps.

MySQL Query JOIN returning 1 null field

I have two simple tables:
user
+-------------------------------------------------+
| uid | firstname | lastname |
|-------------------------------------------------|
| 4000 | Zak | Me |
+-------------------------------------------------+
user_role
+----------------------------------------------+
| rid | uid | oid |
|----------------------------------------------|
| 5 | 4000 | 7000 |
+----------------------------------------------+
I am using this query
SELECT us.firstname, us.lastname, ur.oid
FROM user us
LEFT JOIN user_role ur
ON us.uid = ur.uid
WHERE us.firstname = 'Zak';
My result is
+-------------------------------------------------+
| firstname | lastname | oid |
|-------------------------------------------------|
| Zak | Me | (null) |
+-------------------------------------------------+
What am I missing? It has to be something simple!
UPDATE
Has to do something with the WHERE clause .. Because if left out, it returns all rows with oid included
Follow a process like this:
run this query
SELECT *
FROM user us
WHERE us.firstname = 'Zak';
If you get a result the Where clause is fine. So now you run:
SELECT *
FROM user us
LEFT JOIN user_role ur
ON us.uid = ur.uid
WHERE us.firstname = 'Zak';
If yuo get no records then there is something wrong with the join. Could be that they have some unprintable characters and 4000 <>4000 as a result. So let's check that.
select * FROM user us where us.uid = 4000
select * FROM user_role us where us.uid = 4000
If one of then does not return a result then there is a data problem where one of the fields contains unprintable characters.
If the select * works, then try the original query again only add a few other fields from the user_role table such as the uid. Then you can see if the join is working but the field is empty or if the join is wrong or possibly you are looking at the wrong field.
SELECT us.firstname, us.lastname, ur.oid, ur.uid
FROM user us
LEFT JOIN user_role ur
ON us.uid = ur.uid
WHERE us.firstname = 'Zak';
It is also possible the join fields are different datatypes and some type of implicit conversion is messing them up. In that case you probably want to explicitly convert or preferably design your table so that they both use the same data type as they appear to be in a PK/FK relationship.

MySQL - Duplicate rows by different column

I have a table with the following structure:
id | user_id | job | type
The scenario is as following:
"A person in a company is offering his skills, another person is looking for skills. They can fill in their offerings and searches. After filling this in, they need to see the matches between what they are looking for and others are offering."
So "job" is a string, f.e. "creating backup","repairing bike"
So "type" is a string, f.e. "searching", "offering"
Is it possible to get these matches with one query?
** edit **
id | user_id | job | type
---|---------|---------------|----------
1 | 1 | Create backup | searching
2 | 1 | Format osx | searching
3 | 2 | Create backup | offering
4 | 1 | Program PHP | offering
I want to do a query SELECT * FROM table WHERE user_id = 1 AND type = 'offering' ... with a result that provides me an array of all the other users that are offering this. So that the user has a page with all the results of people that are offering the job that he is searching.
This will get you all user offering skills which are searching. Try it:
SELECT o.* FROM Table1 s
INNER JOIN Table1 o
ON s.job = o.job
WHERE s.type = 'searching'
AND o.type = 'offering'
AND s.user_id = (The user who is searching)

How to write a proper If...Else Statement with JOIN in MySQL?

I'm quite a beginner in MySQL I just know the totally basic statements, however now I'ts time for me to get into some more difficult, but worth stuff.
I actually have 3 tables in MySQL, here is the representation:
users:
user_id | name | country
---------------------------
1 | Joseph | US
2 | Kennedy | US
3 | Dale | UK
admins:
admin_id | name | country
----------------------------
1 | David | UK
2 | Ryan | US
3 | Paul | UK
notes:
id | n_id | note | comment | country | type | manager
----------------------------------------------------------------
1 | 3 | This is the 1st note | First | US | admin | 2
2 | 2 | This is the 2nd note | Second | US | user | 1
3 | 2 | This is the 3rd note | Third | UK | user | 2
Now I would like to execute something like this SQL (I'm going to type not real commands here, because I'm not really familiar with all of the SQL expressions):
IF notes.type = admin
THEN
SELECT
notes.note,
notes.comment,
notes.country,
admins.name,
admins.country
FROM notes, admins
WHERE notes.n_id = admin.admin_id
ELSEIF notes.type = 'user'
SELECT
notes.note,
notes.comment,
notes.country,
users.name,
users.country
FROM notes, users
WHERE notes.n_id = users.user_id
I hope you understand what would I like to achieve here. I could do this easily with more SQL statements, but I would like to try some query which doesn't use that much resources.
Edit 1:
I would like to Get all of the Notes and get which usergroup has submitted it than apply the user's name to it. I mean, if the admin submitted the note, than SQL should choose the ID from the Admin table (as per the type value) but if a User submitted the note, it should get the name from the Users table.
The result should look something similar to this:
result:
------
id | note | comment | country | name
--------------------------------------------------------
1 | This is the 1st note | First | US | Paul
2 | This is the 2nd note | Second | US | Kennedy
3 | This is the 3rd note | Third | UK | Kennedy
Edit 2:
I have actually forgot to mention, that all of these should be listed to a manager. So a 'manager ID' should be added to the Notes and list all of the notes where the manager is for example: 2.
Here is a method that you can do in one query:
SELECT n.note, n.comment, n.country,
coalesce(a.name, u.name) as name, coalesce(a.country, u.country) as country
FROM notes n left join
admins a
on n.n_id = a.admin_id and n.type = 'admin' left join
users u
on n.n_id = u.user_id and n.type = 'user';
This uses left join to bring the records together from both tables. It then chooses the matching record for the select.
To select a particular manager, remove the semicolon and add:
where n.manager = 2;
If you expect admins and users in one result you have got several options. The simplest way is to make a union select like this:
SELECT
notes.note,
notes.comment,
notes.country,
admins.name,
admins.country
FROM
notes join admins on notes.n_id = admin.admin_id
WHERE
notes.manager = 2
UNION ALL
SELECT
notes.note,
notes.comment,
notes.country,
users.name,
users.country
FROM
notes join users on notes.n_id = users.user_id
WHERE
notes.manager = 2