Table 3 foreign key, either has a value and a null - mysql

So i have a users table. user can be author or a normal user. There's no defining column that says a user is an author, only in the story table where there's a foreign key(author_id = user.user_id).
Now i want to add a review function. User can review either a story and an author and can also rate a chapter.
So my table looks like this:
**review**
review_id(PK AI)
user_id(FK - user.user_id)//reviewer NOT NULL
author_id(FK - user.user_id)//reviewee NULL
story_id(FK - story.story_id)//reviewee NULL
chapter_id(FK - chapter.chapter_id)//reviewee NULL
**review_content**
review_content_id(PK AI)
review_id(FK)
rating decimal
content text
date_added datetime
Since user_id in the user_table, story_id in the story table and chapter_id in the chapter table are all PK AI. There is a 100% chance of duplicate/same values.
For example: user_id(5) reviewed author(10). user_id(5) also reviewed story_id(10), or user_id(5) rated chapter_id(10).
So to identify if a review is for a story/author/chapter. I plan to just null other FK and insert a value to the relevant FK.
If a review is for a story, then story_id(FK) will have a value meanwhile author_id and chapter_id will be null.
so in my query it will be:
//fetch review for story
$this->db->from('review');
$this->db->where('author_id', NULL);
$this->db->where('story_id', $story_id);
$this->db->where('chapter_id', NULL);
$this->db->get()->result();
I want to know if my table design is correct. Or should i stick to making different review tables(author,story,chapter).
I really wanted to write reusable methods for the 3 instead of making 3 for each types.
For example: checking whether review exists. Inserting reviews. fetching. etc. If i were to make 3 different tables then i would have to make 3 different methods for each functions.

Related

Best Practice: find row for unique id from multiple tables

