I'm creating a database with various tables. Let's take the user table, for example. It has fields such as marital status and system role. Each of those fields has predefined options. Does it make sense to create two new tables for each of those fields, so then when a user is added to the system, choices can be made available for selection e.g. single, married, divorced? It seems a bit of an overkill in terms of one extra query. Is this the best way to do it or do I have other options?
I would definitely create separate tables to store the available options for these various columns. This is a good thing to do as far as normalization goes, and will also save you headaches down the road when you need to add, remove, disable or change any of the options. Also, if don't create a separate table and populate the values directly in the user table, you may end up having to do something like select distinct RelationshipStatus from User to get the available options, which is not as performant as just selecting 10 or however many values from a separate table.
As someone commented, over-normalization can sometimes be a pain, but I've found that not normalizing something as a way to do a quick work-around almost always comes back to haunt you.
User
----
ID
RelationshipStatusId
...other columns
RelationshipStatus
------------------
ID
Value
Description
You can use the ENUM datatype in MySQL to better take care of this scenario. Storing such options in a seperate table is a bad idea until you have a lot of them..
mysql> DESC Classes;
+-------+-----------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-----------------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| dept | char(4) | NO | | NULL | |
| level | enum('Upper','Lower') | NO | | NULL | |
+-------+-----------------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
mysql> SELECT * FROM Classes;
+----+------+-------+
| id | dept | level |
+----+------+-------+
| 10 | MATH | |
+----+------+-------+
1 row in set (0.00 sec)
mysql> INSERT INTO Classes VALUES (11, 'ENG', 'Upper')
-> ;
Query OK, 1 row affected (0.00 sec)
mysql> SELECT * FROM Classes;
+----+------+-------+
| id | dept | level |
+----+------+-------+
| 10 | MATH | |
| 11 | ENG | Upper |
+----+------+-------+
2 rows in set (0.00 sec)
For design's sake, create another table (what you don't want to do) with a proper PK. This will have the extra benefit of saving space, because imagine having 10000 registers with the word "married" on them.
Also, an alternative is using in your application a "dictionary", storing in a structure and Id and the value, like this:
Id Marital Status
1 Married
2 Single
.. ......
The same table, but not in a database but in the application, hardcoded, serialized or in an external file.
It depends on the size of the rows also. It would be better option to split the tables in to multiple in terms of speed.
For ex. you can keep the frequent used columns in user table and all other informations/optional ones in separate tables. In this case you need take care while displaying the data also.
I guess, there is no need for over-normalization as it will trouble you in writing queries. You need to take care of too many joins.
If your predefined conditions for Marital Status are: Married, Single and Divorced, I would just store a single character like: M, S and D and would provide these options in a DropDown with fixed values.
I think Marital Status has no further possibilities unless you think of something like:
Want to be Divorced
Married but living alone.
For user role also, I would do something like that:
A - Administrator
P - Power User
R - Restricted User
G - Guest
In case you need something more elaborate, I won't create further tables.
Related
I have two tables in my database for users:
users
id|username|password|registration_date|
1 |bruce |****** |2017-03-04 |
2 |jason |***** |2017-03-06 |
3 |brad |******* |2017-03-12 |
google_users
id|username|password|registration_date|
1 |jimmy |***** |2017-03-05 |
2 |wade |******* |2017-03-08 |
I want to apply the same AUTO_INCREMENT index for both tables when a new user signs up with google.
Something like this:
users
id|username|password|registration_date|
1 |bruce |****** |2017-03-04 |
3 |jason |***** |2017-03-06 |
5 |brad |******* |2017-03-12 |
google_users
id|username|password|registration_date|
2 |jimmy |***** |2017-03-05 |
4 |wade |******* |2017-03-08 |
How can I do this?
I'm going to vote against this table design and recommend that that you just maintain a single users table:
users (id, username, password, registration_date)
To keep track of the method by which they signed up, you may create a second table:
accounts (id, user_id, type_id)
The type_id can point to yet a third table, indicating whether Google or something else were the source of the signup. Note also that the accounts table can have a user with more than one signup relationship, if you would need that.
The basic idea is that maintaining an auto increment column across two tables will either be impossible, or at the very least ugly. This is not a feature which is usually supported/needed in SQL. So if you find yourself having this need, you should first look closely at your database design.
Not suggested, but if you really want it to happen this way:
You can try to implement this setting in MySQL:
mysql> SHOW VARIABLES LIKE 'auto_inc%';
+--------------------------+-------+
| Variable_name | Value |
+--------------------------+-------+
| auto_increment_increment | 2 |
and then for your tables, you can do:
ALTER TABLE users AUTO_INCREMENT = 1;
ALTER TABLE google_users AUTO_INCREMENT = 2;
So, now, your auto-increment will be incremented by 2 and it gives you the expected result.
But as I said, this will impact your whole DB. All your increments will be done by 2 instead of 1.
I have a situation where I have a table, for example:
| id | type |
------------------
| 0 | Complete |
| 1 | Zone |
Now, I always want my database to be populated with these values, but additionally users should be able to CRUD their own custom types beyond these. For example, a user might decide they want a "Partial Zone" type:
| id | type |
---------------------
| 0 | Complete |
| 1 | Zone |
| 2 | Partial Zone |
This is all fine. But I don't want anyone to be able to delete/modify the first and second rows.
This seems like it should be so simple, but is there a common strategy for handling this case that ensures that these rows go unaffected? Should I put a lock column on the table and only lock these two values when I initially populate the database on application setup? Is there something much more obvious and elegant that I am missing?
Unless I'm missing something, you should be able to just add a third column to your table for the user ID/owner of the record. For the Complete and Zone records, the owner could be e.g. user 0, which would correspond to an admin. In your deletion logic, just check the ID column and do not allow admin records to be deleted by anyone from the application.
If this won't work, you could also consider having two tables, one for system records which cannot be deleted, and another one for user created records. You would have to possibly always take a union of the two tables when you query.
I have put a lot of effort into my database design, but I think I am
now realizing I made a major mistake.
Background: (Skip to 'Problem' if you don't need background.)
The DB supports a custom CMS layer for a website template. Users of the
template are limited to turning pages on and off, but not creating
their own 'new' pages. Further, many elements are non editable.
Therefore, if a page has a piece of text I want them to be able to edit,
I would have 'manually' assigned a static ID to it:
<h2><%= CMS.getDataItemByID(123456) %></h2>
Note: The scripting language is not relevant to this question, but the design forces
each table to have unique column names. Hence the convention of 'TableNameSingular_id'
for the primary key etc.
The scripting language would do a lookup on these tables to find the string.
mysql> SELECT * FROM CMSData WHERE CMSData_data_id = 123456;
+------------+-----------------+-----------------------------+
| CMSData_id | CMSData_data_id | CMSData_CMSDataType_type_id |
+------------+-----------------+-----------------------------+
| 1 | 123456 | 1 |
+------------+-----------------+-----------------------------+
mysql> SELECT * FROM CMSDataTypes WHERE CMSDataType_type_id = 1;
+----------------+---------------------+-----------------------+------------------------+
| CMSDataType_id | CMSDataType_type_id | CMSDataType_type_name | CMSDataType_table_name |
+----------------+---------------------+-----------------------+------------------------+
| 1 | 1 | String | CMSStrings |
+----------------+---------------------+-----------------------+------------------------+
mysql> SELECT * FROM CMSStrings WHERE CMSString_CMSData_data_id=123456;
+--------------+---------------------------+----------------------------------+
| CMSString_id | CMSString_CMSData_data_id | CMSString_string |
+--------------+--------------------------------------------------------------+
| 1 | 123456 | The answer to the universe is 42.|
+--------------+---------------------------+----------------------------------+
The rendered text would then be:
<h2>The answer to the universe is 42.</h2>
This works great for 'static' elements, such as the example above. I used the exact same
method for other data types such as file specifications, EMail Addresses, Dates, etc.
However, it fails for when I want to allow the User to dynamically generate content.
For example, there is an 'Events' page and they will be dynamically created by the
User by clicking 'Add Event' or 'Delete Event'.
An Event table will use keys to reference other tables with the following data items:
Data Item: Table:
--------------------------------------------------
Date CMSDates
Title CMSStrings (As show above)
Description CMSTexts (MySQL TEXT data type.)
--------------------------------------------------
Problem:
That means, each time an Event is created, I need to create the
following rows in the CMSData table;
+------------+-----------------+-----------------------------+
| CMSData_id | CMSData_data_id | CMSData_CMSDataType_type_id |
+------------+-----------------+-----------------------------+
| x | y | 6 | (Event)
| x+1 | y+1 | 5 | (Date)
| x+2 | y+2 | 1 | (Title)
| x+3 | y+3 | 3 | (Description)
+------------+-----------------+-----------------------------+
But, there is the problem. In MySQL, you can have only 1 AUTO INCREMENT field.
If I query for the highest value of CMSData_data_id and just add 1 to it, there
is a chance there is a race condition, and someone else grabs it first.
How is this issue typically resolved - or avoided in the first place?
Thanks,
Eric
The id should be meaningless, except to be unique. Your design should work no matter if the block of 4 ids is contiguous or not.
Redesign your implementation to add the parts separately, not as a block of 4. Doing so should simplify things overall, and improve your scalability.
What about locking the table before writing into it? This way, when you are inserting a row in the CMSData table, you can get the last id.
Other suggestion would be to not have an incremented id, but a unique generated one, like a guid or so.
Lock Tables
Is it possible to add a database constraint to limit a row to have a single value in one of two columns, never more and never less? Let me illustrate:
Sales Order Table
---------------------------------
id | person_id | company_id |
Rows for this would look like:
id | person_id | company_id |
---|-----------|------------|
1 | 1 | null |
2 | 2 | null |
3 | null | 1 |
4 | null | 2 |
In this illustration, the source of the sales order is either a person or a company. It is one or the other, no more or less. My question is: is there a way to constrain the database so that 1) both fields can't be null and 2) both fields can't be not-null? i.e., one has to be null and one has to be not-null...
I know the initial reaction from some may be to combine the two tables (person, company) into one customer table. But, the example I'm giving is just a very simple example. In my application the two fields I'm working with cannot be combined into one.
The DBMS I'm working with is MySQL.
I hope the question makes sense. Thank you in advance for your help!
This may come as a shock...
mysql doesn't support CHECKconstraints. It allows you to define them, but it totally ignores them.
They are allowed in the syntax only to provide compatibility with other database's syntax.
You could use a trigger on update/insert, and use SIGNAL to raise an exception.
I'm not entirely sure how to ask this question, so I'll lead by providing an example table and an example output and then follow up with a more thorough explanation of what I'm attempting to accomplish.
Imagine that I have two tables. In the first is a list of companies. Some of these companies have duplicate entries due to being imported and continuously updated from different sources. For example, the company table may look something like this:
| rawName | strippedName |
| Kohl's | kohls |
| kohls.com | kohls |
| kohls Corporation | kohls |
So in this situation, we have information that has come in from three different sources. In an attempt to allow my program to understand that each of these sources are all the same store, I created the stripped name column (which I also use for creating URL's and whatnot).
In the second table, we have information about deals, coupons, shipping offers, etc. However, since these come in from their various sources, the end up with the three different rawNames that we identified above. For example, the second table might look something like this:
| merchantName | dealInformation |
| kohls.com | 10% off everything... |
| kohl's | Free shipping on... |
| kohls corporation | 1 Day Flash Sale! |
| kohls.com | Buy one get one... |
So here we have four entries that are all from the same company. However, when a user on the site visits the listing for Kohls, I want it to display all the entries from each source.
Here is what I currently have, but it doesn't seem to be doing the trick. This seems to only work if I set the LIMIT in that sub-query to 1 so that it only brings back one of the rawNames. I need it to match against all of the rawNames.
SELECT * FROM table2
WHERE merchantName = (SELECT rawName FROM table1 WHERE strippedName = '".$strippedName."')
The quickest fix is to replace your mercahantName = with merchantName IN
SELECT * FROM table2
WHERE merchantName IN (SELECT rawName FROM table1 WHERE strippedName = '".$strippedName."')
The = operator needs to have exactly one value on each side - the IN keyword matches a value against multiple values.