Database Design: Multiple tables vs a single table - mysql

I am making a website where there are different types of items such as blogs, posts, articles and so on. A user can set any one of them as his/her favorite. Now when I approach this thing, I have two options
Make a table for user favorites for each type of object.
Make a common table for all type of objects for all the users.
The problem with the 1st structure is that I will have to query a lot of tables for displaying the favorites of a particular user. But it will allow me to easily group the favorites into different categories.
However if I have to show all the favorites on one single page and merge them all, sorted according to time, then that becomes difficult. But if I use the second model, I can easily get the latest favorites, and also grouping them according to object type is not difficult, but I will have one large table site wide.
Which of the two strategies will be more scalable.
The 1st one entails multiple database queries, and the second one
entails a large single table.
If it helps, I am using MySql

It seems that you already know the answer, but remember, keep the systems you design simple to modify as business models always change over time or they eventually fail (it's a generalization but you get the idea). A corollary of that is if you make a rigid model, fast or slow, it's rigid, changes will be harder and the end user won't see the difference, hence no money/happiness change is achieved, unless it's a very bad change.
Your problem is not technical in a way a query works on the engine but more of a philosophical one, easy changes versus apparent speed.
Ask yourself, what's the advantage of having a normalized database? Think about a clean architecture and design, performance is the least problem in todays world as processing is cheaper and storage also. But design is expensive.
Normalization was made to make systems that don't depend on last moment decisions but on a structured design process.
Big tables are not a big deal for MySql but they are a big deal to maintain, modify and expand. It's not just adding one more column, it's about the rigid structure of the data itself. Eventually in time you will just add columns that contain indexes, and those indexes will be pointing to small tables. MySql will be plowing it's way around all that data anyway.
So i'll go for the first one, a lot of small tables, many-to-many.

I have this design on my website. My modules are: news, articles, videos, photos, downloads, reviews, quizzes, polls, etc etc. All in separate tables. I have a likes table where users can like or dislike a post (in your case favorites). The query to get these isn't that complicated.
First off for the most part MOST of my tables for the modules are structured the same way:
id
title
content
user_id (author)
date
etc
with a few exceptions being that sometimes title is called question or there is no content column. That does not cause any issues.
My likes tables is set up like this:
id
page_id
module_id (what table did it come from...I have a modules table where each module has a title, associated id, directory, etc)
post_id (corresponds to the module table id)
user_id (user who did the liking or posting)
status (0 = like, 1 = dislike)
date (when the liking/disliking took place)
Modules table example:
id
title
directory
post_type
Example
id title directory post_type
1 News news news
2 Episode Guide episodes episode
3 Albums discography/albums album
Essentially yours would have a similar set up, modifying the table structure as necessary for your needs.
Query to get all the likes or favorites for a particular user:
$getlikes = mysql_query("SELECT DISTINCT post_id, module_id, page_id FROM likes WHERE user_id = $profile_id ORDER BY id DESC LIMIT $offset, $likes_limit", $conn);
$likes = mysql_num_rows($getlikes);
if($likes == "0"){
echo "<br><Center>$profile_username does not have any liked posts at this time.</center><BR>";
}
else {
echo "<table width='100%' cellspacing='0' cellpadding='5'>
<Tr><th>Post</th><th align='center'>Module</th><th align='center'>Page</th><tr>";
while ($rowlikes = mysql_fetch_assoc($getlikes)) {
// echo data
$like_page_id = $rowlikes['page_id'];
$like_module_id = $rowlikes['module_id'];
$like_post_id = $rowlikes['post_id'];
// different modules have different fields for the "title", most are called title but quotes is called "content" and polls is called "questions"
if($like_module_id == "11"){
$field = "question";
}
elseif($like_module_id == "19"){
$field = "content";
}
else{
$field = "title";
}
// FUNCTIONS
PostURL($like_page_id, $like_module_id, $like_post_id);
ModTitle($like_module_id);
ModTable($like_module_id);
ModURL($like_page_id, $like_module_id);
fpgURL($like_page_id);
$getpostinfo = mysql_query("SELECT $field AS field FROM $mod_table WHERE id = $like_post_id", $conn);
$rowpostinfo = mysql_fetch_assoc($getpostinfo);
$like_post_title = $rowpostinfo['field'];
// Using my "tiny" function to shorten the title if the module is "Quotes"
if($like_module_id == "19"){
Tiny($like_post_title, "75");
$like_post_title = "\"$tiny\"";
}
if(!$like_post_title){
$like_post_title = "<i>Unknown</i>";
}
else {
$like_post_title = "<a href='$post_url'>$like_post_title</a>";
}
echo "<tr class='$altrow'>
<td>$like_post_title</td>
<td align='center'><a href='$mod_url'>$mod_title</a></td>
<td align='center'>$fpg_url</td>
</tr>";
$altrow = ($altrow == 'altrow')?'':'altrow';
} // end while
echo "<tr><Td align='center' colspan='3'>";
// FUNCTIONS - Pagination links
PaginationLinks("$cs_url/users/$profile_id", "likes");
echo "</td></tr></table>";
} // end else if no likes
Ok that may be hard for you to understand since I have alot of my own variables, but basically it gets the module id and post id from the likes table and then runs a query to get the title of the post and any other info I want like the original author.
I have "module" functions set up that will return the url or the title of the module given you provide an id for it.

So if I'm not mistaken, you are trying to create a Favorites table to collect the user's favorite items right? If so, you will need at least two tables.
Types: The types of the resources.
+----+---------+
| ID | Name |
+----+---------+
| 0 | blog |
| 1 | post |
| 2 | article |
| 3 | photo |
| 4 | video |
+----+---------+
Favorites: The most important part of the Favorite system, it's kinda like a relationships map.
+--------+----------+--------------+
| UserID | TargetID | TargetTypeID |
+--------+----------+--------------+
| 941 | 1 | 0 |
| 6 | 935 | 1 |
| 26 | 51 | 4 |
| 7 | 87 | 2 |
+--------+----------+--------------+
Posts: The example posts table, you might also have Blogs or Photos and Albums tables.
+-----+------------------+
| ID | Title |
+-----+------------------+
| 0 | This is my post! |
| 51 | Oh, how are you? |
| 935 | Hello, world! |
+-----+------------------+
Now, the SQL Query might be like this (untested):
-- Get the posts
SELECT p.*
FROM Posts p
LEFT JOIN Favorites f
-- Which are favorited by the user 6
ON f.UserID = 6
-- Also get the type id of the `post`,
-- so we can specify the favorite type of the favorite items
AND f.TargetTypeID = (
SELECT ID
FROM Types
WHERE Name = 'post'
)
-- Make sure we only get the posts which are favorited by the user.
WHERE p.ID = f.TargetID
With the SQL Query above, you can get the favorite posts which is been favorited by the User ID 6.
+-----+------------------+
| ID | Title |
+-----+------------------+
| 935 | Hello, world! |
+-----+------------------+

Related

MySQL: Structuring Notification Message System So Parts of It Can Be Clicked

For my senior design project, I am working on a project which is an application where an engineer can upload his/her design either 3D, 2D, or 1D and get votes by their customers which design looks good. The issue I am having is structuring my notification to where parts of the notification can be clicked and viewed in detail. If I want to insert a value before, in between, or at the end of the notification message like handles, do I need like a config file with custom messages in the backend and then insert the handles from the frontend? See examples 1 and 2 below.
Example 1 of what the notification should look like:
"Design {design_name} voted by {customer_name} and {50} other customers"
Example 2 of what the notification should look like:
"Your design {design_name} and {10} other designs got votes"
As you can see in the examples above, parts of it can be clicked to see in detail. In example 1, I should be able to click on 50 and view the remaining customers who voted for that particular design. Same concept applies to example 2. Any advice will be greatly greatly appreciated. Thank you.
//Notifications MySQL Table Fields
+----------+----------+----------+----------+-----------+----------+
| id | from_id | to_id | type | design_id | viewed |
+----------+----------+----------+----------+-----------+----------+
//3D Design MySQL Table Fields
+----------+----------+-------------+-------------+------------+
|design_id | user_id | description | design_name | image |
+----------+----------+-------------+-------------+------------+
//2D Design MySQL Table Fields
+----------+----------+-------------+-------------+------------+
|design_id | user_id | description | design_name | image |
+----------+----------+-------------+-------------+------------+
//1D Design MySQL Table Fields
+----------+----------+-------------+-------------+------------+
|design_id | user_id | description | design_name | image |
+----------+----------+-------------+-------------+------------+
With the design of the tables as they are, you could get the parameters by the following.
#notification_1
SELECT
design_name,
count(*) - 1 as number_of_other_voters,
voters_table.voters_name
FROM design_table
LEFT JOIN votes_table ON voted_design_id = design_id
LEFT JOIN voters_table on votes_table.voter_id = voters_table.id
WHERE design_id = 'x'
GROUP BY design_id
If you wanted to jam everything into a query, then you can do the following (although I highly suggest against it):
#notification_1
SELECT CONCAT("Design ", design_name, " voted by ", voters_table.voters_name, " and ", count(*) - 1 as number_of_other_voters, " other customers")
FROM design_table
LEFT JOIN votes_table ON voted_design_id = design_id
LEFT JOIN voters_table on votes_table.voter_id = voters_table.id
WHERE design_id = 'x'
GROUP BY design_id
Though this will get you a string, and possibly a meaningful one, this will not achieve the usability goals that you have laid out. If you are using PHP to build a web interface, for example, you would just run the first query and assign the results to a variable, then build your string with the links filled in with some IDs.
foreach($result as $row){
$notification = "Design " . $row['design_name'] .
" voted by <a href='/user/" . $row['customer_id'] . "'>" . $row['customer_name'] .
"</a> and <a href='/some_voting_page_url/" . $row['vote_id'] . "'>. " $row['number_of_other_voters'] " . other customers"
}
Any language (JS, PHP, Java, C, etc) will have the ability to load data from a query into a pre-fabricated string with the above method. The second notification would just be a count on the votes table (which you have not shown where the votes are being stored in your example)
SELECT count(*) FROM (SELECT DISTINCT design_id FROM votes_table WHERE user_id = 'x');
That's the best I can do with the tables as they are currently designed (considering you add a user table and a votes table). As pointed out by #Gilbert Le Blank in the comment on the main task, you should only have one table with the ability to mark what the dimension of the design is rather than three design tables unless the data stored in each is fundamentally different.

MySQL query get column value similar to given

Sorry if my question seems unclear, I'll try to explain.
I have a column in a row, for example /1/3/5/8/42/239/, let's say I would like to find a similar one where there is as many corresponding "ids" as possible.
Example:
| My Column |
#1 | /1/3/7/2/4/ |
#2 | /1/5/7/2/4/ |
#3 | /1/3/6/8/4/ |
Now, by running the query on #1 I would like to get row #2 as it's the most similar. Is there any way to do it or it's just my fantasy? Thanks for your time.
EDIT:
As suggested I'm expanding my question. This column represents favourite artist of an user from a music site. I'm searching them like thisMyColumn LIKE '%/ID/%' and remove by replacing /ID/ with /
Since you did not provice really much info about your data I have to fill the gaps with my guesses.
So you have a users table
users table
-----------
id
name
other_stuff
And you like to store which artists are favorites of a user. So you must have an artists table
artists table
-------------
id
name
other_stuff
And to relate you can add another table called favorites
favorites table
---------------
user_id
artist_id
In that table you add a record for every artist that a user likes.
Example data
users
id | name
1 | tom
2 | john
artists
id | name
1 | michael jackson
2 | madonna
3 | deep purple
favorites
user_id | artist_id
1 | 1
1 | 3
2 | 2
To select the favorites of user tom for instance you can do
select a.name
from artists a
join favorites f on f.artist_id = a.id
join users u on f.user_id = u.id
where u.name = 'tom'
And if you add proper indexing to your table then this is really fast!
Problem is you're storing this in a really, really awkward way.
I'm guessing you have to deal with an arbitrary number of values. You have two options:
Store the multiple ID's in a blob object in JSON format. While MySQL doesn't have JSON functions built in, there are user defined functions that will extract values for you, etc.
See: http://blog.ulf-wendel.de/2013/mysql-5-7-sql-functions-for-json-udf/
Alternatively, switch to PostGres
Add as many columns to your table as the maximum number of ID's you expect to have. So if /1/3/7/2/4/8/ is the longest entry, have 6 columns in your table. Reason this is bad: you'll have sparse columns that'll unnecessarily slow your tables.
I'm sure you could write some horrific regex to accomplish the task, but I caution on using complex regex's on enormous tables.

Database design and query optimization/general efficiency when joining 6 tables in mySQL

I have 6 tables. These are simplified for this example.
user_items
ID | user_id | item_name | version
-------------------------------------
1 | 123 | test | 1
data
ID | name | version | info
----------------------------
1 | test | 1 | info
data_emails
ID | name | version | email_id
------------------------
1 | test | 1 | 1
2 | test | 1 | 2
emails
ID | email
-------------------
1 | email#address.com
2 | second#email.com
data_ips
ID | name | version | ip_id
----------------------------
1 | test | 1 | 1
2 | test | 1 | 2
ips
ID | ip
--------
1 | 1.2.3.4
2 | 2.3.4.5
What I am looking to achieve is the following.
The user (123) has the item with name 'test'. This is the basic information we need for a given entry.
There is data in our 'data' table and the current version is 1 as such the version in our user_items table is also 1. The two tables are linked together by the name and version. The setup is like this as a user could have an item for which we dont have data, likewise there could be an item for which we have data but no user owns..
For each item there are also 0 or more emails and ips associated. These can be the same for many items so rather than duplicate the actual email varchar over and over we have the data_emails and data_ips tables which link to the emails and ips table respectively based on the email_id/ip_id and the respective ID columns.
The emails and ips are associated with the data version again through the item name and version number.
My first query is is this a good/well optimized database setup?
My next query and my main question is joining this complex data structure.
What i had was:
PHP
- get all the user items
- loop through them and get the most recent data entry (if any)
- if there is one get the respective emails
- get the respective ips
Does that count as 3 queries or essentially infinite depending on the number of user items?
I was made to believe that the above was inefficient and as such I wanted to condense my setup into using one query to get the same data.
I have achieved that with the following code
SELECT user_items.name,GROUP_CONCAT( emails.email SEPARATOR ',' ) as emails, x.ip
FROM user_items
JOIN data AS data ON (data.name = user_items.name AND data.version = user_items.version)
LEFT JOIN data_emails AS data_emails ON (data_emails.name = user_items.name AND data_emails.version = user_items.version)
LEFT JOIN emails AS emails ON (data_emails.email_id = emails.ID)
LEFT JOIN
(SELECT name,version,GROUP_CONCAT( the_ips.ip SEPARATOR ',' ) as ip FROM data_ips
LEFT JOIN ips as the_ips ON data_ips.ip_id = the_ips.ID )
x ON (x.name = data.name AND x.version = user_items.version)
I have done loads of reading to get to this point and worked tirelessly to get here.
This works as I require - this question seeks to clarify what are the benefits of using this instead?
I have had to use a subquery (I believe?) to get the ips as previously it was multiplying results (I believe based on the complex joins). How this subquery works I suppose is my main confusion.
Summary of questions.
-Is my database setup well setup for my usage? Any improvements would be appreciated. And any useful resources to help me expand my knowledge would be great.
-How does the subquery in my sql actually work - what is the query doing?
-Am i correct to keep using left joins - I want to return the user item, and null values if applicable to the right.
-Am I essentially replacing a potentially infinite number of queries with 2? Does this make a REAL difference? Can the above be improved?
-Given that when i update a version of an item in my data table i know have to update the version in the user_items table, I now have a few more update queries to do. Is the tradeoff off of this setup in practice worthwhile?
Thanks to anyone who contributes to helping me get a better grasp of this !!
Given your data layout, and your objective, the query is correct. If you've only got a small amount of data it shouldn't be a performance problem - that will change quickly as the amount of data grows. However when you ave a large amount of data there are very few circumstances where you should ever see all your data in one go, implying that the results will be filtered in some way. Exactly how they are filtered has a huge impact on the structure of the query.
How does the subquery in my sql actually work
Currently it doesn't work properly - there is no GROUP BY
Is the tradeoff off of this setup in practice worthwhile?
No - it implies that your schema is too normalized.

Correct way of storing a list of related values in Database

Let's say I have the following scenario.
A database of LocalLibrary with two tables Books and Readers
| BookID| Title | Author |
-----------------------------
| 1 | "Title1" | "John" |
| 2 | "Title2" | "Adam" |
| 3 | "Title3" | "Adil" |
------------------------------
And the readers table looks like this.
| UserID| Name |
-----------------
| 1 | xy L
| 2 | yz |
| 3 | xz |
----------------
Now, lets say that user can create a list of books that they read (a bookshelf, that strictly contains books from above authors only i.e authors in our Db). So, what is the best way to represent that bookshelf in Database.
My initial thought was a comma separated list of BookIDin Readers table. But it clearly sounds awkward for a relational Db and I'll also have to split it every time I display the list of users' books. Also, when a user adds a new book to shelf, there is no way of checking if it already exists in their shelves except to split the comma-separated list and and compare the IDs of two. Deleting is also not easy.
So, in one line, the question is how does one appropriately models situations like these.
I have not done anything beyond simple SELECTs and INSERTs in MySQL. It would be much helpful if you could describe in simpler terms and provide links for further reading.
Please comment If u need some more explanation.
Absolutely forget the idea about a comma separated list of books to add to the Readers table. It will be unsearchable and very clumsy. You need a third table that join the Books table and the Readers table. Each record in this table represent a reader reading a book.
Table ReaderList
--------------------
UserID | BookID |
--------------------
You get a list of books read by a particular user with
select l.UserID, r.Name, l.BookID, b.Title, b.Author
from ReaderList l left join Books b on l.BookID = b.BookID
left join Readers r on l.UserID = r.UserID
where l.UserID = 1
As you can see this pattern requires the use of the keyword JOIN that bring togheter data from two or more table. You can read more about JOIN in this article
If you want, you could enhance this model adding another field to the ReaderList like the ReadingDate

SQL Server table with dynamic columns?

I am wondering how to approach this problem.
We have a profile table in our database that will be populated by a process that reads
an uploaded excel document and then dumps the columns and the data in their rows into the
user's profile (the only thing common between all such excel spreadsheets is an email address).
Accordingly, we can't really predict what a given user's profile is going to look like.
How do I create my profile table(s)?
Sorry I have to create another answer, but comment wont let me create the ascii sketch
----------
| user |
----------
| id pk|
| name |
| ..... |
----------
----------------
| preference |
----------------
| user_id fk | <-- reference user.id
| header |
| value |
----------------
csv_row=1,churk,height,11,weight,500lb,width,22,...
OR csv_row=1,churk,height=11,wieght=500lb,width=22......
this will yield 1 row in user table, user.id = 1, user.name = Churk
at least 3 rows in preference. {[1,height,11],[1,weight,500lb],[1,width,22]}
So when you query the DB, all you need is
SELECT * FROM user JOIN preference on preference.user_id = user.id WHERE user.name = 'Churk';
If you have someway to process these data using a programming language, and not blinding doing mappings, the it should be pretty simple.
User_table with an ID, and some fix info such as name and what not.
Then you have a profile table that has a user_id foreign key, and key pair value, header / value
How will this data be queried later? As much as I hate the practice, this may be a case of needing to store csv data in a column.
Update:
This might be a good fit for an Entity Attribute Value schema. I'm not really a fan of EAV either, but at least it's less evil than csv data in a column.