our database contain 5+ tables
user
----------
user_id (PK) int NOT NULL
name varchar(50) NOT NULL
photo
--------
photo_id (PK) int NOT NULL
user_id (FK) int NOT NULL
title varchar(50) NOT NULL
comment
-------
comment_id (PK) int NOT NULL
photo_id int NOT NULL
user_id int NOT NULL
message varchar(50) NOT NULL
all primary key id's are unique id's.
all data are linked to http://domain.com/{primary_key_id}
after user visit the link with id, which is unique for all tables.
how should i implement to find what table this id belongs to?
solution 1
select user_id from user where user_id = {primary_key_id}
// if not found, then move next
select photo_id from photo where photo_id = {primary_key_id}
... continue on, until we find which table this primary key belongs to.
solution 2
create object table to hold all the uniqe id and there data type
create trigger on all the tables for AFTER INSERT, to create row in object table with its data type, which was inserted to a selected table
when required, then do select statement to find the table name the id belongs to.
second solution will be double insert. 1 insert for row to actual table with complete data and 2 insert for inserting unique id and table name in object table, which we created on step 1.
select type from object_table where id = {primary_key_id}
solution 3
prepend table name + id = encode into new unique integer - using php
decode id and get the original id with table name (even if its just as number type)
i don't know how to implement this in php, but this solution sounds better!? what are your suggestion?
I don't know what you mean by Facebook reference in the comments but I'll explain my comment a little further.
You don't need unique ID's across five DB tables, just one per table. You have couple of options how to create your links (you can create the links yourself can you?):
using GET variables: http://domain.com/page.html?pk={id}&table={table}
using plain URL: http://domain.com/{id}{table}
Depending on the syntax of the link you choose the function to parse it. You can for example use one or both of the following:
http://php.net/manual/en/function.explode.php
http://www.php.net/manual/en/function.parse-url.php
When you get the simple model working you may add encoding/decoding/hashing functions. But do you really need them? And in what level? (I have no experience in that area so I'll shut up now.)
Is it actually important to maintain uniqueness across tables?
If no, just implement the solution 3 if you can (e.g. using URL encoding).
If yes, you'll need the "parent" table in any case, so the DBMS can enforce the uniqueness.
You can still try to implement the solution 3 on top of that,
or add a type discriminator1 there and you'll be able to (quickly) know which table is referenced for any given ID.
1 Take a look at the lower part of this answer. This is in fact a form of inheritance.

MySQL database design of an online video education website

I am planning on a website that offers users training videos on multiple subjects, users can view videos, check their view history, comment on videos, taking relative exams and gain badges/certificates if they succeed in the exam. Also they can submit live questions when they had a problem during the video. ALso there will be a forum where users can help each other out and add friends, send messages, etc. WIth this idea in mind, I am now designing the database, but I had no experience in database design before, so please review the following design and give me some suggestions on performance and best practices. Thank you all in advance.
There are
USER PROFILE
username VARCHAR
id INT
Type ENUM
Email VARCHAR
Date_created TIMESTAMP
Date_modified TIMESTAMP
Pass VARBINARY
Name VARCHAR
certificates VARBINARY
Ranking_points INT
Gender ENUM
DOB DATE
Avatar_url VARCHAR
VIDEOS
Title VARCHAR
id INT
Category ENUM
Description TINYTEXT
Date_created TIMESTAMP
Tmp_name CHAR
File_name VARCHAR
Size MEDIUMINT
Subtitle_url VARCHAR
Liked_count INT
Shared_count INT
Tags ENUM
CATEGORY
Category VARCHAR
id INT
Description TINYTEXT
RecentlyViewed
User_id INT
Video_id INT
Viewed_date TIMESTAMP
FRIEND_LIST
User_id INT
Friend_id INT
USER_QUESTIONS
User_id INT
Question_id INT
Question_title VARCHAR
Question_content LONGTEXT
Date_asked TIMESTAMP
VIDEO_COMMENTS
Title VARCHAR
Video_id INT
Comment_content LONGTEXT
User_id TINYTEXT
Date_created TIMESTAMP
USER_MESSAGES
User_id INT
Message_id INT
Message_content LONGTEXT
Sender_id INT
Date_created TIMESTAMP
ONLINE EXAMS/ASSESSMENTS
id VARCHAR
type ENUM
exam_url VARCHAR
description TINYTEXT
Date_created TIMESTAMP
EXAMS_TAKEN_BY_USER
exam_id INT
User_id INT
Exam_result SMALLINT
Date_taken TIMESTAMP
I have two more quesions:
1. I would like to allow comments on user comments, how would I design the database structure?
2. One video can belong to multiple categories, is it necessary to create a new table for video categories or just put a series of categories in the category field in the VIDEO table?
you can use self-linked table for user comments. The table would look like this
Id ---- comments ---- parent_comment_Id
1 root comments null
2 child comments 1
3 grandchild comments 2
About video category, it would be nice to have a separate table particularly for video category. And then you can scale category information such as category name, description and etc, in future. Considering user experience, it is quite common case that users want to check the category list first and then click through to get all the related videos.
The overall structure seems to be okay, at least that looks pretty much like something I would do.
1.- About comments on comments:
I guess that the simplest design would be to add a parent_comment_id column to it. But that's a tricky one, because there would be a lot of NULL values either on parent_comment_id or video_id. So, another way around this would be to allow NULL values for video_id on VIDEO_COMMENT. In this scenario when a NULL in this column is found, you would know you're in presence of a reply to a user comment. Then, you'll have to found the parent comment on a second table:
USER_COMMENT
user_comment_id (primary key)
video_comment_id (foreign key to VIDEO_COMMENT)
comment (text)
created_date (date)
This is definitely not the cutest solution, but if you're troubled by performance issues, it could be worthy of a shot. You still have the same VIDEO_COMMENTS table (clean as before, no modifications to its structure). User comments on previous comment might not be that frequent, so you have them in a second structure, and not all over the same VIDEO_COMMENTS table.
2.- About Video Categories
Here I would create 2 additional tables: one for video categories (or tags) and a second for linking each video to its categories.
VIDEOS
Here I would drop the CATEGORY column
CATEGORIES
category_id (primary key)
title
VIDEO_CATEGORIES
video_category_id
category_id
video_id
In this case you can have a list of categories already stored, and the user would only have to select the ones he/she thinks right to his/her video.
I not recommend you to put multiple values on a same column since it's really a bad design issue. You couldn't provide the user with a list of categories (like the one I mentioned before). You would have, also, many duplicated values, typos, etc. Also, having several values stored in the same column makes editing unnecessary difficult: think how would you implement a user request to delete a category tag and add another. Of course, it isn't that hard, but it could be a lot simpler if you only had to delete individual references.

Linking columns in different tables in MySql / parent-child relation

I have two tables. "users" and "movies". Users table consists of "id"(Auto increment), "name" and "password" columns. There are 2 usernames stored right now. In movies table there are 'title' and 'year' columns. The PHP script allows each user to watch and add new movies to their list. How do I link or make the parent-child relationship or whatever is needed to make it happen in MySQL? Oh, and I also use Adminer. Right now when I log in one user I still see the same movies that I've added with the other user.
If you are stuck with using just two tables as stated in a comment, you have to redesign the Movies table to include a column UserID which identifies which user created that entry. Then you can filter the data so that a user only sees information about the movies they added to the list.
This isn't a good design — the answer by Jeremy Smyth suggesting an extra table to relate movies to users is much more sensible, but you've indicated that isn't allowed. The reason it isn't a good design is that you're going to end up with lots of rows indicating that the same movie was released in the same year, each row entered by a different user, so there is unnecessary repetition. There's also more chance for error; you'll get entries for 'Gone With The Wind' 1938, and 'Gone With The Wind' 1939, and 'Gone With The Wind' 1940 when there should only be one year (1939, as it happens).
Can you please be more specific about what I have to do ...
In the two-tables-only system, you would create the Movies table like this:
CREATE TABLE Movies
(
Title VARCHAR(32) NOT NULL,
Year INTEGER NOT NULL,
UserID INTEGER NOT NULL REFERENCES Users(ID),
PRIMARY KEY(Title, Year, UserID)
);
When you insert a record into this table, you record the ID of the user who did the insertion, so you can query who created which movie records.
If you are actually going to reference this table from elsewhere in the database, you might well add an ID column here, but if there are more tables, then you'd drop the UserID column from this table and create a relationship table:
CREATE TABLE Movies
(
ID INTEGER AUTOINCREMENT PRIMARY KEY,
Title VARCHAR(32) NOT NULL,
Year INTEGER NOT NULL,
UNIQUE(Title, Year)
);
CREATE TABLE Users_Movies
(
MovieID INTEGER NOT NULL REFERENCES Movies(ID),
UserID INTEGER NOT NULL REFERENCES Users(ID),
PRIMARY KEY(MovieID, UserID)
);
Now you can have one record for 'Gone With The Wind' 1939, which might have ID number 207, and twenty different people might list MovieID 207 as one of their movies with 20 simple records in the Users_Movies table.
You will need to create a "many-to-many" relationship between your two tables.
To do this:
First, create an ID column in the Movies table to uniquely identify each one
Then, create another table called user_movies (or "watched" or something useful), that contains the user ID, the movie ID, and any other information you wish to add such as date watched or rating (number of "stars") etc.
Then, whenever a user watches a movie, add a record to the user_movies table to mark the fact that they've done it.
It should be many-to-many, because each user can watch several movies, but each movie can be watched by several users. A "parent-child" relationship isn't appropriate in this case, being a one-to-many relationship.

Use varbinary as foreign key

Is it ok to use data type varbinary for foreign keys?
Why?
I have an EvalAnswer table with a FK to a Score table.
The score is sensitive and should be encrypted. The encrypt/decrypt happens in the asp.net (4.0) project and not in sql server (2008), so the data type needs to be varbinary.
EDIT: more info
Of course.
I have these columns: Id, Score, ScoreText, Description, Index
The Id is an incremental counter. (PK)
The Score is the score as number (such as 1).
The ScoreText is the score as a letter (Score 1 equals letter A).
The Description is a comment for every score.
The reason I have it like this is also that there are special situations,
such as one of the questions have only scoring from 1-4, and the rest has 1-5.
So every question has a score 1, but the the description differs from another questions score 1.
So If I have 5 questions, this gives 5*5 rows in the Score table. (All with different description)
When I page load I get the correct scoring (with description) for every dropdownlist. Normally 1-5.
But when the user has saved the scoring, I need to know the earlier saaved score for every question when I page load.
Therefore I have a relation between EvalAnswer and the scoring.
There are questions with relation to the score table which is NOT sensitive.
But some are. And for them I need to hide the relation beetween EvalAnswer and Score.
What might be a bad design is the fact that I use the same table (the score table) as the
one to show the available scoring for every questions.
and also as the one to hold what the user has chosen. (this is the FK from EvalAnswer to Score)
Please advice.......
I suggest adding an ID int column to the Score table and reference this field from the EvalAnswer table.
This means your table scripts will change to
CREATE TABLE Score (
Id int not null identity primary key
, Code varbinary(max) --> The new field containing the encrypted information
, Score int
, ScoreText varchar(5)
, Description varchar(max)
, Index int)
CREATE TABLE EvalAnswer (
Id int not null identity,
ScoreId int not null references Score(Id)
...
)
As you can see, the "Old" Id field has now become the Code field. The new field is an identity column containing a unique number
There is nothing against using a varbinary column in a foreign key, but it will make querying and debugging much harder.
Also note there is a 900 byte limit on the width of an index which might be easier to hit when storing an encrypted blob.

Database many-to-many intermediate tables: extra fields

I have created a 'shops' and a 'customers' table and an intermediate table customers_shops. Every shop has a site_url web address, except that some customers use an alternative url to access the shop's site (this url is unique to a particular customer).
In the intermediate table below, I have added an additional field, shop_site_url. My understanding is that this is in 2nd normalised form, as the shop_site_url field is unique to a particular customer and shop (therefore won't be duplicated for different customers/shops). Also, since it depends on customer and shop, I think this is in 3rd normalised form. I'm just not used to using the 'mapping' table (customers_shops) to contain additional fields - does the design below make sense, or should I reserve the intermediate tables purely as a to convert many-to-many relationships to one-to-one?
######
customers
######
id INT(11) NOT NULL PRIMARY KEY
name VARCHAR(80) NOT NULL
######
shops
######
id INT(11) NOT NULL PRIMARY KEY
site_url TEXT
######
customers_shops
######
id INT(11) NOT NULL PRIMARY KEY
customer_id INT(11) NOT NULL
shop_id INT(11) NOT NULL
shop_site_url TEXT //added for a specific url for customer
Thanks
What you are calling an "intermediate" table is not a special type of table. There is only one kind of table and the same design principles ought to be applicable to all.
Well, let's create the table, insert some sample data, and look at the results.
id cust_id shop_id shop_site_url
--
1 1000 2000 NULL
2 1000 2000 http://here-an-url.com
3 1000 2000 http://there-an-url.com
4 1000 2000 http://everywhere-an-url-url.com
5 1001 2000 NULL
6 1001 2000 http://here-an-url.com
7 1001 2000 http://there-an-url.com
8 1001 2000 http://everywhere-an-url-url.com
Hmm. That doesn't look good. Let's ignore the alternative URL for a minute. To create a table that resolves a m:n relationship, you need a constraint on the columns that make up the m:n relationship.
create table customers_shops (
customer_id integer not null references customers (customer_id),
shop_id integer not null references shops (shop_id),
primary key (customer_id, shop_id)
);
(I dropped the "id" column, because it tends to obscure what's going on. You can add it later, if you like.)
Insert some sample data . . . then
select customer_id as cust_id, shop_id
from customers_shops;
cust_id shop_id
--
1000 2000
1001 2000
1000 2001
1001 2001
That's closer. You should have only one row for each combination of customer and shop in this kind of table. (This is useful data even without the url.) Now what do we do about the alternative URLs? That depends on a couple of things.
Do customers access the sites through
only one URL, or might they use more
than one?
If the answer is "only one", then you can add a column to this table for the URL, and make that column unique. It's a candidate key for this table.
If the answer is "more than one--at the very least the site url and the alternative url", then you need to make more decisions about constraints, because altering this table to allow multiple urls for each combination of customer and shop cuts across the grain of this requirement:
the shop_site_url field is unique to a
particular customer and shop
(therefore won't be duplicated for
different customers/shops)
Essentially, I'm asking you to decide what this table means--to define the table's predicate. For example, these two different predicates lead to different table structures.
customer 'n' has visited the web site
for shop 'm' using url 's'
customer 'n' is allowed to visit the
web site for shop 'm' using alternate
url 's'
Your schema does indeed make sense, as shop_site_url is an attribute of the relationship itself. You might want to give it a more meaningful name in order to distinguish it from shops.site_url.
Where else would you put this information? It's not an attribute of a shop, and it's not an attribute of a customer. You could put this in a separate table, if you wanted to avoid having a NULLable column, but you'd end up having to have a reference to your intermediate table from this new table, which probably would look even weirder to you.
Relationships can have attributes, just like entities can have attributes.
Entity attributes go into columns in entity tables. Relationship attributes, at least for many-to-many relationships, go in relationship tables.
It sounds as though, in general, URL is determined by the combination of shop and customer. So I would put it in the shop-customer table. The fact that many shops have only one URL suggests that there is a fifth normal form that is more subtle than this. But I'm too lazy to work it out.