Recommended MySQL data model for similar class objects - mysql

We will be creating a MySQL database to reflect the following PHP classes:
Content
Article extends Content
Post extends Content
...
There will be models associated with Content, like Comment, Ignore etc..
The question here is which data model to use.
1) One table per model
2) One large content table (with all class properties as columns), plus single tables for the other models. I believe it's called single table inheritance.
3) A large content table with the common fields, or only the fields we commonly search on (indexes), plus a large content-data table, with the rest of the properties.
4) Another way?
We want to be able to query against the Content, and get recent, location-based or somehow filtered content, regardless of which content type (subclass) it is. Therefore solution 1 is probably out of the question.
This system should be quick and scalable. What is the best way to go about this problem?

I wrote a blog post about this topic that you may find helpful: http://jasonswett.net/blog/composition-a-better-alternative-to-orm-inheritance/
First, I'd like to recommend that you call your table content_item or something instead of content. The reason for this is I believe the table name should match the name of the thing it represents. See this other blog post of mine for details on that.
So you might have a table called content_item, a table called article and a table called post. content_item would have columns like title, slug, etc - everything that every content item will have, whether it be a post, article or something else. Then article would have a content_item_id and then anything else that's specific to articles, but only what's specific to articles. For each article, you'd have a content_item record, then an article record that's attached to that content_item record.
This would give you the ability to query content_item and get results that include all content types.
I've implemented this kind of solution in the past with good success. (I've also tried different approaches and didn't like them as much.)

If they are sufficiently similar objects, i would use option 3... A la drupals node concept or the current wordPress idea of a 'post'

Try something like this perhaps. Your needs for post vs. articles extending content may differ, but instead of a 1..many relationship from content -> post (or article), why not add flexibility to mix/match content within any. If you don't need/desire that, then omit the article_content and post_content tables and simply add a foreign key content_id INT NOT NULL to each of the post and article tables respectively..
CREATE TABLE IF NOT EXISTS content (id INT NOT NULL AUTO_INCREMENT, title VARCHAR(32) NOT NULL, description VARCHAR(255) NULL, body TEXT, published DATETIME NOT NULL, updated DATETIME NULL, owner_id INT DEFAULT '0', status TINYINT(1) DEFAULT '1', PRIMARY KEY (id), INDEX idx_content_status (status));
CREATE TABLE IF NOT EXISTS article (id INT NOT NULL AUTO_INCREMENT, title VARCHAR(32) NOT NULL, excerpt VARCHAR(255) NULL, url_slug VARCHAR(64) NOT NULL, author_id INT NOT NULL, published DATETIME NOT NULL, updated DATETIME NULL, status TINYINT(1) DEFAULT '1', sort_order INT DEFAULT '1', PRIMARY KEY (id), UNIQUE INDEX idx_article_slug(url_slug), INDEX idx_article_search(title, published, status, sort_order));
CREATE TABLE IF NOT EXISTS post (id INT NOT NULL AUTO_INCREMENT, title VARCHAR(32) NOT NULL, comment VARCHAR(128) NULL, author_id INT NOT NULL, published DATETIME NOT NULL, updated DATETIME NULL, status TINYINT(1) DEFAULT '1', PRIMARY KEY (id));
CREATE TABLE IF NOT EXISTS tags (id INT NOT NULL AUTO_INCREMENT, tag VARCHAR(24) NOT NULL, PRIMARY KEY (id));
CREATE TABLE IF NOT EXISTS article_content (article_id INT NOT NULL, content_id INT NOT NULL, status TINYINT(1) DEFAULT '1', sort_order INT DEFAULT '1', INDEX idx_article_content_search(article_id, content_id, status, sort_order));
CREATE TABLE IF NOT EXISTS post_content (post_id INT NOT NULL, content_id INT NOT NULL, status TINYINT(1) DEFAULT '1', sort_order INT DEFAULT '1', INDEX idx_post_content_search(post_id, content_id, status, sort_order));
CREATE TABLE IF NOT EXISTS article_tags (article_id INT NOT NULL, tag_id INT NOT NULL, INDEX idx_article_tag_search(article_id, tag_id));
CREATE TABLE IF NOT EXISTS post_tags (post_id INT NOT NULL, tag_id INT NOT NULL, INDEX idx_post_tag_search(post_id, tag_id));

Related

How to create a public id?

