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.
1 Answer 1
1. Location for user passwords and others
Obviously I'm not familiar enough with your current project to be assertive, but at a first view I'm not convinced there is an advantage of locating all these data in tbl_meta
.
Maybe you consider it as a good simplification, something like "Why bother with an additional table, while I defined a perfect way to save any kind of data?".
Yes, your tbl_meta
-related mechanism is a good tool to face many different needs whithout having to create many different additional structures. But that's true just for many different things, not for the project main data.
Especially when there are a number of fields for the same table: in the case of users, you'll end with a number of tbl_meta
records (one for each field-equivalent) in place of one tbl_user
record.
At the same time, it'll much increase the engine workload.
Last but not least, it makes the entire database less directly readable: what about the equivalent of a simple display of tbl_user
through PhpMyAdmin?
So actually I'd not recommend that.
2. Queries
As a simple mention, you can suppress all occurrences of "as tbl_meta2
", which are not used.
In the other hand, I couldn't understand the way you get uid
values.
At this time, your question was just put on hold, so I can't say more for now.
3. General comments/suggestions
Readability
I'd highly recommend, first to choose self-expanatory field names where possible, then using COMMENT
s in table definition both at table and column levels.
It's especially useful in the current case, where your opted for a not obvious organization.
E. g.: you must realize that first reading table schemes, one can't at all figure out what are the p_id
and c_id
fields of tbl_map
.
And looking at the CONSTRAINT
s is of no help, since these two fields reference the same foreign field!
In this example, you might merely choose to name your fields parent_id
and chield_id
without it weighs further coding.
Consistency
In the tbl_meta
, the FK_tbl_meta_tbl_item
constraint doesn't conform to the same naming convention than those stated for tbl_item
(or vice-versa, as you want): the former looks like FK__(current-table)_(referenced-table)
while it's only FK__(referenced-table)
for the latter.
-
\$\begingroup\$ Thank you for your feedback. Still reviewing your comments. In answer to how I get uid values. It selects the uid of the last row with the same
key
value. If there is no row with the samekey
value, it selects the last inserteduid
and increments it. \$\endgroup\$Craig Harshbarger– Craig Harshbarger2016年01月31日 01:18:35 +00:00Commented Jan 31, 2016 at 1:18 -
\$\begingroup\$ I do understand that inserting a new user will have more of a tax on the database than inserting a single row into a users table. But the majoring of database interaction is adding, and updating values associated with an item. Which this database makes simple, no matter what type of item is being udpated. \$\endgroup\$Craig Harshbarger– Craig Harshbarger2016年01月31日 01:56:28 +00:00Commented Jan 31, 2016 at 1:56
-
\$\begingroup\$ @CraigHarshbarger Regarding the option to not use a dedicated users table, sure it depends on numerous factors to weigh the pros and cons. But here is again a con: if the number of users is not negligable, their simple presence in
tbl_meta
will impact performance for the rest of the data processing. In the other hand, reagardinguid
, what I espcially don't understand is that (in the common query1/2 part) you seem to select the less found value, so I can't realize its role and utility. BTW I don't understand why your question was put on hold: I found it quite acceptable. \$\endgroup\$cFreed– cFreed2016年01月31日 02:36:22 +00:00Commented Jan 31, 2016 at 2:36 -
\$\begingroup\$ what I'm understanding is that storing mass amounts of data in a single table will impact performance for the individual items. And that separating things into different tables keeps data processing to a minimum. In reguards to UID. If the value does not need to be unique, it gets its own uid (the last used uid + 1). If it does need to be unique, the uid from the last inserted row with the same key is used. If no uid is found, a new uid is inserted (last uid + 1). Hope that makes sense. \$\endgroup\$Craig Harshbarger– Craig Harshbarger2016年01月31日 03:59:23 +00:00Commented Jan 31, 2016 at 3:59
'my_key'
,tbl_meta
,tbl_item
, etc. Is this really what appears in your code, or did you "sanitize" it before posting? \$\endgroup\$