Mysql select distinct user with each of their own preference - mysql

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.

Related

Get Field Based on FK

I didn't quite know how to word my issue, so I apologize for the odd title. I currently have two MySQL tables, business and users that look like the following respectively:
+-------------+----------+
| business_id | owner_id |
| 1 | 1 |
| 2 | 3 |
+-------------+----------+
+---------+-------------+----------------+
| user_id | business_id | email |
| 1 | 1 | a#domain.com |
| 2 | 1 | b#domain.com |
| 3 | 2 | c#domain.com |
+---------+-------------+----------------+
Right now, I need to get the email of the user where the business_id field in the user table matches that of the owner_id in the business table and I will always have the user_id (but it might not necessarily be the owner). To demonstrate what I mean, I can achieve what I want through this mess of a code:
SELECT
`email`
FROM
`user`
WHERE
`user_id` =(
SELECT
`owner_id`
FROM
`business`
WHERE
`business_id` =(
SELECT
`business_id`
FROM
`user`
WHERE
`user_id` = :user_id
)
)
So if I were to pass the a value of 1 or 2 for the user_id parameter, it would return a#domain.com and if I passed a value of 3 it would return c#domain.com.
I just feel as thought there is a better way!
Insead of subqueries you can use joins to achieve the same result by joining the user table twice with different aliases:
select u2.email
from user u1
inner join business b on u1.business_id=b.business_id
inner join user u2 on b.owner_id=u2.user_id
where u1.user_id=...
DEMO

Selecting rows from 3 tables

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;

Joining 3 MySQL Tables with data comparison

I have 3 MySQL tables currently in my database and I was trying to work out how to join them all together and compare data between them.
The queries form part of a "Friend or Foe" system I'm currently working on as a side project in my spare time, but I cannot for the life of me work it out. I've successfully managed a 2 table join but am getting nowhere with a 3 table join.
Anyway, here is the table layout.
Table 1
Unique ID | Username | Password | Activity
1 | SomeUser | password | Active
2 | NewUsers | password | InActive
3 | GuestUse | password | Active
Table 2
FileID | UploadedBy | Type | FileName | Description
1 | SomeUser | MP3 | Demo.mp3 | Bass Guitar Riff
2 | SomeUser | MP4 | Demo.mp4 | Some Youtube Video
Table 3
ListOwner | Friends | Foes
SomeUser | GuestUse | NULL
GuestUse | SomeUser | NULL
All I'm trying to achieve is that the "ListOwner" can view files uploaded by users who are in his/hers "friends" list, naturally there would be a page where you could view all files uploaded, but this is more designed towards seeing uploads of people you appreciate more.
In essence I am trying to get the query to read the; "Username" from Table1, "UploadeBy" from Table2 and Everything from Table3
Example:
GuestUse logs in, the query takes this information and compares his Username against Table 3 and then shows ONLY the uploads in Table2 from those who are in his friends list.
For structure the DB has the following setup.
Unique ID & Field ID are both INT(4)
Username is VARCHAR(42)
Password is VARCHAR(30)
Activity is VARCHAR(8)
ListOwner is VARCHAR(42) < Same as Username
Friends & Foes are TEXT
Any help you can give would be greatly appreciated.
Table 2 field "uploadedby" should be the numeric user id.
FileID | UploadedBy | Type | FileName | Description
1 | 1 | MP3 | Demo.mp3 | Bass Guitar Riff
2 | 1 | MP4 | Demo.mp4 | Some Youtube Video
You should create one table for "friend" relation and one for "foe" relation.
Table 3: friends
ListOwnerId | FriendId
1 | 2
1 | 3
2 | 1
Table 4: foes
ListOwnerId | FoeId
3 | 2
Then proceed with queries from inner to outer.
SELECT friendid FROM friends WHERE listowner=$loginid;
This will extract all friends for $loginid user.
If $someuseris is in $loginid friends list but $someuserid set $loginid as a foe, should $loginid see $someuserid files? Think of that. In the meanwhile, foes table is useless.
Now, select all files from friends:
SELECT * FROM files WHERE uploadedby IN (SELECT friendid FROM friends WHERE listowner=$loginid)
This query will give you a list of desired files.
This is done with a subquery, you can do it also with a join.
SELECT files.* FROM files JOIN friends ON files.uploadedby=friends.friendid WHERE friends.listowner=$loginid;
Give this a go:
select t1.Username,
t3.ListOwner,t3.Friends,t3.Foes,
t2.UploadedBy,t2.Type,t2.FileName,t2.Description
from Table1 t1
inner join Table3 t3 on t1.Username = t3.ListOwner
inner join Table2 t2 on t3.Friends = t2.UploadedBy
where Username = 'GuestUse';

User System - Multiple Roles in MySQL Database