I have a database. As you can see the primary key is an auto_increment and is also unique. I read that publically sharing a row's primary key of a table to the public is unsafe. I want to assign each row in customers a unique ID that I can publically share. How can I do this without having to specify each time what the public_id is in the INSERT statement? The database should automatically find a unique ID to assign to that row just like it does for id because of auto_increment.
CREATE TABLE customers (
id int primary key auto_increment,
name varchar(32) not null,
-- public_id (an ID I can give to the public to uniquely identify this row
);
INSERT INTO customers (name) VALUES ('Bob'), ('Sarah'), ('Bob');
Well, here's one way:
CREATE TABLE customers (
id int primary key auto_increment,
name varchar(32) not null,
public_id char(36) not null unique default uuid()
);
Note that the manual says:
Warning
Although UUID() values are intended to be unique, they are not necessarily unguessable or unpredictable. If unpredictability is required, UUID values should be generated some other way.
So this is simple, and maybe will float your goat, but we can also try better:
CREATE TABLE customers (
id int primary key auto_increment,
name varchar(32) not null,
public_id char(24) not null unique default to_base64(random_bytes(18))
);
This will be a nice and dense identifier, but it will have characters + and / which don't play well with URLs. You can encode them, of course, but if you want to go one lazier, you can also do this:
CREATE TABLE customers (
id int primary key auto_increment,
name varchar(32) not null,
public_id char(32) not null unique default hex(random_bytes(16))
);
Mind you, the identifier will get quite a bit longer this way.
To get the best of both worlds, we can do this, at the expense of a really long default value:
CREATE TABLE customers (
id int primary key auto_increment,
name varchar(32) not null,
public_id char(24) not null unique default replace(replace(to_base64(random_bytes(18)), '+', '_'), '/', '-')
);
Also note that messing around with MD5()/SHA()/SHA1()/SHA2() is no better than just generating a random hex string with a given length.

One table or Two tables to store Forum's threads and Posts?

I am building forum site with ASP.net and DB is MYSQL.
as you all know users can start a thread and others can reply to it.
So here is the table that I implemented.
CREATE TABLE `a_post` (
`post_id_pk` int(10) unsigned NOT NULL AUTO_INCREMENT,
`is_thread` tinyint(1) NOT NULL,
`parent_thread_id` int(10) unsigned DEFAULT NULL,
`title` varchar(100) DEFAULT NULL,
`short_description` varchar(200) DEFAULT NULL,
`description` text NOT NULL,
`category_id_fk` tinyint(3) unsigned DEFAULT NULL,
`user_id_fk` int(10) unsigned NOT NULL,
......
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
So I am using one table to store both threads and posts. This approach is working fine. Normally thread count is smaller than posts count. Or else I can implement two tables to do is. One is to store threads and another one is to store comments with the corresponding thread Id.
What is best? One table or managing two tables?
I want an answer with performance wise. People who did forum systems. You are most welcome here.
One thread can have multiple posts and by that you might consider having two table, one for threads and one for posts.
also remember that, duo to microsoft naming conventions table field should be pascal case. so by that i suggest to change you schema to below:
Table [Threads]
ID int
CreatedBy int
CreatedOn DateTime
Title nvarchar
CategoryID int
...
Table [ThreadPosts]
ID int
ThreadID int
CreatedBy int
CreatedOn DateTime
Body nvarchar
...
by doing this you avoid data duplication, because for example when someone send a post on a thread you don't have a category_id to fill, and the field left empty. that might be the performance issue on larger systems.
consider you want to get all the posts of a thread. its so easy with two tables :
var query = from thread in db.Threads
join posts in db.ThreadPosts on thread.ID equals posts.ThreadID
where thread.ID == threadID
select new ThreadFullModel(){
Thread = thread,
Posts = posts
};

Table localization - One column for a table

I have got only one column for a table when i create two localized tables. Code as bellow.
-- Month
CREATE TABLE `month` (
`id` INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
);
-- Month Localized
CREATE TABLE `month_loc` (
`month_id' INT NOT NULL,
`name` VARCHAR(200) NOT NULL,
`description` VARCHAR(500) NOT NULL,
`lang_id` INT NOT NULL
);
month_loc.month_id is the foreign key.
month table holds only the primary key. Other all fields should be localized. Is this table structure correct ?
Thanks.
If correct implies a certain degree of normalization, and the content of your columns name and description vary per month_id, lang_id (which would be the combined primary key of month_loc), then yes, your design has reached the 3rd grade of normlization.

Database Design - Catalogue - Range - Product

Can anyone suggest a database design for the following:
A user can make a catalogue
Within a catalogue a user can make a range - i.e. a range of products
Within a range a user can add multiple products
Within a range a user can add multiple ranges -> range->range->range all with products in them.
I currently have in my database -
catalogue_range with - id, name, description
and
catalogue_product with - id, range_id, name, description
can anyone see what I'm trying to produce?
My aim is to be able to make multiple catalogue ranges within a catalogue range and add multiple products to each of these catalogue ranges.
Here is my current SQL:
`catalogue_range` (
`id` char(40) NOT NULL,
`profile_id` char(40) NOT NULL,
`type` enum('pdf','db') DEFAULT NULL,
`status` enum('new','draft','live') NOT NULL,
`name` varchar(64) NOT NULL,
`description` varchar(1000) NOT NULL,
`updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `profile_id` (`profile_id`)
)
`catalogue_product` (
`id` char(40) NOT NULL,
`catalogue_id` char(40) NOT NULL,
`order` smallint(5) unsigned NOT NULL,
`name` varchar(50) NOT NULL,
`description` varchar(250) NOT NULL,
PRIMARY KEY (`id`),
KEY `catalogue_id` (`catalogue_id`)
)
Thanks in advance.
catalogue(catalogue id, your private attributes)
product(product id, #catalogue id, your private attributes)
range(range id, #range id parent, your private attributes)
product range(#product id, #range id)
You will need stored procedures/applicative algorithms to compile:
the list of product of a range (to calculate recursive sqls mysql doesn't offer analytic functions as oracle does)
the list of ranges of a catalogue/range
Hope it helps.
S.
Assuming that a product can only exist in one catalogue at a time, your design is almost alright as it is. What you are missing is a recursive foreign key on catalogue_range. Add something like the following to your catalogue_range table definition:
`parent_range_id` char(40) NULL,
FOREIGN KEY (`parent_range_id`) REFERENCES catalogue_range(`id`)
The top level range(s) for any given user will have a NULL parent_range_id, others will refer to the containing range. Note that hierarchies aren't necessarily easy to work with in SQL. You may also want to look into techniques for making hierarchical data more SQL-friendly, such as nested sets.

What is Best Practice for mapping a comment table to multiple tables in mysql?

I am looking to find the best practice in mapping one base table to multiple tables. For example if I had any one of the following base tables (comments,tags,favorites,ratings), it could map to one or more tables such as (blog post, picture, video). The example below can provide a better explanation.
More info:
I am looking to use these tables to create a Yii application which uses Active Record.
My proposed solution (Mysql):
My Base Table
create table comment (
id int(4) unsigned not null auto_increment primary key,
attach_id int(4) unsigned not null, #used to attach to a specific post/photo/video
attach_type_id tinyint(1) unsigned not null, #foreign key to attach_type(id)
comment text not null,
user_id int(4) unsigned null,
datetime_added datetime not null,
foreign key (attach_type_id) references attach_type(id)
);
My "Global Mapping" Table:
create table attach_type (
id tinyint(1) unsigned not null auto_increment primary key,
table_name varchar(20) not null #used for reference purposes only
);
Primitive Example of two of the "Multiple" Tables:
create table blog_post (
id int(4) unsigned not null auto_increment primary key,
title varchar(100) not null,
post text not null,
user_id int(4) unsigned null,
datetime_added datetime not null
);
create table photo (
id int(4) unsigned not null auto_increment primary key,
title varchar(100) not null,
description varchar(255) null,
file_name varchar(100) not null,
user_id int(4) unsigned null,
datetime_added datetime not null
);
To retrieve all comments for blog post id=54
blog_post table's id for its row in the attach_type table = 1
The post's row id for its row in the blog_post table = 54
select * from comments where attach_type_id=1 and attach_id=54;
So a (comment,tag,favorite,rating), comment seen above, can be attached to both a blog_post and/or a photo. Likewise multiple comments can be attached to a single blog_post / photo (allows for multiple users to comment). My question is what is the best way to go about this in mysql. Does the above look like a proper setup or would you suggest a better way and why. And also if using the above solution, does anyone forsee any glaring cons? Thank you for your response in advance, I'm simply trying to figure out the best way to go about doing this.
I believe this topic is related to what I am asking, but did not really answer my question:
Database tables, one table referencing multiple unrelated tables
Tom H. supplied an answer to another question which I believe answered my question. I do not know how to give him the proper credit but the link to his solution is here:
Database tables, one table referencing multiple unrelated tables
Thank you for your assistance Tom.
I'm still open to suggestions but taking into account the information posted in the link above I think I'm going to go the route of making multiple map tables.