String as primary KEY? - mysql

I have this database structure
CREATE TABLE `productinfo` (
`ProductID` int(11) NOT NULL AUTO_INCREMENT,
`ProductName` varchar(255) NOT NULL,
`ProductImage` varchar(255) NOT NULL,
`CategoryID` int(11) NOT NULL,
`SubCategoryID` int(11) NOT NULL,
`ProductBrief` varchar(255) NOT NULL,
`Features` text NOT NULL,
`Specifications` text NOT NULL,
`Reviews` text NOT NULL,
`Price` varchar(255) NOT NULL,
`Status` tinyint(4) NOT NULL,
PRIMARY KEY (`ProductID`)
) ENGINE=MyISAM AUTO_INCREMENT=12 DEFAULT CHARSET=latin1;
I now I need to turn ProductID, CategoryID, and SubCategoryID into a string like Ps-5678 for the part number. ProductID is the primary key so how do i change the structure of the database. CategoryID, and SubCategoryID are primary keys in other tables so how do i handle this..is it as easy as turning
`ProductID` int(11) NOT NULL AUTO_INCREMENT
into a string..and getting rid of
PRIMARY KEY (`ProductID`)
ideas, suggestions anyone

Primary keys are for the database.
Display names are for end users.
Do not confuse one with another! Don't make a primary key out of something that has a meaning. You will regret it sooner or later.
Having a surrogate key / identity / autonumber as a primary key is a very good idea and is used widely in database design.
You can add a column or even a DERIVED COLUMN and add a unique constraint on it.

I believe it should be that easy.
however you need to determine what string type you want to use
http://dev.mysql.com/doc/refman/5.1/en/string-types.html

Your requirement is unclear. How do you get "PS-5678" fro 3 int columns ? There is only 2 components in your example.
Do you just need to CONVERT the 3 INTs to a single CHAR() string ?
If so, the database is fine, no need to change the table at all !?!?! The three components are already available, correctly seperated, as distinct columns. What you are looking for is merely DISPLAYING the three components as a single string.

It's not clear to me from your question just what you do to product, category, and subcategory to make your part number. I'll assume for the sake of argument that you are concatenating them together, like product 123, category 456, subcategory 789 gives part number 123-456-789 or some such.
I like to use natural identifiers as primary keys whenever practical. But "whenever practical" can be a serious constraint. If your natural identifier is derived by somehow combining three other fields, you have four choices:
Make the primary key be the combination of these three fields. This tends to be a pain. All your joins then must match on three fields, searches must test three fields, etc.
Create a new field that is the concatenation of the three fields, and use this as the priamry key. Then whenever one of the "basic" fields changes, also change this concatenated field. This is a very, very bad idea. Don't do it. It's redundant data, with all the bad things that come from redundant data.
Replace the three separate fields with one combined field. This is worse than #2. Now when you need the individual values, you have to take the field apart.
Give up and create a synthetic key, like a serial number. Use this as the primary key, and then just use the natural key for display purposes. If my natural key requires concatenating or otherwise manipulating three fields, I tend to go with this option.

Related

Referring MySQL ENUM in another table

