I'm rebuilding an add-buddy module and I have a question about the database table design.
In the current version i have a design like this:
id | user1_id | user2_id | status
Each time an user invites another, two rows are created, in one row the current users id is placed in the field user1_id, in the next row the users id is placed in user2_id, which enables me to use a rather simple MySQL query in the list mode of the module:
WHERE a.id=b.user2_id AND b.status = 1 AND b.user1_id = '".$get_user."'
Now, i try to find another way to build this, as i would prefer to have only one row for each friendship, instead of the currently used two.
I have come up with several approaches for table-design and building the logged in users buddylist and i would like to see your opinion about this.
using only one field "ids" for both users ids, comma-seperated. in the select-query i check if the field ids contains the logged in users id, probably using LIKE for that. Than in the while loop i would remove the current users id from the field with str_replace($current_user_id, '', $row['id']);
one row, with fields id1 and id2, using a simple OR in the select-query, in the while loop for building the list i would place an if-statement:
if ($row['id1'] == $current_user_id) use $row['id2'] else use $row['id1']
using two select-queries with UNION like this:
(select * from users where id1 = user_id)
UNION
(select * from users where id2 = user_id)
What do you think of this approaches would be the best for this kind of thing, or do you have another idea?
Edit: thought i would have found an easy solution for table and a matching query, but didnt work, so i deleted it.
Like #Nonym suggested, you could use the status column. Set -1 when the friendship is awaiting confirmation, 0 when the friendship has been denied, and 1 if the friendship is accepted.
Getting a list of all friends for a certain user ID is as simple as calling:
SELECT user2_id FROM users WHERE status = 1 AND user1_id = <your user id> .
Since your database is going to be filled with denied invitations, you could have a cronjob running every 24 hours or so, which will delete all denied invitations, reducing space. A query like
DELETE FROM users WHERE status = 0
would be of use. Actually, you can move even further and add another field called date, which will indicate the date when the request was sent and in the same cronjob include deleting records with status = -1, which have been in the table for too long.
EDIT
Just like #jayden said, the user_id's may be mixed, so the best way is to keep these two consistent. Like user1_id being always the current user and user2_id being the receiver. And to know, who invited who, add another field like addressee or any other (probably) more suitable name, which will hold the id of the user who initiated the request.
For starters, and while waiting for a response to the comment, I'll have to say this as soon as possible:
Approach # 1: Try to avoid doing this as much as possible. Data should be what it is; data, so refrain (when you can) from putting logic into the data. Put your logic perhaps in a view, function or stored procedure :)
[edit]
Now I'm confused. Let's take a step back, shall we?
First, you have a table that appears this way (and I will just make a rough 'sketch' of it) :
TABLE USERS
id (auto increment?)
user1_id (the one initiating the friendship invite)
user2_id (the one user1_id is inviting to form a friendship)
status (the status of the friendship; like
A record is added to the table USERS when:
A user, whose id goes into user1_id invites someone whose id then goes into user2_id , and a status is set to mean something like 2 if an invite had just been sent, and 1 if it was accepted or perhaps 0 if it was rejected, etc
What part/s in the above statements is/are incorrect?
[edit] part ii:
Everything aside, if it comes down to:
Get all records where the viewing user is either an initiator (user1_id) or is a recipient of a buddy request (user2_id)
Then you don't really need a union:
SELECT
CASE
WHEN user1_id = <Id_Of_Viewing_User> THEN user2_id
WHEN user2_id = <Id_Of_Viewing_User> THEN user1_id
END AS user_id
FROM users
WHERE
user1_id = <Id_Of_Viewing_User>
OR
user2_id = <Id_Of_Viewing_User>
Can you try the SQL above? That way, you won't need to worry about the programming part. Whatever comes out in user_id is the id of someone who has either requested to be the viewing user's friend of someone the viewing user requested to be a buddy with.
Is this... anywhere.. anywhere at all.. near what you want?
Related
So this is my sorta first time doing an Online RPG (MMORPG), It's a browser based-Pokemon game.
In the Database, l've created 2 tables;
1.Pokemons (Columns; ID#, PokemonName, PokemonType, Level, Exp, HPoints, ATT, DEF)
2.Users (Columns; ID, Full Name, E-Mail, Username, Password)
In the Register field, they put in their info (User, Pass, Email), then chooses a Starter Pokemon to fight with. My question is how would i interpret that into a SQL/PHP command that joins the starter pokemon to that User or vise versa?
Far as l know it's
SELECT * FROM table_name;
But let's say l wanted to choose THAT user who just registered. Would the * just automatically choose that player or will it select everything from the Users list (Currently 3 rows of users in the Table).
Im reading w3schools for the moment, but needed some real-time advice on how l should go about with this. Thanks again!
thepokemonrpg.x10.mx If you guys wanna see what l mean.
This would indeed select everything from the Users table:
SELECT * FROM Users
The * doesn't mean all records, it just means all columns for any matching record. However, since there's no filter, all records happen to be matching records. If you want to only select a single record from that table, you would add a WHERE clause:
SELECT * FROM Users WHERE Username='someusername'
There are a few different ways that you can construct the SQL query to include a value like that (since someusername would likely come from a variable and not be explicitly written like that). Just be aware of SQL injection vulnerabilities when building those queries. You wouldn't want to accidentally publish a website where users can write their own database code and execute it on your server.
As for joining the tables, I currently don't see a way that you could do that. These two tables define two distinct entities, but have no way to relate to one another. There are a couple of ways you could do that, depending on how these entities are actually related. To that end:
Does a Pokemon always have exactly 0 or 1 owner? or;
Does a Person always have exactly 0 or 1 Pokemon? or;
Can a Pokemon have many owners and a Person have many Pokemons?
If the first statement is true, then you can add a UserId column to your Pokemons table and make it a foreign key to the Users table. That way every Pokemon record would indicate which User owns it.
If the second statement is true, then you can add a PokemonId column to your Users table and make it a foreign key to the Pokemons table. That way every User record would indicate which Pokemon is currently owns.
If the third statement is true, then you'd need to add a joining table to maintain this many-to-many relationship. Something like this:
PokemonUsers
------------
Id
PokemonId
UserId
Every record in this table would essentially be a link between a record in the Users table and a record in the Pokemons table.
Description:
I am building a rating system with mysql/php. I am confused as to how I would set up the database.
Here is my article setup:
Article table:
id | user_id | title | body | date_posted
This is my assumed rating table:
Rating table:
id | article_id | score | ? user_id ?
Problem:
I don't know if I should place the user_id in the rating table. My plan is to use a query like this:
SELECT ... WHERE user_id = 1 AND article_id = 10
But I know that it's redundant data as it stores the user_id twice. Should I figure out a JOIN on the tables or is the structure good as is?
It depends. I'm assuming that the articles are unique to individual users? In that case, I could retain the user_id in your rating table and then just alter your query to:
SELECT ... WHERE article_id = 10
or
SELECT ... WHERE user_id = 1
Depending on what info you're trying to pull.
You're not "storing the user_id twice" so much as using the user_id to link the article to unique data associated to the user in another table. You're taking the right approach, except in your query.
I don't see anything wrong with this approach. The user id being stored twice is not particularly relevant since one is regarding a rating entry and the other, i assume, is related to the article owner.
The benefit of this way is you can prevent multiple scores being recorded for each user by making article_id and user_id unique and use replace into to manage scoring.
There are many things to elaborate on this depending on whether or not this rating system needs to be intelligent to prevent gaming, etc. How large the user base is, etc.
I bet for any normal person, this setup would not be detrimental to even a relatively large scale system.
... semi irrelevant:
Just FYI, depending on the importance and gaming aspects of this score, you could use STDDEV() to fetch an average factoring the standard deviation on the score column...
SELECT STDDEV(`score`) FROM `rating` WHERE `article_id` = {article_id}
That would factor outliers supposing you cared whether or not it looked like people were ganging up on a particular article to shoot it down or praise it without valid cause.
you should not, due to 3rd normal form, you need to keep the independence.
"The third normal form (3NF) is a normal form used in database normalization. 3NF was originally defined by E.F. Codd in 1971.[1] Codd's definition states that a table is in 3NF if and only if both of the following conditions hold:
The relation R (table) is in second normal form (2NF)
Every non-prime attribute of R is non-transitively dependent (i.e. directly dependent) on every superkey of R."
Source here: http://en.wikipedia.org/wiki/Third_normal_form
First normal Form: http://en.wikipedia.org/wiki/First_normal_form
Second normal Form: http://en.wikipedia.org/wiki/Second_normal_form
you should take a look to normalization and E/R model it will help you a lot.
normalization in wikipedia: http://en.wikipedia.org/wiki/Database_normalization
On a social network I am working on in PHP/MySQL, I have a friends page, it will show all friends a user has, like most networks do. I have a friend table in MySQL, it only has a few fields. auto_ID, from_user_ID, to_friend_ID, date
I would like to make the friends page have a few different options for sorting the results,
By auto_ID which is basically in the order a friend was added. It is just an auto increment id
new friends by date, will use the date field
By friends name, will have a list in alphabetical order.
The alphabetical is where I need some advice. I will have a list of the alphabet A-Z, when a user clicks on K it will show all the user's name starting with K and so on. The trick is it needs to be fast so doing a JOIN on the user's table is not an option, even though most will argue it is fast, it is not the performance I want for this action. One idea I had is to add an extra field to my friendship table and store the first letter of the users name in it. User's can change there name at anytime so I would have to make sure this is updated on possible thousands of records, anytime a user changes there name.
Is there a better way to do this?
Well if you don't want to do a join, then storing the user's name or initials on the friendships table is really your only other viable option. You mention the problem of having to update thousands of records every time a name changes, but is this really a problem? Unless you're talking about a major social networking site like Facebook, or maybe MySpace, does the average user really have enough friends to make this problematic? And then you have to multiply that by the probability that a user will change their name, which I would imagine isn't something that happens very often for each user.
If those updates are in fact non-trivial, you could always background or delay that to happen during non-peak times. Sure you would sacrifice up-to-the-second accuracy, but really, would most users even notice? Probably not.
Edit: Note, my answer above really only applies if you already have those levels of users. If you are still basically developing your site, just worry about getting it working, and worry about scaling problems when they become real problems.
You could also look at a caching solution like memcached. You can have a background process that is always updating a memcached hash and then when you want this data it is already in memory.
I'd just join on the table that contains the name and then sort on the name. Assuming a pretty normal table layout:
Table Person:
ID,
FirstName,
LastName
Table Friend:
auto_ID,
from_user_ID,
to_friend_ID,
date
You could do things like:
Select person.id, person.firstname, person.lastname, friend.auto_id
from Friend
left join on person where person.id = friend.to_friend_ID
where friend.from_user_ID = 1
order by person.lastname, person.firstname
or
Select person.id, person.firstname, person.lastname, friend.auto_id
from Friend
left join on person where person.id = friend.to_friend_ID
where friend.from_user_ID = 1
order by friend.date desc
I'd really recommend adding a column in the friend table to keep the first letter around, no need to duplicate data like that (and have to worry about keeping it in sync), that's what joins are for.
I'm knee deep in modifying some old logging code that i didn't write and wondering what you think of it. This is an event logger written in PHP with MySQL, that logs message like:
Sarah added a user, slick101
Mike deleted a user, slick101
Bob edited a service, Payment
Broken up like so:
Sarah [user_id] added a user [message], slick101 [reference_id, reference_table_name]
Into a table like this:
log
---
id
user_id
reference_id
reference_table_name
message
Please note that the "Bob" and "Payment" in the above example messages are Id's to other tables, not the actual names. A join is needed to get the names.
It looks like the "reference _ table _ name" is for finding the proper names in the correct table, since only the reference _ id is stored. This would probably be good if somehow i could join on a table name that stored in reference_table_name, like so:
select * from log l
join {{reference_table_name}} r on r.id = l.reference_id
I think I see where he was going with this table layout - how much better to have ids for statistics instead of a storing the entire message in a single column (which would require text parsing). Now I'm wondering..
Is there a better way or is it possible to do the make-believe join somehow?
Cheers
To get the join based on the modelling, you'd be looking at a two stage process:
Get the table name from LOG for a particular message
Use dynamic SQL by constructing the actual query as a string. IE:
"SELECT l.* FROM LOG l JOIN "+ tableName +" r ON r.id = l.reference_id"
There's not a lot of value to logged deletions because there's no record to join to in order to see what was deleted.
How much history does the application need?
Do you need to know who did what to a value months/years in the past? If records are required, they should be archived & removed from the table. If you don't need all the history, consider using the following audit columns on each table:
ENTRY_USERID, NOT NULL
ENTRY_TIMESTAMP, DATE, NOT NULL
UPDATE_USERID, NOT NULL
UPDATE_TIMESTAMP, DATE, NOT NULL
These columns allow you to know who created the record & when, and who last successfully updated it and when. I'd create audit tables on a case by case basis, it just depends on what functionality the user needs.
I'm working on a PHP app that has several objects that can be commented on. Each comment can be voted on, with users being able to give it +1 or -1 (like Digg or Reddit). Right now I'm planning on having a 'votes' table that has carries user_id and their vote info, which seems to work fine.
The thing is, each object has hundreds of comments that are stored in a separate comments table. After I load the comments, I'm having to tally the votes and then individually check each vote against the user to make sure they can only vote once. This works but just seems really database intensive - a lot of queries for just the comments.
Is there a simpler method of doing this that is less DB intensive? Is my current database structure the best way to go?
To be clearer about current database structure:
Comments table:
user_id
object_id
total_votes
Votes table:
comment_id
user_id
vote
End Goal:
Allow user to vote only once on each comment with least # of MySQL queries (each object has multiple comments)
To make sure that each voter votes only once, design your Votes table with these fields—CommentID, UserID, VoteValue. Make CommentID and UserID the primary key, which will make sure that one user gets only one vote. Then, to query the votes for a comment, do something like this:
SELECT SUM(VoteValue)
FROM Votes
WHERE CommentID = ?
Does that help?
Why don't you save the totaled votes for every comment? Increment/decrement this when a new vote has happened.
Then you have to check if the user has voted specifically for this comment to allow only one vote per comment per user.
You can put a sql join condition which returns all the votes on comments made by the current user for this object, if you get no rows, the user hasn't voted. That is just slightly different from you checking each comment one by one in the program.
as far as the database structure is concerned, keeping these things separate seems perfectly logical. vote { user_id, object_id, object_type, vote_info...)
You may be already doing this, sorry but I couldn't interpret from you post if that was the case.