MySQL Table Structure For a Rating System - mysql

I want to create a rating system that keeps track of who voted for which article, and how many votes that article received along with the value of each vote.
And I was wondering what is the best way to go about this?
Here is what I have so far below.
CREATE TABLE IF NOT EXISTS `vote` (
`counter` int(8) NOT NULL default '0',
`value` int(8) NOT NULL default '0'
)
CREATE TABLE articles (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` INT UNSIGNED NOT NULL,
`title` TEXT NOT NULL,
`summary` TEXT DEFAULT NULL,
`content` LONGTEXT NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS `users` (
`id` int(8) NOT NULL auto_increment,
`username` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
)

You need to keep track of your users somewhere
CREATE TABLE IF NOT EXISTS `vote` (
`user_id` int(8) NOT NULL,
`article_id` int(8) NOT NULL,
`value` int(8) default 0,
`datetime` timestamp DEFAULT CURRENT_TIMESTAMP
)
(Indexes are missing here and for the other tables)
If you want to unnormalize the number of votes, it should then be put in the articles table but if you do not have too much records, I would rather advice to compute it using a regular count and put the front in cache (refreshed each time there is a new vote for the article of course).

Related

Is this relationship OneToMany or ManyToMany?

I have two tables ARTICLE and FAQ ( frequently asked questions ). I'm trying to establish a relationship between these two tables but I'm confused!
What I want to achieve is that article can have many FAQ. So for this should I create a pivot table or just reference a FK in FAQ table?
What I tried but I'm not sure that the below flow is right or not?
Article table:
CREATE TABLE IF NOT EXISTS `article` (
`id` int(11) UNSIGNED NOT NULL,
`title` varchar(255) DEFAULT NULL,
`slug` varchar(255) DEFAULT NULL,
`description` longtext NOT NULL,
PRIMARY KEY (`id`)
);
FAQ Table Schema:
CREATE TABLE IF NOT EXISTS `eb_faq` (
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`faq_category_id` bigint(20) UNSIGNED DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`question` text NOT NULL,
`answer` text NOT NULL,
PRIMARY KEY (`id`)
);
Pivot:
CREATE TABLE IF NOT EXISTS `article_linked_faq` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`article_id` int(11) DEFAULT NULL,
`faq_id` int(11) DEFAULT NULL,
`order_by` int(11) DEFAULT NULL,
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
);
This schema will indeed allow an article to have multiple FAQs, but also allows one FAQ to be linked to multiple articles. If that's what you want, great! If not then I'd suggest removing the pivot table and adding article_id into eb_faq.
No, you just need to add foreign key in faq table, it will create the relationship between both tables. There is no need to create a third table
CREATE TABLE IF NOT EXISTS `eb_faq` (
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`articleId` int(11),
`faq_category_id` bigint(20) UNSIGNED DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`question` text NOT NULL,
`answer` text NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (articleId) REFERENCES article(id)
);

mysql best practice to concentrate data from multiple tables with different table design

My database contains around 20 tables that holds a user's information. For example it has
Personal : hold user personal info
Documents : uploaded files
Activities :
Etc..
Every table contains a user_Id column for wiring them together ( one to many relationship), along with different table specific columns and constraints.
My question is how should I fetch all data for a single user from all these tables ?
Currently when ever I load user , application need to do
Select * from table1 where user_Id = x;
Select * from table2 where user_Id = x;
Select * from table3 where user_Id = x;
..etc
Since I'm using php (oop) its not a bad thing as every table has its own model that retrieve it. Yet I'm worried about performance as I currently run over 20 queries every time I load page. And since these data are very dynamically updated. Caching isn't helping much.
So what is the best methodology to fix this ?
example of table structures
CREATE TABLE IF NOT EXISTS `documents` (
`id` int(10) unsigned NOT NULL,
`user_id` int(10) unsigned NOT NULL,
`collection` text NOT NULL,
`photo_date` timestamp NULL DEFAULT NULL,
`gallery` varchar(50) NOT NULL,
`cover` int(1) DEFAULT NULL,
`upload_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=8 ;
CREATE TABLE IF NOT EXISTS `problemlist` (
`id` int(10) unsigned NOT NULL,
`user_id` int(10) unsigned NOT NULL,
`visit_id` int(10) unsigned NOT NULL,
`pt_id` int(10) unsigned NOT NULL,
`problem` varchar(200) NOT NULL,
`severity` int(2) unsigned NOT NULL,
`note` text,
`solved` int(1) unsigned NOT NULL,
`datetime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=6 ;
CREATE TABLE IF NOT EXISTS `visits` (
`id` int(10) unsigned NOT NULL,
`pt_id` int(10) unsigned NOT NULL,
`user_id` int(10) unsigned NOT NULL,
`visit_date` timestamp NULL DEFAULT NULL,
`visit_end` timestamp NULL DEFAULT NULL,
`complain` varchar(250) DEFAULT NULL,
`dx` varchar(200) DEFAULT NULL,
`note` text,
`stats` enum('booked','waitting','finished','noshow','canceled','inroom') NOT NULL,
`deleted` int(1) DEFAULT NULL,
`booked_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`arrived_at` timestamp NULL DEFAULT NULL,
`started_at` timestamp NULL DEFAULT NULL,
`checkout_at` timestamp NULL DEFAULT NULL,
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=212 ;

How to optimize this mysql join on large table?

I have a project where the admin needs to create multiple newsletters with some crawled posts from the web.
I insert the posts in posts table after crawling has completed and assign them a feed_id to identify the source. this is the structure of posts table (truncated):
CREATE TABLE `posts` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`feed_id` int(11) NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT NULL,
`identifier` varchar(255) DEFAULT NULL,
`published` timestamp NULL DEFAULT NULL,
`content` longtext,
...
...
`is_unread` int(1) NOT NULL DEFAULT '1',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Every admin (user) has access to one or more "feeds". So in Newsletter creation page I want to show them a list of posts from the feeds they are allowed to see and also, I show a button to put the posts in specifict categories of that newsletter, if the user previously selected that post, I should show him that and let him remove it from the category. So I have some other tables too: newsletters, categories, newsletter_post, category_post. Here is their structures:
newsletters:
CREATE TABLE `newsletters` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT NULL,
`sent_at` timestamp NULL DEFAULT NULL,
`title` varchar(255) DEFAULT NULL,
`date` date DEFAULT NULL,
`topic_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
categories:
CREATE TABLE `categories` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`topic_id` int(11) NOT NULL,
`title` varchar(255) DEFAULT NULL,
`slug` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
newsletter_post:
CREATE TABLE `newsletter_post` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT NULL,
`newsletter_id` int(11) NOT NULL,
`post_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
category_post:
CREATE TABLE `category_post` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT NULL,
`category_id` int(11) NOT NULL,
`post_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
So I'm using this query to find posts for the allowed feeds and check the status if a post is in a specific category of this specific newsletter:
SELECT DISTINCT `posts`.`id`, `published`, `posts`.`title`, `posts`.`content`, `source_name`, `category_id`, `newsletter_id`, `link_href`, categories.title as category_title
FROM `posts`
LEFT JOIN `category_post` ON `posts`.`id` = `category_post`.`post_id`
LEFT JOIN `categories` ON `categories`.`id` = `category_post`.`category_id`
LEFT JOIN `newsletter_post` ON `posts`.`id` = `newsletter_post`.`post_id`
LEFT JOIN `newsletters` ON `newsletters`.`id` = `newsletter_post`.`newsletter_id`
WHERE `feed_id` IN (6, 7) ORDER BY `posts`.`published` DESC LIMIT 40 OFFSET 0
but the problem is this is horrible and not optimized. My posts table contains up to 50,000 rows each month, and each row with 3~10kbs of data in avg., so sometimes when I try to run the query (which is frequently run by the admin to make the newsletter, pagination etc) mysql shows this error: too much rows to join, etc. and most of the times its really slow.
and the reason I'm doing all this in one query is because I want the result to be in one json response so I can show them the user quickly without doing additional requests.
I wanna know if there is a better way to do this query or use indexes or something else.
Thanks you in advance for your help.
index your posts table on
( feed_id, published )
so the data is already optimized for your WHERE clause, and pre-sorted to help your ORDER BY.
For reading querys that have a lot of demand, InnoDB is very inefficient. I recommend you to use a NoSQL Database but if you don't want or the cost of change is too much... you can try this:
1) LIKE Sallar Kaboli told you, you have to index your tables in columns that use in JOIN querys. For example:
CREATE INDEX index1 ON newsletter_post (post_id);
2) USE only important columns for JOINS.
I mean, you have to only use the columns that use in SELECT part of query.
I hope this'd be helpful.
To complete other answers, I suggest to change this types on posts table:
1) Change feed_id to int(4). Really you have more than int(4) feeds?
2) Change is_unread to bit instead of int(1). I should say that this may not improve your given query in the question but according to the field name, the correct type is bit.
Another more improvement to this answer is that never use default int(11) for numeric or id fields, assign types more specific. Using smaller size of types will improve your indexes also. I don't think you need more than int(4) for fields id.
For example indexing and querying int(3) column is more faster than int(11).
Please create the following indexes indexes on ::
1) `post_id` in `category_post`
2) `post_id` in `newsletter_post`

Normalizing CSV to MySQL?

I'm new to the whole "normalized table" thing. I have a csv file with the contents as follows:
Cell,Width(m),Length(m),Spacing(m),VDD(V),VSS(V),Temp,Param,Value,Path,TOOL
pmos_var12,5e-03,5e-03,5e-03,0.5,0,0,delay[s],4.65e-06,/home/user/tests/run2/pspice
pmos_var12,5e-03,5e-03,5e-03,0.5,0,10,delay[s],6.2e-06,/home/user/tests/run2/pspice
pmos_var12,5e-03,5e-03,5e-03,0.5,0,25,delay[s],7.46e-06,/home/user/tests/run2/pspice
pmos_var12,5e-03,5e-03,5e-03,0.5,0,70,delay[s],8.98e-06,/home/user/tests/run2/pspice
pmos_var12,5e-03,5e-03,5e-03,0.5,0,100,delay[s],9.56e-06,/home/user/tests/run2/pspice
nmos_var12,5e-03,5e-03,5e-03,0.5,0,0,delay[s],4.65e-06,/home/user/tests/run2/pspice
nmos_var12,5e-03,5e-03,5e-03,0.5,0,10,delay[s],6.2e-06,/home/user/tests/run2/pspice
nmos_var12,5e-03,5e-03,5e-03,0.5,0,25,delay[s],7.46e-06,/home/user/tests/run2/pspice
nmos_var12,5e-03,5e-03,5e-03,0.5,0,70,delay[s],8.98e-06,/home/user/tests/run2/pspice
nmos_var12,5e-03,5e-03,5e-03,0.5,0,100,delay[s],9.56e-06,/home/user/tests/run2/pspice
I've created these tables to store the data:
CREATE TABLE `TEST__RUN_MAPPING` (
`ID` int(11) NOT NULL auto_increment,
`NAME` varchar(45) NOT NULL,
`STATUS` varchar(20) NOT NULL,
`PATH` text NOT NULL,
`TOOL` varchar(10) NOT NULL,
`COMMENTS` text NOT NULL,
PRIMARY KEY (`ID`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1
CREATE TABLE `TEST__DATA_MAPPING` (
`ID` int(11) NOT NULL auto_increment,
`NAME_ID` int(11) NOT NULL,
`CONDITIONS` int(11) NOT NULL,
`VALUE` varchar(10) NOT NULL,
PRIMARY KEY (`ID`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1
CREATE TABLE `TEST__CONDITION_MAPPING` (
`ID` int(11) NOT NULL auto_increment,
`CELL_ID` int(11) NOT NULL,
`W_ID` int(11) NOT NULL,
`L_ID` int(11) NOT NULL,
`SPACE_ID` int(11) NOT NULL,
`VDD_ID` int(11) NOT NULL,
`VSS_ID` int(11) NOT NULL,
`TEMP_ID` int(11) NOT NULL,
`PARAM_ID` int(11) NOT NULL,
PRIMARY KEY (`ID`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1
TEST__RUN_MAPPING ID maps to TEST__DATA_MAPPING NAME_ID
TEST__DATA_MAPPING CONDITIONS maps to TEST__CONDITION_MAPPING ID
All *ID in TEST_CONDITION_MAPPING map to their own table in order to have things unique.
each one of these csv files will differ in what technology used in the simulations, and I keep tabs on this with the NAME column in TEST__RUN_MAPPING. Cell, Width(m), Length(m), Spacing(m), VDD(V), VSS(V), and Temp are all values that are swept, but usually they're the same per technology so I grouped them together in a separate table.
Are there any other ways that a more experienced person could break down the relationship such that it can have optimal reading times? better normalization?
If I understand this structure correctly then I would not seperate the conditions table from the run table. Surely they would have a 1-to-1 relationship. So why not have them both in the same table. The conditions for that paticular run.
Also I would be careful about putting a 'TEXT' block inside a record. TEXT and Blobs can cause some performance problems. varchar in mysql 5 can go as high as 65,000 characters. For paths you should not need more than 1024 characters. So varchar(1024) should be enough for a path.

mySQL view for two different tables?

I've a massive problem creating a view in mySQL:
Table A in database DB1:
CREATE TABLE `a` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'internal ID',
`account` VARCHAR(10) NOT NULL DEFAULT '0',
`filename` VARCHAR(50) NOT NULL,
`filesize` BIGINT(15) NOT NULL DEFAULT '0'
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
Table B in database DB2:
CREATE TABLE `b` (
`archive_id` INT(10) UNSIGNED NULL AUTO_INCREMENT,
`archive_datetime` DATETIME,
`id` INT(10) UNSIGNED NOT NULL,
`account` VARCHAR(10) NOT NULL DEFAULT '0',
`filename` VARCHAR(50) NOT NULL,
`filesize` BIGINT(15) NOT NULL DEFAULT '0'
PRIMARY KEY (`archive_id`)
)
ENGINE=Archive
Entries from table A are automatically transfered to table B via trigger if BEFORE DELETE.
I need a view that gives me all entries from table a and table b as if they were still in one table of the same database. Columns archive_id and archive_datetime can be ignored in the view as they are not needed for this scenario.
You could use UNION:
SELECT * FROM a UNION SELECT * FROM b;
You just have to replace * with the desired table columns.
SELECT id, account, filename, filesize FROM a UNION ALL SELECT id, account, filename, filesize FROM b
Surely I must be missing something?