I have a table SkillLevel created as
CREATE TABLE `sklllevel` (
`Name` varchar(20) NOT NULL,
`level` enum('No Experience','Beginner','Expert','Advisor') DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
With Values
INSERT INTO test.sklllevel (name,level) values ('No Experience','No Experience'),('Beginner','Beginner'),('Expert','Expert'),('Advisor','Advisor');
I want to refer SkillLevel.Level with testSkill.tkSkill in another table created as:
CREATE TABLE `testskill` (
`pkid` int(11) NOT NULL,
`name` varchar(45) DEFAULT NULL,
`tkSkill` tinyint(4) DEFAULT NULL,
PRIMARY KEY (`pkid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DO I need to have tkSkill as ENUM with same set of Values to set a foreign key? What is the best practise here?
Short answer: enums are stored as a number, so technically, you could join them with tkSkill as a tinyint.
To use it as a foreign key, you indeed need to have both tkSkill and level to be the same enum - but you need level to be a unique column to qualify as a foreign key, so add unique to it (to be really precise: for InnoDB, you can have non-unique foreign keys if you manually create the index, but non-unique foreign keys are generally a bad idea). But you should think about what your key in sklllevel should be, since now it looks as if you want Name to be the key.
And independently from having it as key you should define tkSkill as (the same) enum to make sure they both mean the same if you at one point would like to change the enums (what is a bad idea!) and e.g. add another skilllevel; or if you want to "read" (directly understand) the value when you select directly from the table testskill without the need to join sklllevel; and if you want to insert values into tkSkill by using their enum-namoe (e.g. 'Expert' instead of 3, but you can use both) without looking them up in sklllevel.
Longer answer: Best practice is: don't use enums. Depending on "belief", there is none to only a handful of cases when enums might be slightly useful, if at all. One could be that you don't want to use a reference table to skip a join to display the textvalue/description of an integer-id. In your setup, you are actually using a reference table and still want to use enums.
The closest you'll get to best practice using enums would be to define tkSkill as enum in testskill and don't have the sklllevel-table (the reference table) at all.
But again, I would urge you not to use enums. You can define your table as
CREATE TABLE `sklllevel` (
`Id` tinyint(4) primary key,
`Name` varchar(20) NOT NULL,
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
and then use that id as a foreign key for tkSkill. Or even
CREATE TABLE `sklllevel` (
`Name` varchar(20) primary key
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
and then define tkSkill as varchar(20) and use this as a foreign key - it will use more space, though, but you will have "readable" values in the table if that was the reason for you to use enums in the first place.
Here you can find some background to enums: 8 Reasons Why MySQL's ENUM Data Type Is Evil

mysql - referencing one foreign key to multiple possible primary keys

I'd like to set up the following database scenario:
CREATE TABLE IF NOT EXISTS `points` (
`po_id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`po_north` INT,
`po_east` INT,
PRIMARY KEY (`po_id`),
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `lines`(
`li_id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`li_from` INT NOT NULL,
`li_to` INT NOT NULL,
PRIMARY KEY (`li_id`),
FOREIGN KEY (`li_from`) REFERENCES points(`po_id`),
FOREIGN KEY (`li_to`) REFERENCES points(`po_id`),
) ENGINE=InnoDB;
Now I want to set up a third table, that sores some metadata like who created or altered a point or a line:
CREATE TABLE IF NOT EXISTS `metadata` (
`me_type` ENUM('point','line') NOT NULL,
`me_type_id` INT UNSIGNED NOT NULL,
`me_permissions` VARCHAR(255) NOT NULL,
`me_created_by` INT UNSIGNED NOT NULL,
`me_created_on` DATETIME NOT NULL,
`me_last_modified_by` INT UNSIGNED NOT NULL,
`me_last_modified_by` DATETIME NOT NULL,
) ENGINE=InnoDB;
My first approach was to set an ENUM with two types (points and lines). But the problem is still, that I cannot properly reference a foreign key to one of the tables. Is there any recommended solution for such problem in MySQL?
BTW:
The fields for me_created_by and me_last_modified_by shall reference to a table storing some user data.
Your case appears to be yet another instance of the design pattern known as "generalization specialization" or perhaps "table design for class inheritance".
If you think of points and lines as classes of objects, they are both subclasses of some more general class of objects. I'm not sure what name to give the superclass in this case. Here's one of several previous questions that address the same issue.
Extending classes in the database
Fowler gives an extensive treatment of the subject. Your case has an added wrinkle, because you are dealing with metadata. But that need not alter the design. You need a third table, which I'll call "Items" for lack of a better term. The key, "it_id" would be assigned an auto number, and you would add an item every time you add either a point or a line. The two columns "po_id" and "li_id" would not be assigned an auto number. Instead they would be foreign keys, referencing "it_id" in the Items table.
The references to points or lines in the metadata table would then be references to "items" and you could use that information to find information about points or lines as the case may be.
How helpful this is depends on what you are trying to do with the metadata.
Your tables points and lines should contain a foreign key to metadata – not the other way around. Doing so will save you from defining any more complicated table setups. Using this approach, a single metadata-entry could be re-used several times for many different points or lines. This isn't even MySQL specific but a general, normalized database structure.
you can do this using a trigger, you need to trigger an event that can create reference key for either point or line before you insert a record based on respective tables

PRIMARY KEY definition in MySQL CREATE TABLE statement

What's the difference between this code:
CREATE TABLE samples (
sampleid INT(11) NOT NULL AUTO_INCREMENT,
sampledate DATE NOT NULL,
location VARCHAR(25) NOT NULL,
PRIMARY KEY (sampleid)
)
ENGINE=InnoDB;
and this:
CREATE TABLE samples (
sampleid INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
sampledate DATE NOT NULL,
location VARCHAR(25) NOT NULL,
)
ENGINE=InnoDB;
code?
So a separate PRIMARY KEY statement or as part of a column definition. Same question for UNIQUE INDEX and UNIQUE keyword in column definition.
The second syntax is merely a shortcut allowing you to specify the column and add an index on it in a single clause.
This works out fine in cases where you simply want to create a column and add an index on it.
You'll need to use the first syntax if you want to do something more complicated, such as adding an index based on multiple columns rather than a single column, or if you are adding or changing an index on an existing column; that is, you are not creating the column and the index on it at the same time.
MySQL allows uses the PRIMARY KEY directive to allow you to set the Primary Key dynamically. Supplying PRIMARY KEY as an argument to the constructor can only be called on creating the column. PRIMARY KEY(X), PRIMARY KEY(Y), PRIMARY KEY(Z) allows for changing the primary keys on subsequent queries.
The way I see it is.. The first method is used to create composite keys. While the second method (more readable to me) is primarily used if there is only primary key in the table.
The second method cannot be used if you want to implement composite key
There are many ways to skin a cat and above 2 examples are just 2 of them. They are identical. There's no difference.
They are literally the same. Here is a quick site that shows you the different ways (3) to do it. http://www.java2s.com/Code/SQL/Key/Defineanduseprimarykey.htm

Having trouble with foreign key

I am trying to have categories in my budget2000 table be the foreign key to category in mainBudget. Category is not a unique number so it cannot be a primary key. When I run the code I get the famous error 1005. When I make category part of the primary key in mainBudget with id the code runs, however this will create problems later on. What can I do to make categories a foreign key. I am using mysql 5.5.
Here is my code
create table mainBudget(
id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
year Year NOT NULL,
amount double(10,2) NOT NULL,
category SMALLINT UNSIGNED NOT NULL,
primary key(id)
)ENGINE=INNODB;
create table budget2000(
id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
categories SMALLINT UNSIGNED NOT NULL,
INDEX categoryNumber (categories),
subCategory SMALLINT NOT NULL,
amount FLOAT(10,2) NOT NULL,
date DATE NOT NULL,
description VARCHAR(300) NOT NULL,
primary key(id),
FOREIGN KEY (categories) REFERENCES mainBudget(category)
)ENGINE=INNODB;
category is not indexed in mainBudget. The column in the referenced table has to be indexed (or the left prefix of an index).
Incidentally, are you sure it isn't better to have an additional table category and have mainBudget.category and budget200.categories both foreign keys to this table? Your current setup looks a little odd, particularly with the referenced column in mainBudget not being unique.
Having FKs referencing non-unique columns is not standard SQL. Even when MySQL InnoDB allows this, it does not mean that it is a good idea.
Make some ER-Diagrams and normalize your tables. (Use 3.NF if nothing else forces you not to do.) Having a separate table for Category seems to be the way to go. On the other hand the naming of your exiting tables makes me thinking these should be only one table or their naming is bad.
And when this 2000 has something to do with a year or what then forget about it. You can select this easy in your Queries. Just put everything in one table no matter what year it is.
Your question/problem seems to be design-related to me.

Two tables, with indexes, with the same column names, containing different info... will there be a conflict?

So I have a class that creates a table to be populated with data. Right now I have all the column names the same (product_name, date, etc). I noticed that when I view my tables in Webmin that there is only one index named "product_date" despite the fact that there are, supposedly, two tables using the index. I don't think this can be good.
My question is whether or not this will cause a conflict in the future? I don't want to populate the tables with thousands of rows if I'm only going to need to restructure everything later. I can't imagine that I'm the first to encounter this... maybe I'm just misinformed on how indexes work/webmin displays indexes and being overly paranoid.
(edit)
In response to one comment below, here are the results of SHOW CREATE TABLE tablename:
c_1 | CREATE TABLE c_1 (
p_id int(11) NOT NULL auto_increment,
nm varchar(100) NOT NULL,
m_name text NOT NULL,
PRIMARY KEY (p_id),
KEY nm (nm),
FULLTEXT KEY m_name (m_name)
) ENGINE=MyISAM DEFAULT CHARSET=latin1
c_2 | CREATE TABLE c_2 (
p_id int(11) NOT NULL auto_increment,
ne varchar(100) NOT NULL,
m_name text NOT NULL,
PRIMARY KEY (p_id),
KEY nm (nm),
FULLTEXT KEY metaphone_name (m_name)
) ENGINE=MyISAM DEFAULT CHARSET=latin1
Note that all the indexes on equivalent columns are named the same way.
If it's an index per table, no problem
If I understand your question correctly (a big if), you must create an index for each table. Indexes do not cover more than one table until you get into advanced concepts like indexed/materialized views, which I don't think MySQL handles.