I have a database containing over 1,000 item information and I am now developing a system that will have this check the API source via a regular Cron Job adding new entries as they come. Usually, but not always the case, when a new item is released, it will have limited information, eg; Image and name only, more information like description can sometimes be initially withheld.
With this system, I am creating a bulletin to let everyone know new items have been released, so like most announcements, they get submitted to a database, however instead of submitting static content to the database for the bulletin, is it possible to submit something which will be executed upon the person loading that page and that bulletin data is firstly obtained then the secondary code within run?
, For example, within the database could read something like the following
<p>Today new items were released!</p>
<?php $item_ids = "545, 546, 547, 548"; ?>
And then on the page, it will fetch the latest known information from the other database table for items "545, 546, 547, 548"
Therefore, there would be no need to go back and edit any past entries, this page would stay somewhat up-to-date dynamically.
Typically you would do something like have a date field on your items, so you can show which items were released on a given date. Or if you need to have the items associated with some sort of announcement record, create a lookup table that joins your items and announcements. Do not insert executable code in the DB and then pull it out and execute it.
CREATE TABLE `announcements` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`publish_date` DATETIME NOT NULL,
`content` text,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE `items` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`title` VARCHAR(128) NOT NULL,
`description` text,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE `announcement_item_lkp` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`announcement_id` int(11) unsigned NOT NULL,
`item_id` int(11) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `announcement_item_lkp_uk1` (`announcement_id`,`item_id`),
KEY `announcement_item_lkp_fk_1` (`announcement_id`),
KEY `announcement_item_lkp_fk_2` (`item_id`),
CONSTRAINT `announcement_item_lkp_fk_1` FOREIGN KEY (`announcement_id`) REFERENCES `announcements` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `announcement_item_lkp_fk_2` FOREIGN KEY (`item_id`) REFERENCES `items` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
With the announcement_item_lkp table, you can associate as many items to your announcement as you like. And since you have cascading deletes, if an item gets deletes, its lookup records are deleted as well, so you don't have to worry about orphaned references in your announcements, like you would it you just stuff a string of IDs somewhere.
You're already using a relational database, let it do its job.
Related
create table Board (
boardID char(30) not null,
readLevel tinyint not null,
writeLevel tinyint not null,
PRIMARY KEY (boardID) ) engine=InnoDB character set=utf8;
create table Post (
postID int not null AUTO_INCREMENT,
title char(50) not null,
content TEXT not null,
writeDate date not null,
readCount int not null,
PRIMARY KEY (postID)) engine=InnoDB character set=utf8;
create table Save_Board_Post(
boardID char(30) not null,
postID int not null,
FOREIGN KEY (boardID) REFERENCES Board(boardID) ON UPDATE CASCADE,
FOREIGN KEY (postID) REFERENCES Post(postID) ON UPDATE CASCADE ) engine=InnoDB character set=utf8;
insert into Board (boardID, readLevel, writeLevel) values ('testBoard', 0, 0);
insert into Post (title, content, writeDate, readCount) values ('testPost1', 'test', CURRENT_TIMESTAMP() ,0);
select * from Board where boardID='testBoard';
select * from Post where tile='testPost1';
select * from Save_Board_Post where boardID='testBoard';
I'm rookie in sql. and I'm not native about English.
So, Please forgive my English skills.
Here's my mysql code.
Last five lines are for test. And select from Board and Post is working fine.
But
select * from Save_Board_Post where boardID= 'testBoard';
It doesn't work. This code has no error. but there is no output result.
I guess it means no data in Save_Board_Post table.
I thought REFERENCES command is automatically creation data when insert parent table.
If it does not, please let me know how to automatically creation in relation data.
No, that's not what REFERENCES does. All that your REFERENCES constraints mean is that every row that is inserted (manually) into the Save_Board_Post table must have a boardID and a postID that exist in the Board and Post tables. Nothing is inserted into that table automatically.
If you are trying to represent what board a post is in, the appropriate way to do this would be to make the board ID be a property of the post, e.g.
CREATE TABLE Post (
postID INTEGER NOT NULL AUTO_INCREMENT,
boardID CHAR(30) NOT NULL,
...
FOREIGN KEY (boardID) REFERENCES Board(boardID)
);
rather than having an entirely separate table just for that data.
You cannot automatically insert data in child table by inserting in the parent table. You got it right when you said it failed because there was no data in the table. Referential integrity exist to remove redundancy in a database. I dont think there is a way for you to automatically insert into the child table by inserting into a parent table. you have to do it manually.
I have been over this issue for the last year or so, changing what I am doing and trying different things. The issue is to do with the schema so I can still order nicely in player/clan ladders but if we want to add a stat later it won't lock our table changing every row due to one stat per column.
I see two options for how to do this but both don't seem to be right. One is one stat per column. There would be 4 tables, user_stat_summary (for basic stats shown on ladders), user_stat_beast (teams are human vs beast), user_stat_human and user_stat_overall. Stats are shown everywhere from the last 30 days. A cron job will take any dated stats by getting a query on matches that happened after the 30 days and taking away those stats from the 3 main tables and putting them into the overall one. Matches will have blobs for the stats each player got for that match. The issue I see here is when we have a lot of rows that we can't easily add more stats when say the game changes a little. What I was thinking was a extra_stats blob column on each table and if we add new stats they simply aren't going to be sortable on the ladders.
The other option is an EAV model which is what I have been playing around with but can't seem to get it right. I would be getting many more rows per query and then grouping them into users and the order would work for the most part but I couldn't get limits right for pagination since there was generally an unknown number of rows selected.
What I was thinking is the EAV model with a table that stores ranks per stats which could be used for ordering. So the EAV tables are currently as follows...
CREATE TABLE `user_stat` (
`user_id` int(10) unsigned NOT NULL,
`stat_id` varchar(50) NOT NULL,
`value` int(10) unsigned NOT NULL,
PRIMARY KEY (`user_id`,`stat_id`),
CONSTRAINT `user` FOREIGN KEY (`user_id`) REFERENCES `xf_user` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `user_human_stat` (
`user_id` int(10) unsigned NOT NULL,
`stat_id` varchar(50) NOT NULL,
`value` int(10) unsigned NOT NULL,
PRIMARY KEY (`user_id`,`stat_id`),
CONSTRAINT `human_user` FOREIGN KEY (`user_id`) REFERENCES `xf_user` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `user_beast_stat` (
`user_id` int(10) unsigned NOT NULL,
`stat_id` varchar(50) NOT NULL,
`value` int(10) unsigned NOT NULL,
PRIMARY KEY (`user_id`,`stat_id`),
CONSTRAINT `beast_user` FOREIGN KEY (`user_id`) REFERENCES `xf_user` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `user_stat_overall` (
`user_id` int(10) unsigned NOT NULL,
`human` blob NOT NULL,
`beast` blob NOT NULL,
`total` blob NOT NULL,
PRIMARY KEY (`user_id`),
CONSTRAINT `user_overall` FOREIGN KEY (`user_id`) REFERENCES `xf_user` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
So I was thinking I could add a user_stat_rank table which would be user_id, stat_id, rank. Then say I want to get the first page of the ladder ordered by the 'kills' stat I could get all the user_ids order by rank where stat_id is kills. Then make a second query to populate all the users stats.
After writing all this out it seems like it would work fine but I might not be seeing something. I also understand this question is all over the place so if you would like me to edit in details at places just say so.
For sake of managibility, I would stick to adding a column for every stat. In the long run, this will probably be the easiest way to manage it without ending up in a corner due to the limitations that for instance the EAV model would impose on you.
If you're worried about the stats table growing too large, you could consider implementing some form of table partitioning where you regularly move the data older than 4 weeks to (a) historic table(s). The historic table(s) can be indexed to the extreme, as they won't require constant updating.
I am in the process of writing a web-based quiz application using PHP and MySQL. I don't want to bore you with the details of it particularly, so here's what (I think) you need to know.
Questions are all multiple choice, and can be stored in a simple table with a few columns:
ID: The question number (primary index)
Category: The category this question falls under (e.g. animals,
vegetables, minerals)
Text: The question stem (e.g. What is 1+1?)
Answer1: A possible answer (e.g. 2)
Answer2: A possible answer (e.g. 3)
Answer3: A possible answer (e.g. 4)
CorrectAnswer: The correct answer to the question (either 1, 2 or 3 (in this case 1))
Users can sign up by creating a username and password, and then attempt questions from categories.
The problem is that the questions I'm writing are designed to be attempted more than once. However, users need to be given detailed feedback on their progress. The FIRST attempt at a question matters, and contributes to a user's overall 'questions answered first time' score. I therefore need to keep track of how many times a question has been attempted.
Since the application is designed to be flexible, I would like to have support for many hundreds of users attempting many thousands of questions. Thus, trying to integrate this information into the user table or questions table seems to be impossible. The way I would like to approach this problem is to create a new table for each user when they have signed up, with various columns.
Table Name: A user's individual table (e.g. TableForUser51204)
QuestionID: The ID of a question that the user has attempted.
CorrectFirstTime: A boolean value stating whether or not the
question was answered correctly first time.
Correct: The number of times the question has been answered
correctly.
Incorrect: The number of times the question has been answered
incorrectly.
So I guess what I would like to ask is whether or not organising the database in this manner is a wise thing to do. Is there a better approach rather than creating a new table for each user? How much would this hinder the performance if there are say 500 users and 2000 questions?
Thanks.
You don't want to be creating a new table per user. Instead, modify your database structure.
Normally, you'd have a table for questions, a table for options (with maybe a boolean column to indicate if it's the correct answer), a users table, and a join table on users and options to store users' responses. A sample schema:
CREATE TABLE `options` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`question_id` int(10) unsigned NOT NULL,
`text` varchar(255) NOT NULL,
`correct` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
KEY `question_id` (`question_id`)
) TYPE=InnoDB;
CREATE TABLE `options_users` (
`option_id` int(10) unsigned NOT NULL,
`user_id` int(10) unsigned NOT NULL,
`created` timestamp NOT NULL,
KEY `option_id` (`option_id`),
KEY `user_id` (`user_id`)
) TYPE=InnoDB;
CREATE TABLE `questions` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`question` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`,`question`)
) TYPE=InnoDB;
CREATE TABLE `users` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(60) NOT NULL,
`password` char(40) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) TYPE=InnoDB;
ALTER TABLE `options`
ADD CONSTRAINT `options_ibfk_1` FOREIGN KEY (`question_id`) REFERENCES `questions` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE `options_users`
ADD CONSTRAINT `options_users_ibfk_2` FOREIGN KEY (`option_id`) REFERENCES `options` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `options_users_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
This links options to questions, and users' responses to options. I've also added a created column to the options_users table so you can see when a user answered the question and track their progress over time.
Scenario
Say I have a list of voucher codes that I am giving away, I need to ensure that if two persons place an order at the exact same time, that they do not get the same voucher.
Tables
CREATE TABLE IF NOT EXISTS `order` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`voucher_id` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `voucher_id` (`voucher_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
ALTER TABLE `order` ADD CONSTRAINT `order_fk` FOREIGN KEY (`voucher_id`) REFERENCES `voucher` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
CREATE TABLE IF NOT EXISTS `voucher` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`code` varchar(10) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Sample data
INSERT INTO `voucher` (`code`) VALUES ('A'), ('B'), ('C');
Sample Query
SELECT #voucher_id := v.id FROM `voucher` v LEFT JOIN `order` o ON o.voucher_id = v.id WHERE o.id IS NULL;
INSERT INTO `order` (`voucher_id`) VALUES (#voucher_id);
Question
I believe the UNIQUE KEY on voucher_id in the order table will prevent two orders having the same voucher_id, giving an error / throwing an exception if the same voucher id is inserted twice. This would require a while loop to retry upon failure.
The alternative is read locking the vouchers table before the SELECT and releasing that lock after the INSERT, ensuring the same voucher isn't picked twice.
My question is therefore:
Which is faster?
A while loop in PHP code.
Read locking the vouchers table.
Is there another way?
Edits
ALTER TABLEorderCHANGEvoucher_idvoucher_idBIGINT(20) UNSIGNED NOT NULL
will cause the INSERT to fail if #voucher_id is null (as desired, as there would be no vouchers left).
The "correct" and by that I mean best way to do what you're looking to do is to generate the voucher at the time you place the order. Look at the documentation for the sha1() function in php. You can seed it with unique information to prevent duplicates and use that for your voucher along with an auto_increment field for the unique ID.
When the order is placed, PHP generates a new voucher, saves it to the database, and sends it to the user. This way you're only storing valid vouchers and you're also preventing duplicates from being created.
You can use START TRANSACTION, COMMIT, and ROLLBACK to prevent race conditions in your SQL. http://dev.mysql.com/doc/refman/4.1/en/commit.html
In your case, I would just put your transactions into a critical area bounded by these tokens.
I have inherited a PHP project and the client is wanting to add some functionality to their CMS, basically the CMS allows them to create some news, all the news starts with the same content, and that is saved in one table, the actually news headline and articles are saved in another table, and the images for the news are saved in another, basically if the base row for the news is deleted I need all the related rows to be deleted, the database is not setup to work with foreign keys so I cannot use cascade deletion, so how can I delete the all the content I need to, when I only what the ID of the base news row is?
Any help would be very helpful I am sorry I cannot give you much more help, here is this the original SQL of tables scheme if that helps?
--
-- Table structure for table `mailers`
--
CREATE TABLE IF NOT EXISTS `mailers` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`mailer_title` varchar(150) NOT NULL,
`mailer_header` varchar(60) NOT NULL,
`mailer_type` enum('single','multi') NOT NULL,
`introduction` varchar(80) NOT NULL,
`status` enum('live','dead','draft') NOT NULL,
`flag` enum('sent','unsent') NOT NULL,
`date_mailer_created` int(11) NOT NULL,
`date_mailer_updated` int(10) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=13 ;
-- --------------------------------------------------------
--
-- Table structure for table `mailer_content`
--
CREATE TABLE IF NOT EXISTS `mailer_content` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`headline` varchar(60) NOT NULL,
`content` text NOT NULL,
`mailer_id` int(11) NOT NULL,
`position` enum('left','right','centre') DEFAULT NULL,
`created_at` int(10) NOT NULL,
`updated_at` int(10) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=18 ;
-- --------------------------------------------------------
--
-- Table structure for table `mailer_images`
--
CREATE TABLE IF NOT EXISTS `mailer_images` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(150) NOT NULL,
`filename` varchar(150) NOT NULL,
`mailer_id` int(11) NOT NULL,
`content_id` int(11) DEFAULT NULL,
`date_created` int(10) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=15 ;
It is worth noting that the schema cannot be changed nor can I change to the DB to MYISAM so that I can use foreign keys.
Add foreign key to table mailer_content
FOREIGN KEY (mailer_id)
REFERENCES mailers(id)
ON DELETE CASCADE
Add foreign key to table mailer_images
FOREIGN KEY (content_id)
REFERENCES mailer_content(id)
ON DELETE CASCADE
http://dev.mysql.com/doc/refman/5.1/en/innodb-foreign-key-constraints.html
It is worth noting that the schema cannot be changed nor can I change to the DB to MYISAM so that I can use foreign keys.
Why can't the schema be changed? You designed the app, didn't you? Even if you didn't, adding the proper keys is just a matter of adding the right indexes and then altering the right columns. #Michael Pakhantosv's answer has what looks to be the right bits of SQL.
Further, it's InnoDB that does foreign keys, not MyISAM. You're fine there already.
If you could change the schema, making the appropriate IDs actual, real Foreign Keys and using ON DELETE CASCADE would work. Or maybe triggers. But that's just asking for it.
Now, for some reason, ON DELETE CASCADE isn't liked very much around here. I disagree with other people's reasons for not liking it, but I don't disagree with their sentiment. Unless your application was designed to grok ON DELETE CASCADE, you're in for a world of trouble.
But, given your requirement...
basically if the base row for the news is deleted I need all the related rows to be deleted
... that's asking for ON DELETE CASCADE.
So, this might come as a shock, but if you can't modify the database, you'll just have to do your work in the code. I'd imagine that deleting a news article happens in only one place in your code, right? If not, it'd better. Fix that first. Then just make sure you delete all the proper rows in an appropriate order. And then document it!
If you can not change the schema then triggers are not an option.
InnoDB supports transactions, so deleting from two tables should not be an issue, what exactly is your problem?
P.S. It would be worth noting which version of the server are you using.