I'm trying to come up with the most simple / userful / efficient method to group 3 users together using mysql.
To set the stage:
X number of users in a list (all with int account_id's)
mini groupings need to be created on a per user basis (user 1 wants to group with 220 for instance).
Max 3 people per grouping (user 1 + user 220 + user 9123 = group full)
Need to easily find if a user is in a group or not without looking in a bunch of columns
I'm stumped about how best to create a schema for this (so I can easily query my table to see if user is in a group, or if they can be added, or check for group space availability).
Anyone have any idea? My initial thought is schema like this (but it really seems too rigid):
Schema
GROUP_ID USER1 USER2 USER3 LASTUPDATE
1 1 220 null 5/25/2011 20:00:00
2 300 2 4 5/25/2011 20:00:00
How would you do it to make something this simple very flexible and efficient. I really feel stupid for asking.
Personally I would approach this by using 3 tables.
Users Table
user_id user_name ..... last_update
Groups Table
group_id group_name ......
Users to groups Table
user_to_group_id user_id group_id
This forms a many to many relationship by linking through the "Users to groups" table, obviously you can have more that 3 users linked to one group so you will have to make your PHP logic check for this when adding a new user to a group.
You can simply use SQL joins to retrieve all the data required and filter the results in your PHP code.
I hope this helps
Kind regards
Garry
Related
I have a one-to-many relationship of rooms and their occupants:
Room | User
1 | 1
1 | 2
1 | 4
2 | 1
2 | 2
2 | 3
2 | 5
3 | 1
3 | 3
Given a list of users, e.g. 1, 3, what is the most efficient way to determining which room is completely/perfectly filled by them? So in this case, it should return room 3 because, although they are both in room 2, room 2 has other occupants as well, which is not a "perfect" fit.
I can think of several solutions to this, but am not sure about the efficiency. For example, I can do a group concatenate on the user (ordered ascending) grouping by room, which will give me comma separated strings such as "1,2,4", "1,2,3,5" and "1,3". I can then order my input list ascending and look for a perfect match to "1,3".
Or I can do a count of the total number of users in a room AND containing both users 1 and 3. I will then select the room which has the count of users equal to two.
Note I want to most efficient way, or at least a way that scales up to millions of users and rooms. Each room will have around 25 users. Another thing I want to consider is how to pass this list to the database. Should I construct a query by concatenating AND userid = 1 AND userid = 3 AND userid = 5 and so on? Or is there a way to pass the values as an array into a stored procedure?
Any help would be appreciated.
For example, I can do a group concatenate on the user (ordered ascending) grouping by room, which will give me comma separated strings such as "1,2,4", "1,2,3,5" and "1,3". I can then order my input list ascending and look for a perfect match to "1,3".
First, a word of advice, to improve your level of function as a developer. Stop thinking of the data, and of the solution, in terms of CSVs. It limits you to thinking in spreadsheet terms, and prevents you from thinking in Relational Data terms. You do not need to construct strings, and then match strings, when the data is in the database, you can match it there.
Solution
Now then, in Relational data terms, what exactly do you want ? You want the rooms where the count of users that match your argument user list is highest. Is that correct ? If so, the code is simple.
You haven't given the tables. I will assume room, user, room_user, with deadly ids on the first two, and a composite key on the third. I can give you the SQL solution, you will have to work out how to do it in the non-SQL.
Another thing I want to consider is how to pass this list to the database. Should I construct a query by concatenating AND userid = 1 AND userid = 3 AND userid = 5 and so on? Or is there a way to pass the values as an array into a stored procedure?
To pass the list to the stored proc, because it needs a single calling parm, the length of which is variable, you have to create a CSV list of users. Let's call that parm #user_list. (Note, that is not contemplating the data, that is passing a list to a proc in a single parm, because you can't pass an unknown number of identified users to a proc otherwise.)
Since you constructed the #user_list on the client, you may as well compute #user_count (the number of members in the list) while you are at it, on the client, and pass that to the proc.
Something like:
CREATE PROC room_user_match_sp (
#user_list CHAR(255),
#user_count INT
...
)
AS
-- validate parms, etc
...
SELECT room_id,
match_count,
match_count / #user_count * 100 AS match_pct
FROM (
SELECT room_id,
COUNT(user_id) AS match_count -- no of users matched
FROM room_user
WHERE user_id IN ( #user_list )
GROUP BY room_id -- get one row per room
) AS match_room -- has any matched users
WHERE match_count = MAX( match_count ) -- remove this while testing
It is not clear, if you want full matches only. In that case, use:
WHERE match_count = #user_count
Expectation
You have asked for a proc-based solution, so I have given that. Yes, it is the fastest. But keep in mind that for this kind of requirement and solution, you could construct the SQL string on the client, and execute it on the "server" in the usual manner, without using a proc. The proc is faster here only because the code is compiled and that step is removed, as opposed to that step being performed every time the client calls the "server" with the SQL string.
The point I am making here is, with the data in a reasonably Relational form, you can obtain the result you are seeking using a single SELECT statement, you don't have to mess around with work tables or temp tables or intermediate steps, which requires a proc. Here, the proc is not required, you are implementing a proc for performance reasons.
I make this point because it is clear from your question that your expectation of the solution is "gee, I can't get the result directly, I have work with the data first, I am ready and willing to do that". Such intermediate work steps are required only when the data is not Relational.
Maybe not the most efficient SQL, but something like:
SELECT x.room_id,
SUM(x.occupants) AS occupants,
SUM(x.selectees) AS selectees,
SUM(x.selectees) / SUM(x.occupants) as percentage
FROM ( SELECT room_id,
COUNT(user_id) AS occupants,
NULL AS selectees
FROM Rooms
GROUP BY room_id
UNION
SELECT room_id,
NULL AS occupants,
COUNT(user_id) AS selectees
FROM Rooms
WHERE user_id IN (1,3)
GROUP BY room_id
) x
GROUP BY x.room_id
ORDER BY percentage DESC
will give you a list of rooms ordered by the "best fit" percentage
ie. it works out a percentage of fulfilment based on the number of people in the room, and the number of people from your set who are in the room
Maybe I am not putting the search in correctly in Google or SOF.com, but perhaps someone is willing to assist regardless. I know this is not the best method, but I am learning and I am hoping someone can assist in this way. I have a forum system in PHP/MySQL. The forum is set up to have an ID column.
I now also have 2 tables. One is a group table and the other is a forum permission table. The group table has an ID column and a user_id column, both int. The Forum Permission table has an id, forum_id and group_id. Basically I am trying find if the user belongs to a group(Group Table) and if that group is allowed to be in that forum(Forum Permission Table). The problem is that the user can belong to multiple groups and each forum can have multiple groups assigned to it.
Is there a query which can basically just search the three tables and distinguish if the user has access to the forum? If it were just one group to one forum, or one group per user, I can figure that out, but in this case I am stuck.
Any help? Example of Table structure below:
This is the Forum Table
Forum_ID || Forum_Name
1 ||| General tal
k
For Group Table:
Group_id ||| Group_name ||| User_ID
1 ||| Administrators ||| 2
For the Forum permission table(Forum_ID corresponds to Forum_ID in the Forums Table and Group_To_Allow corresponds to Group_ID on the groups table):
ID ||| Forum_ID ||| Group_To_Allow
1 ||| 1 ||| 1
Best I can do with the formatting.
EDIT:
OK, I figured it out with the InnerJoin. So using the table structure above:
SELECT *
FROM forum_perm
INNER JOIN membergroups
ON forum_perm.forum_groupallow=membergroups.group_id
WHERE forum_perm.forum_id = 1 AND membergroups.user_id = 6
LIMIT 0,1
Forum_perm.forum_id = 1 is the id number of the forum to check for the group. Membergroups.user_id = 6 is the users id number that belongs to the that group. The limit 0,1 will limit the result to just return one result instead of looping over and over. This is good because in PHP if MySQL loops through all the results and finds one group that does not match, it will still return false. Hope this helps anyone who is trying to achieve something similar.
You could make a JOIN statement and only select the highest value with MAX() from the certain row you want. I don't know your exact table structure or how you're doing that with the forum permissions. If you have only one column in your user table (I guess that's what you have, since that's usually the case), you maybe can use it in a IN statement (WHERE field IN(...). Maybe it works to just say mysql, hey take the value of this field (if the value of this field is commata-separated). I've never done it so I don't know it works.
Maybe you should make us some screenshots from your tables in phpmyadmin, so we can give you more accurate and specific informations.
edit: It does work with just throwing the name of the column.
The question is not new in any way but it has a small twist to it.
My webpage is a membership page where users places bets. My idea is to create a new table for the users(with a naming convention like TABLE userBet+$userid) bets. User login information is already handled, my goal is now to save the bets of the user to a new table. A table which is created when users register. This will hopefully make score counting easier. Am I right or wrong? Could this be done in a better way? (Everything is done in PHP MySQL)
User registers -> Table for bets get created
"CREATE Table $userID ,id_bet, games, result, points"
And then matching this table against the correct result?
So again my questions: Is this a good way to do it? Is creating a table with the userID a smart thing to do?
EDIT
The bets is always 40 matches, which makes the tables Huge with columns and rows.
Should I make 40 Tables, one for each games instead? and put all users in there?
Am I right or wrong?
You are wrong. Dynamically altering your database schema will only make it harder to work with. There's no advantage you gain from doing so. You can do the same things by storing all bets within the same table, adding a column userid.
Posting as an answer due to author's request : )
Suggested database schema:
table matches:
id | name |
---------------
1 | A vs B |
table user_bets
id | user_id | match_id | points | result |
-------------------------------------------
1 | X | 1 | Y | Z |
Where match_id is related on matches.id
user_id = user.id
user_bets is only one table, containing all the info. No need of separate tables, as it was clear from the comments it's considered bad practice to alter the db schema via user input.
Apologies if this is really stupid but I don't have any experience in php and mysql to know how things should be done. I have a customer table in a mysql db and a group table:
customers - ID name email phone group
groups - ID name description
So I need to assign groups to customers if necessary, this can be more than one group to each customer. So e.g. customer 1 is in group 4,5,6
What way should I assign groups in the group column of the customer table. Should I just add the group ID's separated by commas, then just use explode when I need to get the individual ID's out?
Maybe this isn't the right approach at all, could someone enlighten me please. I would appreciate knowing the right way to do this, thanks.
Do not store multiple IDs in one column. This is a denormalization that will make it much harder to query and change your data, as well as hurting performance.
Instead, create a separate CustomerGroup table (with CustomerID and GroupID columns), and have one row per Customer/Group relationship.
Here is an example of tables to show how you should implement this :
Table 1 CONSUMERS:
id name email
1 john john#something.com
2 ray ray#something.com
Table 2 GROUPS :
id group_name description
1 music good music group
2 programming programmers
Table 3 CONSUMERS_GROUPS
consumer_id group_id
1 1
1 2
2 1
Now the table 3 is listing consumers ids which belong to which group id.
This type of relationship is called one to many relation where, one consumer can have many groups. Reverse might also be true where one group can have many consumers. In that case relationship is called many to many
Should I just add the group ID's separated by commas, then just use explode when I need to get the individual ID's out?
No! If you do that then you won't quickly be able to (for example) query for which users there are in a specific group.
Instead use a join table with two columns, each of which has a foriegn key constraint to the corresponding table.
group_id customer_id
4 1
5 1
6 1
I've got an small issue, I'm trying to make my own forum, but I am stuck at something.
I have 3 tables, 1 users with a user_level (Authentication level).
forum_section which contains all the sections with a user_level again.
But how can I link forum_section.user_level to forum_topics.user_level.
So if I define: forum_section.user_level = 4 for forum_section.section_id = 1.
For example:
Someone wants to visit the forum_section id 1 then they must have a auth level of 4.
And when they go to the topic it then again checks if the user level is 4, but I do not want to manual set the topic level, topic level must always be the same as the section level.
I've googled for this, but I can't really find a good manual for it. I guess it has something to do with: "foreign keys"?
Okay, let me explain a bit more just to be sure.
I have 3 tables
Table User
username = Wesley
userlevel < for example 4(admin).
Table section
section_id = 3
section_name = News
section_level = 3 <require level to view.
table topics
topic_id = 123
topic_name = I like candy
topic_level = Needs to be the same as section_level so if I change section_level it automatically changes this too.
You should have to manage your database by using master and transaction tables.
Your table format should look like this:
table name: user
user_id user_name
1 abc
2 xyz
3 pqr
4 new
table2 name : forum_section
forum_id user_id forum_name
1 4 abc
2 4 jkl
3 2 cbd
4 3 lmn
Now, you can JOIN these two tables and make a query as you want, like this:
SELECT forum_id FROM forum_section as fs,user as u WHERE fs.user_id = u.user_id AND fs.user_id = 4
This may help you. Please write if you need more help!
You need to either use a SQL JOIN or a NESTED SUB QUERY
The simplest implementation would be to initially establish the users access level, then filter subsequent queries based on this- so in your PHP, pass the access level to subsequent requests for content- if the query returns anything, the user can view the content, if not- they can be redirected.