I have a mySQL table entitled users. It has a UID, rating, username, password, etcetera.
My goal is to make a system (tribes) similar to Facebook's friends list. Each user will be able to view the profile of users and add them to their tribe. Each user is the chief of only one tribe, but can be the villager of as many tribes as he/she wants to be. The rating system would take into account all of the tribe's members ratings.
After doing some research on relational database tables and grouping, I am not clear about how I should go about setting up the tables, or the PHP code that would go along with that.
If someone can get me pointed in the right direction, it'd be much appreciated!
EDIT: One of the big problems I foresee is accepting to be in a tribe. I'm not sure how you account for this.
similar to Facebook's friends list. Each user will be able to view the profile of users and add them to their tribe. Each user is the chief of only one tribe
Okay, so you have a User table, and also will need a Tribe table.
Then the relations you're describing are a chief-of, which is one-to-one (one user can be chief of one tribe; one tribe has only one chief), therefore you can either store this within User (chief_of: Tribe) or within Tribe (chief: User).
CREATE TABLE User ...
chief_of integer
Here, chief_of might be a foreign key so that if you delete a tribe, the relevant tuple will have its chief_of set to NULL (a user can't be chief of a no longer existing tribe).
The membership is a bit more complicated because one user can belong to several tribes, and a tribe will have more than one member.
This is a many-to-many relationship and is usually done with a table holding key pairs:
CREATE TABLE member_of (
user_id integer,
tribe_id integer
);
Both fields are natural candidates for foreign keys. Here you can find a similar implementation using Authors and Books.
To indicate that Bob is a member of the Clan of the Cave Bear, you retrieve the ids of Bob and Bears, and insert a tuple in member_of.
To retrieve all members of the clan, you can use a JOIN:
SELECT Users.* FROM Users
JOIN member_of ON (Users.user_id = member_of.user_id)
WHERE member_of.tribe_id =
(SELECT tribe_id FROM Tribes WHERE tribe_name = 'Clan of the Cave Bear');
I think that a shorter version of that ON in MySQL is USING(user_id) (meaning that both tables have an identical column identically named), but in my opinion the ON is clearer.
You can also retrieve a virtual "is_chief" column:
SELECT Users.*, chief_of = tribe_id AS is_chief FROM Users
JOIN member_of ON (Users.user_id = member_of.user_id)
WHERE member_of.tribe_id =
(SELECT tribe_id FROM Tribes WHERE tribe_name = 'Clan of the Cave Bear');
The one user whose chief_of attribute is equal to the tribe id will have is_chief set to TRUE, which is equal to 1, so
SELECT Users.*, chief_of = tribe_id AS is_chief FROM Users
JOIN member_of ON (Users.user_id = member_of.user_id)
WHERE member_of.tribe_id =
(SELECT tribe_id FROM Tribes WHERE tribe_name = 'Clan of the Cave Bear')
ORDER BY (chief_of = tribe_id) DESC, user_name;
will retrieve the users in alphabetical order, except the chief, who, if present, will come first.
As for the acceptance into the tribe, this identifies three states: a user is not in a tribe, a user is in the tribe, a user asked to be in a tribe. The first two are actually two faces of the same attribute member_of. So naturally we might create a new attribute and call it wants_in. It would map to a table identical to member_of.
A chief could retrieve all tuples of wants_in whose tribe_id is equal to his own chief_of (so if it's NULL, meaning he's not a chief, this will automatically return nothing). Then he might see this as a table of checkboxes with user names. When he approves the join, for each approval you delete the tuple from wants_in and put it into member_of.
Or you might decide that "membership" is a state in itself, so that you have a more complex join table
user_id,
tribe_id,
status
where status could be, say,
nothing (there's no (U, T, ?) tuple): user U and tribe T are unknown to each other
100: user U is full member of tribe T
-1 : tribe T has decided that U is not a member and cannot even ask to be.
0: user U wants to be member of T
1-99: user U is a probationary (apprentice) member.
It sounds like you'll need two classes: Villager and Tribe.
Then, maybe a database field called chief_of or something that is null if the person is not a chief, and contains the name of their tribe that they are the chief.
In the classes you can have a getChiefOf() method that can be tested to see if the user is a chief or not.
Alternatively, you could have a table of chiefs, indexed by UID, with the same column that says which tribe they're chief of. A little less efficient but a better structure. A drawback that jumps to mind is if the UID for some reason is changed, two tables would have to be updated, so maybe the first one's better.
Related
I am currently making an android app which uses a feed to display statuses made by users. I have three tables within the same database, each has username either as a primary or unique key column, but each table has different information relating to that user.
For instance, the first table ===>> tbl_users:
username
fname (first name)
mname (middle name)
lname (last name)
etc. (the list is long)
The second table ===>> tbl_userprofilepictures:
profilepictureID
username
profilepicturepath
The third table ===>> tbl_user_feed:
postID (the status' unique ID)
username
status
imagepostpath (the path to the image uploaded with the status)
timestamp
I want to be able to search for the username across all three tables and display the relevant information relating to them on their post. For example I will need their name and surname for tbl_users and I will need their profilepicturepath for tbl_userprofilepictures as well as their status, imagepostpath and timestamp from tbl_user_feed.
Would I need to do this in a seperate PHP file or in the app itself? PS I'm fairly noob at PHP so please feel free to help a bro out.
May the force be with you.
You can use JOIN.
What is JOIN ?
An SQL JOIN clause is used to combine rows from two or more tables, based on a common field between them.
Source : w3schools.com Joins
Here is the sample I made base on your tables given. For these one our common field is username.
SELECT CONCAT('a.fname', 'a.lname'), b.profilepictureID, c.status, c.imagepostpath, c.timestamp
FROM
tbl_users as a
LEFT JOIN tbl_userprofilepictures as b ON b.username = a.username
LEFT JOIN tbl_user_feed as c ON c.username = a.username
Using Alias (table_name as custom_name) is a good practice in joining the tables
I've currently got two tables:
mems (members):
id,
name,
email,
password,
salt,
achievements_id
achievements:
id,
achievement,
description,
points
I am able to correctly display the data for a user with:
"SELECT * FROM achievements WHERE id IN (SELECT achievements_id FROM mems WHERE name = '$name')";
My question is, how do I add the achievement ID to the user so each user has their own achievement records? Currently if I use an update it just wipes over the old achievement so it only ever displays 1 record.
Say I have 2 achievements and 2 users
User 1 achieves achievement 1, it's viewable and they have achievements_id set to 1.
User 2 achieves achievement 1, it's viewable and they have achievements_id set to 1.
User 1 then achieves achievement 2, it's viewable but now they have achievements_id set to 2.
I have no clue how to do this. I know what I want to do, but no clue how to design the database to have each user having their own records of achievements.
I originally did it where achievements table had a member_id and I'd concat the other users ID into their, dodgy but it semi-worked.
Any help? Sorry if I am making no sense.
It sounds like what you need is to model a many-to-many relationship (many users can share the same achievement - ie. be linked to the same entitiy in the achievements table; while a single user can have multiple achievements). This is usually done using an extra table. Let's call it: Members_Achievements_Map.
CREATE TABLE Members_Achievements_Map (
MemberID,
AchievementID
)
This table would link entities from the members table to entities in the achievements table.
The way I would go about this would be to create another table to hold the users achievements.
So basically another table that looks like such:
UserAchievements:
userID, achievementID
That way you can join the tables like:
SELECT * FROM achievements a
INNER JOIN userAchievements uA ON uA.achievementID = a.id
INNER JOIN users u ON uA.userID = u.id
That will give you all the users for all the different achievements.
Hope that helps!
From what I've been reading online, I understood that it's better to split the data into more tables, if possible because of the access times.
Right now I have a table in which I am storing usernames, passwords and join date
This is how my table looks:
'user'
'user_id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
user_username VARCHAR(80) NOT NULL,
user_password VARCHAR(255) NOT NULL,
user_join_date INTEGER UNSIGNED NOT NULL,
PRIMARY KEY (user_id) ');
I am trying to create a new table called profiles in which I want to store first name, last name, email, age and gender. What I think is that I need a one-to-one relationship, so I should be using the user_id but I'm not really sure how to implement it.
Should I create another table called profiles with
profiles
profiles_id
first_name
last_name
email
age
gender
and another one which should be the relationship ? e.g.
user_profiles
----------
user_id
profiles_id
Is this right? How would the SQL look for the user_profiles?
Thanks
Don't split the tables. Just add the new columns to your existing user table. You might find later on that splitting tables is a good idea based on actual queries and usage patterns but until you have that kind of data, keep things simple.
If you must create a profile table, don't create a user_profiles table. That would allow an m-to-n relationship which is probably not what you want. A simple user_id column in profiles is better. In fact, it could be both a foreign key and the primary key to make sure that each user row only have one and only one profile row (although by splitting the tables you might still have a user with no profile).
Usually, you create an association table, like user_profiles you have described when one user could have more than one profile, and/or one profile could belong to one or more user.
As you have said, here you have a one-to-one relationship between user and profile. So, you can simply add a user_id column to your profile table, and define it as a foreign key to user table.
Then, a simple JOIN will allow you to query both tables at the same time:
SELECT u.*, p.*
FROM user u
JOIN profile p ON u.user_id = p.user_id
Add a new field in the User table, ProfileId, and set it as Foreign Key (FK). Each time you create a User, you have to assign to it a profile (which will be the ProfileId PK from profile table).
If you want to see also the profile information of a user, you have to do a join
Select username, first_name,second_name
From user u, profile p
Where u.profileId = p.profileId
this
user_profiles
----------
user_id
profiles_id
is used in a many-to-many relationship. By example, you want to assign to an admin some privileges, but those privileges can be also assigned to more admins. Then, you have to create a 3rd table to solve this problem. Here is an example, but you don't need to do this.
You could add a user_id field to your profiles table and JOIN the tables on user_id.
SELECT user.user_username, ..., profiles.first_name, ...
FROM user
INNER JOIN profiles
ON user.user_id = profiles.user_id
This should fetch data combining information from those rows where the JOIN condition is met (i.e. user.user_id = profiles.user_id).
It is true that having more than one tables is a good idea. I am not sure what you mean about access time, but there are other advantages.
- Your users database containing passwords etc is "sacred", you never change its structure and you limit the rights to it (read, write) to the strict minimum.
- You can then have several "satelites" tables such as profiles, private messages, etc which are more flexible, less sensitive and which you can change all the time.
About your question per se, there is no need for a separate table with the relationships. In fact is a very bad idea which will complicate your queries and doesn't have any advantage. Instead, in your profiles database you will have one column that refers back to the user id.
users
--------
id
user_name
email
password
users_profiles
---------
id
user_id
favourite_animal
Table user
user_id |user_username |user_password |user_join_date |profile_id
Table profile
profile_id |first name |last name |email |age |gender
When selecting a user by user id:
SELECT u.*, p.* FROM user AS u INNER JOIN `profile` AS p ON u.profile_id = p.profile_id WHERE u.user_id = 1
But a user should only one gender, one age, one name and surname. Maybe e-mail adresses might be many. I suggest you there is no need to join tables which have a 1-to-1 relation. Instead merge those tables.
For the site I'm working on, I want a user to have the ability to checkmark multiple boxes that represent things he/she might be interested, similar to StumbleUpon. A user would check 'web development' and 'web design' then click 'Submit', which would then store his preferences in a database.
Later, if somebody created a project that was tagged with one of the preferences he selected, that user would get an update. So if I made a new project that said "Building a Website" and checked the category "web development", all users who had "web development" selected on their personal profiles would get some kind of message or email alerting them to the newly created topic.
What is the best way to implement this in MySQL format? I looked at some pages on managing hierarchical data (there will be generalized categories like "Computers" or "Music" and an admin will be able to add/delete/edit categories), but none of the methods seemed to be what I needed - at least, not in the way of thinking I'm stuck in. Perhaps there's an easier answer out there that I've been overlooking?
Create a table containing the various interests. Say
Interests :- id, interest
Then a table which stores all the interests selected by a user as
UserInterests :- user_id, interest_id
And a project interest relation as
ProjectInterest :- project_id, interest_id
Now when a new project is added you can run a query similar to the following onw to get the users that the project is of interest
SELECT DISTINCT user_id
FROM UserInterests ui, ProjectInterests pi
WHERE ui.interest_id = pi.interest_id AND pi.project_id = <new project id>
Or, using the explicit join syntax:
SELECT DISTINCT user_id
FROM UserInterests ui
INNER JOIN ProjectInterests pi ON ui.interest_id = pi.interest_id
WHERE pi.project_id = <new project id>
What you are asking - I think - is how to implement a many-to-many relationship.
The problem is that you can't give a user a list of interests without locking yourself into an exact number of interests they can select.
The solution is a Junction Table. On one hand, you have your list of users, and on the other hand, you have your list of interests. The junction table is a third table that lists the relationship between these two groups.
CREATE TABLE `user_interest` (
userid UNSIGNED INT REFERENCES `user` (userid),
interestid UNSIGNED INT REFERENCES `interests` (interestid),
PRIMARY KEY (interestid, userid)
)
Now you have a list of UNIQUE combinations of users and interests. Let's say you have a list of news articles, each with a single topic ("interestid") assigned to it. Now you can do something like,
SELECT * FROM `article` WHERE `article`.`interestid` IN (
SELECT `interestid` FROM `user_interest` WHERE `userid` = X
)
Which will retrieve the list of articles related to user X's selected interests. First, you get the list of topics that were related to your specified user, then you get the list of articles with matching topics.
I'm working on an upgrade for an existing database that was designed without any of the code to implement the design being considered. Now I've hit a brick wall in terms of implementing the database design in code. I'm certain whether its a problem with the design of the database or if I'm simply not seeing the correct solution on how to what needs to be done.
The basic logic stipulates the following:
Users access the online trainings by way of Seats. Users can have multiple Seats.
Seats are purchased by companies and have a many-to-many relationship with Products.
A Product has a many-to-many relationship with Modules.
A Module has a many-to-many relationship with Lessons.
Lessons are the end users access for their training.
To muddy the waters, for one reason or another some Users have multiple Seats that contain the same Products.
Certification takes place on a per Product basis, not on a per Seat basis.
Users have a many-to-many relationship with lessons that stores their current status or score for the lesson.
Users certify for a Product when they complete all of the Lessons in all of the Modules for the Product.
It is also significant to know when all Lessons for a particular Module are completed by a User.
Some Seats will be for ReCertification meaning that Users that previously certified for a Product can sign up and take a recertification exam.
Due to Rule 11, Users can and will have multiple Certification records.
Edit: When a User completes a Lesson (scores better than 80%) then the User has (according to the current business logic) completed the Lesson for all Products and all Seats that contain the Lesson.
The trouble that I keep running into with the current design and the business logic as I've more or less described it is that I can't find a way to effectively tie whether a user has certified for a particular product and seat vs when they have not. I keep hitting snags trying to establish which Products under which Seats have been certified for the User and which haven't. Part of the problem is because if they are currently registered for multiple of the same Product under different Seats, then I have to count the Product only once.
Below is a copy of the portion of the schema that's involved. Any suggestions on how to improve the design or draw the association in code would be appreciated. In case it matters, this site is built on the LAMPP stack.
You can view the relevant portion of the database schema here: http://lpsoftware.com/problem_db_structure.png
What you're looking for is relational division
Not implemented directly in SQL, but it can be done. Search google for other examples.
After a quick look at the schema I think one of the things you can do is create a 'to_be_certified' table. Populate it with user_id, product_id and seat_id when a product is assigned to a seat (when product_seat_rtab is populated).
On adding a record to the certification_rtab table, delete the corresponding record in the 'to_be_certified' table. This will give you an easy access to all the products which are certified for a users and the ones that are not.
To get rid of duplicate product_ids, you can group by product_id.
You need to make changes to the lessonstatus_rtab table:
CREATE TABLE lessonstatus_rtab (
user_id INT NOT NULL,
seat_id INT NOT NULL,
lesson_id INT NOT NULL REFERENCES lesson_rtab,
accessdate TIMESTAMP,
score NUMERIC(5,2) NOT NULL DEFAULT 0,
PRIMARY KEY (user_id, seat_id, lesson_id),
FOREIGN KEY (user_id, seat_id) REFERENCES user_seat_rtab (user_id, seat_id)
);
Then you can query for each product that a user has a seat for, is he certified? This presumes that the number of lessons he has scored, say, 50% or higher is the same as the number of lessons in all modules for the product.
SELECT p.name, us.user_id, us.seat_id, COUNT(l.id) = COUNT(lu.lesson_id) AS is_certified
FROM user_seat_rtab AS us
JOIN seat_rtab AS s ON (s.id = us.seat_id)
JOIN product_seat_rtab AS ps ON (ps.seat_id = s.id)
JOIN product_rtab AS p ON (p.id = ps.product_id)
JOIN product_module_rtab AS pm ON (pm.product_id = p.id)
JOIN module_rtab AS m ON (m.id = pm.module_id)
JOIN module_lesson_rtab AS ml ON (ml.module_id = m.id)
JOIN lesson_rtab AS l ON (l.id = ml.lesson_id)
LEFT OUTER JOIN lessonstatus_rtab AS lu
ON (lu.lesson_id = l.id AND lu.user_id = us.user_id
AND lu.seat_id = us.seat_id AND lu.score > 0.50)
GROUP BY p.id, us.user_id, us.seat_id;
UPDATE:
I have considering this issue further and have considered whether it would allow things to work better to simply remove the user_seat_rtab table and then use the equivalent certification_rtab table (probably renamed) to hold all of the information regarding the status of a user's seat. This way there is a direct relationship established between a User, their Seat, each Product within the Seat, and whether the User has certified for the particular Product and Seat.
So I would apply the following changes to the schema posted with the question:
DROP TABLE user_seat_rtab;
RENAME TABLE certification_rtab TO something_different;
An alternative to further normalize this new structure would be to do something like this:
ALTER TABLE user_seat_rtab
DROP PRIMARY KEY;
ADD COLUMN product_id int(10) unsigned NOT NULL;
ADD CONSTRAINT pk_user_seat_product PRIMARY KEY (user_id, seat_id, product_id);
ADD CONSTRAINT fk_product_user_seat FOREIGN KEY (product_id) REFERENCES product_rtab(id) ON DELETE RESTRICT;
I'm not really certain whether this would solve the problem or if it will just change the nature of the problem slightly while introducing new ones. So, does anyone have any other criticisms or suggestions?