I'm playing around with database designs to use for a new system. This system at its core revolves around various types of items (users, accounts, images), their meta (names, titles, file sizes), and item's relation to each other (users mapped to accounts, images mapped to users).
Comments
- I created the
uid
solution so I could store all item meta in the same table, rather than having a unique table for each item type with its own custom columns. This way I have one item table, and one meta.
Questions
- Since
tbl_item
doesn't have any columns besides type, things like user passwords and usernames/emails would be stored intbl_meta
. I have never seen a database where passwords (hashed of course) and usernames were not stored in their own table. Is there a reason they shouldn't betbl_meta
? - I'm not a query expert, and I wonder if either of my queries below have side effects or are written poorly. Thoughts/suggestions?
- General comments/suggestions on the database as a whole. I'm fairly new to database design so any instruction will be helpful.
Database Scheme
I have three tables: tbl_item, tbl_meta, and tbl_map. Below is the scheme for each table.
CREATE TABLE IF NOT EXISTS `tbl_item` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`type` varchar(50) NOT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE IF NOT EXISTS `tbl_map` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`p_id` int(11) NOT NULL,
`c_id` int(11) NOT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `FK__tbl_item` (`p_id`),
KEY `FK__tbl_item_2` (`c_id`),
CONSTRAINT `FK__tbl_item` FOREIGN KEY (`p_id`) REFERENCES `tbl_item` (`id`),
CONSTRAINT `FK__tbl_item_2` FOREIGN KEY (`c_id`) REFERENCES `tbl_item` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE IF NOT EXISTS `tbl_meta` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`item_id` int(11) NOT NULL,
`key` varchar(255) NOT NULL,
`value` varchar(255) NOT NULL,
`uid` int(11) NOT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `item_id_key` (`item_id`,`key`),
UNIQUE KEY `value_uid` (`value`,`uid`),
CONSTRAINT `FK_tbl_meta_tbl_item` FOREIGN KEY (`item_id`) REFERENCES `tbl_item` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
tbl_item
and tbl_map
are pretty straight forward. An item has a unique id and a type. and tbl_map
creates parent child relations. tbl_meta
contains key/value pairs that assigned to an item.
Queries
The column uid
(unique id) is what makes this table useful. The unique constraint on value
and uid
is so that I can set a key to have unique values across all items. I have two queries for for updating this table.
This one is used when inserting meta that is not unique:
INSERT INTO tbl_meta (`item_id`, `key`, `value`, `uid`) VALUES (1, 'my_key', 'my_value', (SELECT (`uid` + 1) as uid FROM `tbl_meta` as `tbl_meta2` ORDER BY `id` DESC LIMIT 1) ) ON DUPLICATE KEY UPDATE `value`=VALUES(value)
This one is used for inserting meta that needs to be unique across all items. It insures that that
key
has the sameuid
, and the unique constraint onvalue
anduid
makes sure that no duplicate values are entered.INSERT INTO tbl_meta (`item_id`, `key`, `value`, `uid`) VALUES (1, 'my_key', 'my_value', (SELECT IFNULL ( ( SELECT uid FROM `tbl_meta` as `tbl_meta2` WHERE `key`='my_key' ORDER BY uid DESC LIMIT 1 ), (SELECT (`uid` + 1) as uid FROM `tbl_meta` as `tbl_meta2` ORDER BY `id` DESC LIMIT 1) ) as uid) ) ON DUPLICATE KEY UPDATE `value`=VALUES(value)
Examples
For these examples, assume an account already exists in the database.
Create a new user and add meta to that user.
A user is a type of item. So insert a row into
tbl_item
withtype='user'
then insert the users password, email, and name intotbl_meta
. password, and name are not unique so those values would be inserted using query 1 above. Email needs to be unique across all users (because that's what they log in with), so email would be inserted using the second query. That query will make sure that no item has the same value for the keyemail
.Add a profile photo to a user.
An image is a type of item. So insert a row into
tbl_item
withtype='image'
then insert the images title and url intotbl_meta
. Title and url are not unique so use query 1. Then add the metaprofile_photo='img_id'
whereimg_id
is the id of the image we just inserted into the database.Map the user to an account
An account is a type of item. An account can have multiple users mapped to it. To map our new user to an account, insert a row into
tbl_map
wherep_id
(parent item's id) is the account's ID andc_id
(child item's id) is the id of the user.
Result
We started with an account. Now we have a user mapped to that account with meta (password, email, name, profile_photo). As well as an image and its title and URL.
'my_key'
,tbl_meta
,tbl_item
, etc. Is this really what appears in your code, or did you "sanitize" it before posting? \$\endgroup\$