So I am in the process of attempting to create a basic user system and within this system I want users to be able to have multiple roles.
Say for example I have the roles as follows: Administrator, Events Organiser, Donator
What is the best way to assign these multiple roles to a user and then check if they have any of these roles for showing certain permissions.
If it was only one role per person then it wouldn't be a problem as I'd just assign say Administrator = 10, Organiser = 5 and Donator = 1 and then do an if statement to check if the MySQL data is equal to any of those three numbers.
I can't imagine there is a way to add a MySQL Field and fill it with say "Administrator,Donator" and therefore that user would have both of those roles?
Is it just a case of I would need to create 3 separate fields and put a 0 or a 1 in those fields and check each one separately?
Use multiple tables and join them:
User
--------------
id name
1 test
Role
--------------
id name
1 Donator
2 Organizer
3 Administrator
User_Role
--------------
id user_id role_id
1 1 1
2 1 3
SELECT * FROM User u
LEFT JOIN User_Role ur ON u.id = ur.user_id
LEFT JOIN Role r ON ur.role_id = r.id
WHERE r.name = "Administrator";
The query is easier if you know you only have 3 roles and they are easy to remember.
SELECT * FROM User u LEFT JOIN User_Role ur ON u.id = ur.user_id WHERE ur.role_id = 3;
You will have a roles, users and users_roles tables:
The roles table will hold the various roles your users can have. In my example data I've declared Administrator and Donator roles.
roles
id unsigned int(P)
description varchar(15)
+----+---------------+
| id | description |
+----+---------------+
| 1 | Administrator |
| 2 | Donator |
| .. | ............. |
+----+---------------+
And of course you'll have to store information about your users.
users
id unsigned int(P)
username varchar(32)
password varbinary(255)
etc.
+----+----------+----------+-----+
| id | username | password | ... |
+----+----------+----------+-----+
| 1 | bob | ******** | ... |
| 2 | mary | ******** | ... |
| .. | ........ | ........ | ... |
+----+----------+----------+-----+
Finally you'll tie the two together in the users_roles table. In my example data you can see that bob is a Donator and mary is both an Administrator and a Donator. The user_id and role_id are both foreign keys to their respective tables and together they form the primary key for this table.
users_roles
user_id unsigned int(F user.id)\_(P)
role_id unsigned int(F role.id)/
+----+---------+---------+
| id | user_id | role_id |
+----+---------+---------+
| 1 | 1 | 2 |
| 2 | 2 | 1 |
| 3 | 2 | 2 |
| .. | ....... | ....... |
+----+---------+---------+
This way a user can have an unlimited number of roles.
If you don't want to create two new table for such small thing.
You can utilize MySQL built in SET data type or you can store comma separated role in varchar and do query operation using FIND_IN_SET()
Eg.
SELECT * FROM user WHERE FIND_IN_SET('admin', role)>0;
+----+----------+----------+----------------+
| id | username | password | role (varchar) |
+----+----------+----------+-----------------+
| 1 | bob | ******** | admin |
| 2 | mary | ******** | admin,role1 |
| .. | ........ | ........ | role1,role2 |
+----+----------+----------+-----------------+

INNER or LEFT Joining Multiple Table Records Into A Single Row

Phone Table
+----------------+-------------+
| Field | Type |
+----------------+-------------+
| f_id | int(15) |
| f_client_id | int(11) |
| f_phone_type | varchar(50) |
| f_phone_number | varchar(13) |
+----------------+-------------+
Clients Table
+-----------------------------+--------------+------+-----+
| Field | Type | Null | Key |
+-----------------------------+--------------+------+-----+
| f_id | int(15) | NO | PRI |
| f_first_name | varchar(13) | YES | MUL |
| f_mi | char(1) | YES | |
| f_last_name | varchar(20) | NO | MUL |
+-----------------------------+--------------+------+-----+
Assumptions:
Each record in 'Phone Table' belongs to one record in 'Clients Table'.
Each record in 'Clients Table' can have 0 or more records in 'Phone Table'.
Simple Translation: A client can have 0 or more phone numbers
With a standard LEFT or INNER join, I get something like this:
+------------+------------+--------------+
| name | Phone Type | Phone Number |
+------------+------------+--------------+
| John Smith | Home | 712-555-6987 |
| John Smith | Work | 712-555-1236 |
+------------+------------+--------------+
I need a query that will give me the work and home numbers that belong to a given client:
+------------+----------------+--------------+
| Name | Work Number | Home Number |
+------------+----------------+--------------+
| John Smith | 712-555-1236 | 712-555-6987 |
+------------+----------------+--------------+
Is it possible to do a LEFT or INNER join and then merge those results into a single row? I've seen similiar questions on this, but the examples given were much more complex than what I'm after:
Similiar Questions
MySQL Need help constructing query: join multiple tables into single row
SQL left join with multiple rows into one row
Thanks
Though you can join several numbers (in any) into a single field:
SELECT
CONCAT(f_first_name, ' ', f_last_name) as Client_Name,
GROUP_CONCAT(IF(phone_type='work',f_phone_number, NULL)) as Work_Numbers,
GROUP_CONCAT(IF(phone_type='home',f_phone_number, NULL)) as Home_Numbers
FROM clients
JOIN phone
USING (f_id)
WHERE phone_type IN ('home', 'work')
GROUP BY f_id;
Are there limits on how many Work or Home numbers a particular Client record can have? If it can be many, then no, there is no way to make a single row. If there can be at most 1 of each, then you can just join on the phone numbers table twice.
SELECT CONCAT(c.f_first_name, ' ', c.f_last_name) as Client_Name,
wp.f_phone_number as Work_Number,
hp.f_phone_number as Home_Number
FROM clients c
LEFT OUTER JOIN phone hp
ON hp.f_client_id = c.f_id
AND
hp.phone_type = 'home'
LEFT OUTER JOIN phone wp
ON wp.f_client_id = c.f_id
AND
wp.phone_type = 'work'
With LEFT OUTER JOINs you will still get rows for clients with missing numbers. If you don't want to see those, change to INNER JOINs.
Edit: As Nick kindly reminds me, this will return multiple rows for clients with multiple phone numbers. Once you have the data you need, you're then faced with presentation issue. You can handle that in the application layer, or make a sacrifice to the SQL gods and look into MySQL's GROUP_CONCAT() function.