When to use an auto-increment key? - mysql

I've recently started to work on MySQL and while I've read some documentation on database structure, I cannot get my head around auto-increment keys and why using them.
I have been told:
it's best to use a number instead of text as a primary key,
it's best to use a key that doesn't have any business signification
Let's look at the situation below:
tStores tSales tCustomers
---------- ----------- --------------
store_id sale_id customer_id
storeCode store_id
customer_id
First, I load some data in tStores for all the stores products can be sold. In our business, all stores have a 4 letters code to identify them. I could use this as a primary key, but based on the recommendations above I should use a store_id field that auto-increments?
The problem is, each time I insert something in tSales, I have to go back to tStores and do something like:
SELECT store_id from tStores WHERE storeCode = #myStoreCode;
Assuming I am loading hundreds of thousands rows in tSales for each store, would it not be more efficient to use the storeCode as primary key?
What would be the most efficient way to deal with this?

Yes you can use storeCode as the primary key, it will work if you can ensure it is unique. Then you will add a foreign key on your other tables to establish the relationship.
The benefit of auto increment index are:
It is usually faster than any index on other column type
It is usually recommended by some framework (such as Laravel in PHP)
Related to you structure I would comment on some points:
You have mixed casing columns/tables. When working on MySQL, especially when used on different OS (Windows/Linux), I would always recommend to use lowercase names for both schemas, tables and columns.
You added a prefix in front of store_id and store_code. This prefix is not necessary. Why not simply naming the columns id and code.
The relationship on tSales should be named tStores_id instead to clearly indicate from which table and which column you are referring to.
Here the SQL code for this example:
CREATE SCHEMA `myshop` ;
CREATE TABLE `store`.`stores` (
`code` VARCHAR(10) NOT NULL,
PRIMARY KEY (`code`));
CREATE TABLE `store`.`sales` (
`id` INT NOT NULL AUTO_INCREMENT,
`store_code` VARCHAR(10) NOT NULL,
`customer_id` INT NOT NULL,
PRIMARY KEY (`id`));
CREATE TABLE `store`.`customers` (
`id` INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`));
ALTER TABLE `store`.`sales`
ADD INDEX `fk_sales_customers_id_idx` (`customer_id` ASC) VISIBLE;
ALTER TABLE `store`.`sales`
ADD CONSTRAINT `fk_sales_customers_id`
FOREIGN KEY (`customer_id`)
REFERENCES `store`.`customers` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE;
ALTER TABLE `store`.`sales`
ADD INDEX `fk_sales_stores_code_idx` (`store_code` ASC) VISIBLE;
ALTER TABLE `store`.`sales`
ADD CONSTRAINT `fk_sales_stores_code_id`
FOREIGN KEY (`store_code`)
REFERENCES `store`.`stores` (`code`)
ON DELETE CASCADE
ON UPDATE CASCADE;

Related

MySQL - Error: 150 "Foreign key constraint is incorrectly formed")

Got an odd problem I cant solve after browsing dozens of forum posts, and my local SQL Books.
I've got two tables, and want to add a foreign key to one of them. The foreign key and primary key share the same datatype and charset and yet I cannot add the Foreign Key at all.
addon_account
name
type
comments
id
int(11)
Primary Key
name
varchar(60)
Primary Key
label
varchar(255)
shared
int(11)
addon_account_data
name
type
comments
id
int(11)
Primary Key
account_name
varchar(60)
Primary Key
money
double
owner
varchar()
The query I ran:
ALTER TABLE `addon_account_data` ADD FOREIGN KEY (`account_name`) REFERENCES `addon_account`(`name`) ON DELETE RESTRICT ON UPDATE RESTRICT;
Can't get it to work. Tosses out the same issue the entire time.
You are creating a foreign key on addon_account_data(account_name) that references addon_account(name). You have a composite primary the referred table : addon_account(id, name).
This is not allowed in MySQL, as explained in the documentation:
MySQL requires indexes on foreign keys and referenced keys so that foreign key checks can be fast and not require a table scan. In the referencing table, there must be an index where the foreign key columns are listed as the first columns in the same order.
Possible solutions:
add an additional column in the referring table: addon_account_data(account_id, account_name) and create a composite primary key to the corresponding columns in addon_account
create an index on addon_account(name) (probably the simplest solution)
change the order of the columns in the primary key of the referred table, like: addon_account(name, id) (you might want to first consider the impacts this may have in terms of performance)
I am not exactly a MySQL guy, but:
I believe the problem is that you are referencing only part of the primary key:
Your table addon_account has a composite key PK(id, name).
So, to put your relationship to work, you will need to add 'account_id' as part of the foreign key as well:
ALTER TABLE addon_account_data ADD FOREIGN KEY (account_id, account_name) REFERENCES addon_account(id, name)
This thread deals with something similar.
I hope this helps.
EDITED
I have installed a MySQL server instance on my local machine... (MySQL 8).
I have run the script below, and it worked (giving warnings about integer display being a deprecated feature, so I would recommend ommitting it):
CREATE TABLE addon_account(
id INT(11) NOT NULL,
`name` VARCHAR(60) NOT NULL,
label VARCHAR(255),
shared INT(11),
CONSTRAINT pk_addon_account PRIMARY KEY(id, `name`));
CREATE TABLE addon_account_data (
id INT(11) NOT NULL,
account_name VARCHAR(60) NOT NULL,
account_id INT(11),
money DOUBLE,
`owner` VARCHAR(255),
CONSTRAINT pk_addon_account_data PRIMARY KEY(id, account_name),
CONSTRAINT fk_addon_account_account_data FOREIGN KEY(account_id, account_name)
REFERENCES addon_account(id, `name`));
Could you try it and see if this works for you?
I am not that familiar with MySQL.
make sure that the 2 tables have the same collation
like
COLLATE='utf8_general_ci'

Database design for an entity that might comprise from other same entities

I have some sport facilities that have fields that one can play 5x5 football in them. I am trying to make a simple reservation system for them.
My problem is that some fields combine and make bigger fields that the manages of the facilities want to treat them as their own entities (makes sense, if they book them like that why not).
Let's give an actual example.
We have facility FA. They have 3 5x5 fields one next to another, let's call then sa, sb, sc and any two of them can combine to make a 7x7 field, let's call it dd and all three to make a 10x10 field, let's call it te.
This happens with the other facilities as well but this is the more extreme case.
I have been trying to think how to model the tables for the fields when I make the reservation and deal with it but I am not sure.
One solution I have is to have a table for the fields
CREATE TABLE IF NOT EXISTS field (
id SMALLINT(5) UNSIGNED NOT NULL AUTO_INCREMENT,
arena_id SMALLINT(4) UNSIGNED NOT NULL,
internal_id TINYINT(3) UNSIGNED NOT NULL,
is_composite BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY (id),
UNIQUE (arena_id, internal_id),
CONSTRAINT fk_field_arena_id FOREIGN KEY (arena_id) REFERENCES arena(id) ON UPDATE CASCADE ON DELETE CASCADE
)
;
And theh have a one to one or zero relationship with another table
CREATE TABLE IF NOT EXISTS field_component (
field_id SMALLINT(5) UNSIGNED NOT NULL,
component SMALLINT(5) UNSIGNED NOT NULL,
PRIMARY KEY (field_id, component),
CONSTRAINT fk_field_component_field_id FOREIGN KEY (field_id) REFERENCES field(id) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT fk_field_component_field_id2 FOREIGN KEY (component) REFERENCES field(id) ON UPDATE CASCADE ON DELETE CASCADE
)
;
that will have entries with the fields that comprise a component field. An entry will exist here only when the flag is_composite in the field table is true.
A simpler solution I was thinking that is a bit more manual, was instead of having the second table and the flag, to just have a string column where I put the ids of the fields that make the composite field as a comma separated list.
On a separate note, I was thinking of moving the flag is composite to a third table called field_info that I might have one to one relationship with the field and will contain information about each field. i.e. size, material of the ground, if it's composite or not, notes about it etc.
Any thought suggestions, criticism, alternatives are welcome.
I would consider the following, that ensures in the composite table field_component that the child and composite arenas are at least the same. Note that InnoDB check constraints are not enforced.
Point #1: The is_composite quality in the following for the field_component implicitly pointing back to something that is truly a composite is not enforced. It could be with more compositing (meaning more tables).
Point #2: The datatypes should not be overly engineered into small and tiny INTs at this stage or perhaps ever. Especially if new to mysql.
Point #3: The FK relationships have the tendency to create KEYS for you automatically when not present in the child table. The unique key that we explicitly have in field_component effectly serves two purposes. It enforces non-dupes, and it serves as the index used where an FK auto-gen one would have been generated. Another one is generated automatically as can be seen in show create table. So, our UNIQUE KEY serves a few purposes there.
CREATE TABLE IF NOT EXISTS field (
id SMALLINT(5) UNSIGNED NOT NULL AUTO_INCREMENT,
arena_id SMALLINT(4) UNSIGNED NOT NULL,
internal_id TINYINT(3) UNSIGNED NOT NULL,
is_composite BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY (id),
UNIQUE (arena_id, internal_id),
CONSTRAINT fk_field_arena_id FOREIGN KEY (arena_id) REFERENCES arena(id) ON UPDATE CASCADE ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS field_component (
field_id SMALLINT(5) UNSIGNED NOT NULL,
component SMALLINT(5) UNSIGNED NOT NULL,
PRIMARY KEY (field_id, component),
CONSTRAINT fk_field_component_field_id FOREIGN KEY (field_id) REFERENCES field(id) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT fk_field_component_field_id2 FOREIGN KEY (component) REFERENCES field(id) ON UPDATE CASCADE ON DELETE CASCADE
);
TweakA:
CREATE SCHEMA TweakA;
USE TweakA;
-- drop table arena
CREATE TABLE IF NOT EXISTS arena
( id INT PRIMARY KEY,
aName varchar(200) NOT NULL
);
-- drop table field
CREATE TABLE IF NOT EXISTS field
( id INT AUTO_INCREMENT PRIMARY KEY,
arena_id INT NOT NULL, -- like the Arena #
internal_id INT NOT NULL, -- 1, 2, 3 for the field #
is_composite BOOLEAN NOT NULL DEFAULT FALSE,
friendly_name VARCHAR(100) NOT NULL,
UNIQUE KEY (arena_id, internal_id),
CONSTRAINT fk_field_arena_id FOREIGN KEY (arena_id) REFERENCES arena(id) ON UPDATE CASCADE ON DELETE CASCADE
);
-- drop table field_component
CREATE TABLE IF NOT EXISTS field_component
( id INT AUTO_INCREMENT PRIMARY KEY,
arena_id INT NOT NULL,
child_internal_id INT NOT NULL,
composite_internal_id INT NOT NULL,
-- The following UK will pick up part of what I will explain in the Narrative
UNIQUE KEY `unq_arena_comp_child` (arena_id,child_internal_id,composite_internal_id),
CONSTRAINT fk_field_child_field_id FOREIGN KEY (arena_id,child_internal_id)
REFERENCES field(arena_id, internal_id) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT fk_field_composite_field_id FOREIGN KEY (arena_id,composite_internal_id)
REFERENCES field(arena_id, internal_id) ON UPDATE CASCADE ON DELETE CASCADE
-- note that InnoDB check constraints are not effective
);
-- Note, look at output from the following
-- show create table field_component; -- this shows the auto-gen of 1 key due to FK
--
-- The following block is a Helper block during testing
-- Truncate in reverse order:
-- TRUNCATE TABLE field_component;
-- TRUNCATE TABLE field;
-- TRUNCATE TABLE arena;
-- test data load:
INSERT arena(id,aName) VALUES (1,'Boston Arena, North Shore');
INSERT field(arena_id,internal_id,is_composite,friendly_name) VALUES
(1,1,FALSE,'sa'),
(1,2,FALSE,'sb'),
(1,3,FALSE,'sc'),
(1,4,TRUE,'dab'),
(1,5,TRUE,'dac'),
(1,6,TRUE,'dbc'),
(1,7,TRUE,'abc');
INSERT field_component(arena_id,child_internal_id,composite_internal_id) VALUES
(1,1,4),
(1,2,4),
(1,1,5),
(1,3,5),
(1,2,6),
(1,3,6),
(1,1,7),
(1,2,7),
(1,3,7); -- SUCCESS
INSERT field_component(arena_id,child_internal_id,composite_internal_id) VALUES
(2,2,4); -- will fail, as expected
INSERT field_component(arena_id,child_internal_id,composite_internal_id) VALUES
(1,72,4); -- will fail, as expected
INSERT field_component(arena_id,child_internal_id,composite_internal_id) VALUES
(1,1,444); -- will fail, as expected
show create table field_component;
-- the above will exhibit the AUTO_INCREMENT gap anomoly due to the above
-- expected failed inserts, setting AI=13 or so
DROP SCHEMA TweakA;
I wrote an FK Enforces Composite Relationship answer that got a little complicated. Yours can go that route depending on the level of DB Enforcement you are looking for.
Also see the MySQL Using FOREIGN KEY Constraints concerning auto-gen of KEYS due to FK relationships as mentioned in Point #3.
So, this answer could just keep growing as you work thru enforcement. Or do it client side. If it were me, I would do it DB Enforcement.
Regardless, as mentioned in comments, don't store CSV values in a column.

MariaDB foreign key issue

Been awhile since I've had to create/define a database, so ignore my lack of understanding. Basically, I have a table that has a few fields, one of which is a hash which references another table with the same hash, and the hash value. I believe the problem lies in the fact that I'm using the foreign key to references the non-primary key in the secondary table. So my question is this: how do I set up this relationship? Below is a minimum example to create my issue:
-- first table, imagine this as the hash value (should I remove the r_id,
-- and make the r_hash a unique & primary key?)
create table rx(
r_id int(10) auto_increment primary key,
r_hash varchar(175) default null,
r_val varchar(175) default null
);
create table cx(
c_id int(10) auto_increment primary key,
c_name varchar(175) default null,
querystr varchar(175) default null,
r_c_hash varchar(175) default null,
constraint r_fk foreign key(r_c_hash) references rx(r_hash)
);
And the classic error that I somehow still can't get around:
ERROR 1005 (HY000): Can't create table 'test2.cx' (errno: 150)
EDIT:
Just for clarification, an example row from rx will look like this:
1 | asdkjIOFJE93fijflskf | anexamplehashvalue
and an example row from cx may look like this:
1 | name_of_file | queryString=yes&1=3 | asdkjIOFJE93fijflskf
which as you can see, the hash values match which is all I need.. Do I recreate the rx table with the hash as the PK and make it unique? or can I keep the structure of the tables as it already is?
Why are you not referencing the primary key? That really is what primary keys are for.
If you really, really need to use a different key, then you should define a unique index on it:
create unique index unq_rx_r_hash on rx(r_hash);

MySql: Composite Unique Key

I want to make composite key of 2 column id & code,the both columns altogether should act like Unique key for the table. while I have browsed and tried to create a table as follows,
Create table test (
`test_no` int not null AUTO_INCREMENT,
`code` varchar(5) NOT NULL,
`name` varchar(255),
`UPDBy` varchar(255),
PRIMARY KEY (`test_no`),
FOREIGN KEY (code) REFERENCES other_table(code)
// CONSTRAINT `K_test_1` Unique KEY (`test_no`,`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Just a second thought, can i make both the column as PK ? I think it will serve my purpose, RIght?
CONSTRAINT `K_test_1` Primary KEY (`test_no`,`code`) OR Primary KEY (`test_no`,`code`)
You seem to be on the wrong track somehow. Your table has an ID which is auto incremented. This is not supposed to be the primary key? Why do you call it ID then?
There are two ways to build a database: Either use the natural values a user is used to, such as an employee number a department number and so on. Or use IDs (which are usually hidden from the user). Than you would have an employee table with primary key "id" or "employee_id" or whatever, and the employee number just as a field. But as it must be unique, you would have an additional unique index on that field.
Having said that; you have a table "other_table" with primary key "code" it seems. So you are not using an ID concept here. Then why do you use it on table test? If this is a detail table on other_table, then I'd expect the composite key to be something like code + test_no (thus showing numbered tests per code) for isntance.
So the essence is: 1. Think about what your table contains. 2. Think about wether to use IDs or natural keys. The answer to these questions should help you find the correct key for your table. (And sometimes a table even doesn't have a primary key and needs none.)
You sure can make them both as PRIMARY KEY. If you don't want to, just use UNIQUE instead of UNIQUE KEY.
To set both as PRIMARY KEY, do as it follows:
...
PRIMARY KEY (`id`, `code`);
...
To set a UNIQUE CONSTRAINT, do as it follows:
...
CONSTRAINT `K_test_1` UNIQUE (`id`,`code`);
...

In SQL, is it OK for two tables to refer to each other?

In this system, we store products, images of products (there can be many image for a product), and a default image for a product. The database:
CREATE TABLE `products` (
`ID` int(10) unsigned NOT NULL AUTO_INCREMENT,
`NAME` varchar(255) NOT NULL,
`DESCRIPTION` text NOT NULL,
`ENABLED` tinyint(1) NOT NULL DEFAULT '1',
`DATEADDED` datetime NOT NULL,
`DEFAULT_PICTURE_ID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`ID`),
KEY `Index_2` (`DATEADDED`),
KEY `FK_products_1` (`DEFAULT_PICTURE_ID`),
CONSTRAINT `FK_products_1` FOREIGN KEY (`DEFAULT_PICTURE_ID`) REFERENCES `products_pictures` (`ID`) ON DELETE SET NULL ON UPDATE SET NULL
) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8;
CREATE TABLE `products_pictures` (
`ID` int(10) unsigned NOT NULL AUTO_INCREMENT,
`IMG_PATH` varchar(255) NOT NULL,
`PRODUCT_ID` int(10) unsigned NOT NULL,
PRIMARY KEY (`ID`),
KEY `FK_products_pictures_1` (`PRODUCT_ID`),
CONSTRAINT `FK_products_pictures_1` FOREIGN KEY (`PRODUCT_ID`) REFERENCES `products` (`ID`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
as you can see, products_pictures.PRODUCT_ID -> products.ID and products.DEFAULT_PICTURE_ID -> products_pictures.ID, so a cycle reference. Is it OK?
No, it's not OK. Circular references between tables are messy. See this (decade old) article: SQL By Design: The Circular Reference
Some DBMS can handle these, and with special care, but MySQL will have issues.
Option 1
As your design, to make one of the two FKs nullable. This allows you to solve the chicken-and-egg problem (which table should I first Insert into?).
There is a problem though with your code. It will allow a product to have a default picture where that picture will be referencing another product!
To disallow such an error, your FK constraint should be:
CONSTRAINT FK_products_1
FOREIGN KEY (id, default_picture_id)
REFERENCES products_pictures (product_id, id)
ON DELETE RESTRICT --- the SET NULL options would
ON UPDATE RESTRICT --- lead to other issues
This will require a UNIQUE constraint/index in table products_pictures on (product_id, id) for the above FK to be defined and work properly.
Option 2
Another approach is to remove the Default_Picture_ID column form the product table and add an IsDefault BIT column in the picture table. The problem with this solution is how to allow only one picture per product to have that bit on and all others to have it off. In SQL-Server (and I think in Postgres) this can be done with a partial index:
CREATE UNIQUE INDEX is_DefaultPicture
ON products_pictures (Product_ID)
WHERE IsDefault = 1 ;
But MySQL has no such feature.
Option 3
This approach, allows you to even have both FK columns defined as NOT NULL is to use deferrable constraints. This works in PostgreSQL and I think in Oracle. Check this question and the answer by #Erwin: Complex foreign key constraint in SQLAlchemy (the All key columns NOT NULL Part).
Constraints in MySQL cannot be deferrable.
Option 4
The approach (which I find cleanest) is to remove the Default_Picture_ID column and add another table. No circular path in the FK constraints and all FK columns will be NOT NULL with this solution:
product_default_picture
----------------------
product_id NOT NULL
default_picture_id NOT NULL
PRIMARY KEY (product_id)
FOREIGN KEY (product_id, default_picture_id)
REFERENCES products_pictures (product_id, id)
This will also require a UNIQUE constraint/index in table products_pictures on (product_id, id) as in solution 1.
To summarize, with MySQL you have two options:
option 1 (a nullable FK column) with the correction above to enforce integrity correctly
option 4 (no nullable FK columns)
The only issue you're going to encounter is when you do inserts.
Which one do you insert first?
With this, you will have to do something like:
Insert product with null default picture
Insert picture(s) with the newly created product ID
Update the product to set the default picture to one that you just inserted.
Again, deleting will not be fun.
this is just suggestion but if possible create one join table between this table might be helpfull to tracking
product_productcat_join
------------------------
ID(PK)
ProductID(FK)- product table primary key
PictureID(FK) - category table primary key
In the other table you can just hold that field without the foreign key constraint.
it is useful in some cases where you want to process with the smaller table but connect to the bigger table with the result of the process.
For example if you add a product_location table which holds the country, district, city, address and longitude and latitude information. There might be a case that you want to show the product within a circle on the map.
John what your doing isnt anything bad but using PK-FK actually helps with normalizing your data by removing redundant repeating data. Which has some fantastic advantages from
Improved data integrity owing to the elimination of duplicate storage locations for the same data
Reduced locking contention and improved multiple-user concurrency
Smaller files
that is not a cyclic ref, that is pk-fk