I need help setting up foreign keys for migrating to InnoDB. The situation is that I have 3 tables: jobs, customers and contacts.
A job belongs to a Customer and a job belongs to a Contact. A Customer has many jobs and has many contacts. A contact belongs to a Customer and has many Jobs. I need help setting up foreign keys in the jobs table so that only Contacts that belong to a selected Customer can be inserted. (i.e. If Customer A has contacts A1 and A2 and Customer B has contact B1, once you select Customer A as the customer for a job, it will reject entries for Contact other than A1 or A2). Is this possible to do with foreign keys or will I have to do the verification in the programming language of my choice?
Here's my schema:
CREATE TABLE jobs(
job_id INT(11) NOT NULL AUTO_INCREMENT,
customer_id INT(11) DEFAULT NULL,
contact_id INT(11) DEFAULT NULL,
job_number INT(11) UNSIGNED NOT NULL DEFAULT 0,
status_void TINYINT(1) DEFAULT 0,
PRIMARY KEY (job_id),
INDEX active_jobs (job_number, status_void),
INDEX customer_id (customer_id),
UNIQUE INDEX job_number (job_number),
CONSTRAINT FK_jobs_contacts_contact_id FOREIGN KEY (contact_id)
REFERENCES contacts (contact_id) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT FK_jobs_customers_customer_id FOREIGN KEY (customer_id)
REFERENCES customers (customer_id) ON DELETE SET NULL ON UPDATE CASCADE
)
ENGINE = INNODB
CHARACTER SET utf8
COLLATE utf8_general_ci;`
CREATE TABLE customers(
customer_id INT(11) NOT NULL AUTO_INCREMENT,
inactive TINYINT(1) DEFAULT 0,
customer_name VARCHAR(50) NOT NULL DEFAULT '',
PRIMARY KEY (customer_id),
UNIQUE INDEX customer_name (customer_name),
INDEX inactive (inactive)
)
ENGINE = INNODB
CHARACTER SET utf8
COLLATE utf8_general_ci;
CREATE TABLE contacts(
contact_id INT(11) NOT NULL AUTO_INCREMENT,
customer_id INT(11) DEFAULT NULL,
inactive TINYINT(1) DEFAULT 0,
first_name VARCHAR(50) DEFAULT NULL,
last_name VARCHAR(50) DEFAULT NULL,
PRIMARY KEY (contact_id),
UNIQUE INDEX contact_key (customer_id, last_name, first_name),
INDEX customer_id (customer_id),
INDEX inactive (inactive),
INDEX name (last_name, first_name),
CONSTRAINT fk_contacts_customers_customer_id FOREIGN KEY (customer_id)
REFERENCES customers (customer_id) ON DELETE RESTRICT ON UPDATE CASCADE
)
ENGINE = INNODB
CHARACTER SET utf8
COLLATE utf8_general_ci;
Another goal I have is to setup indexes to make listing all jobs that are not void (status_void=0) and all customers and contacts that are not inactive(inactive=0). I don't know if a multi-column index (jobs) or two separate indexes (customers/contacts) would help in this case.
Please take it easy on me as I'm still learning. Thank you for your time.
Is this possible to do with foreign keys or will I have to do the verification in the programming language of my choice?
Yes and yes.
This is the referential integrity that FKs add to your data. However if you do not also account for it in your application, you would present a horrible user experience that would allow a user to enter contacts that don't belong to a customer and after submitting that to the DB getting an error. It would be better to account for this in you application to prevent that situation from ever being sent to the DB, and the FK is kind of a back-up in case that somehow gets through.
Another goal I have is to setup indexes to make listing all jobs that are not void (status_void=0) and all customers and contacts that are not inactive(inactive=0). I don't know if a multi-column index (jobs) or two separate indexes (customers/contacts) would help in this case.
Which indexes to build tends to be query specific. So you would want to show an example query to best get advice in indexing, not just what your end goal is. It's important to know that only one index can be used for each table reference in a query. So if a table has multiple filters in the where clause you tend to get the best performance by having a multi-column index.
When planning your indexes it's a good idea to think about which columns have fewer unique values that would filter out a larger number of rows and list those first in the index.
All of that being said, without a representative query and the results of an explain statement on it, an outsider can't do much to advise you on indexes as their effectiveness is dependent on the data they are being used against.
Related
I have a mysql performance problem : i have to update a large innodb table (approx. 1 million lines), deleting rows by thousands. Let's say it's items, coming from multiple sources. So, the table has a primary key item_id, an provider_id column identifying the provider of the item, and an external_id column (which is the id of this item in this provider, the way that this specific provider identify it) which has to be a varchar (several providers, several internal ways for them of identifying their items).
When i update, i go provider by provider, and i make a match/diff between a json file and the database, to know which items have to be added, updated or deleted. The match is on the external_id. When i have to delete items, i go with a query like DELETE FROM table_items WHERE provider_id=A AND external_id IN (...).
Even if i make batches of 1k items, it's really slow.
Here's a simplified table definition
CREATE TABLE `annonce` (
`annonce_id` INT(11) NOT NULL AUTO_INCREMENT,
`annonce_id_externe` VARCHAR(70) NOT NULL,
`provenance_id` INT(11) NOT NULL,
`categorie_id` INT(11) NOT NULL,
PRIMARY KEY (`annonce_id`),
UNIQUE INDEX `id_externe_par_provenance_et_categorie` (`annonce_id_externe`, `provenance_id`, `categorie_id`),
INDEX `provenance_id` (`provenance_id`),
INDEX `annonce_id_externe` (`annonce_id_externe`),
INDEX `categorie_id` (`categorie_id`),
CONSTRAINT `annonce_categorie_id` FOREIGN KEY (`categorie_id`) REFERENCES `categorie` (`categorie_id`) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT `annonce_provenance_id` FOREIGN KEY (`provenance_id`) REFERENCES `provenance` (`provenance_id`) ON UPDATE CASCADE ON DELETE CASCADE,
)
Any idea on making it faster ?
Thanks
I'm having issues with setting up a relation between 2 tables called Customer and Customer_Number. I have both tables set to InnoDB both have indexes, but when I go to create the foreign key, I get a "no index defined" error. Below are some screen shots
Here Is the Customer table.
Here is the Customer_Number table.
And here is my error message when trying to create the foreign key.
And lastly, this is the error I get when trying to create the relationship manually.
I just can't seem to figure out the issue, and it's driving me nuts!
the output for SHOW CREATE TABLE Customer is
CREATE TABLE `Customer` (
`Customer_ID` int(11) NOT NULL AUTO_INCREMENT,
`First` varchar(255) NOT NULL,
`Last` varchar(255) NOT NULL,
PRIMARY KEY (`Customer_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1
and the output for SHOW CREATE TABLE Customer_Number is
CREATE TABLE `Customer_Number` (
`Num_ID` int(11) NOT NULL AUTO_INCREMENT,
`Customer_ID` int(11) NOT NULL,
`Number` varchar(255) NOT NULL,
PRIMARY KEY (`Num_ID`),
KEY `Customer_ID` (`Customer_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1
The two CREATE TABLE statements as posted are correct and should be able to accept a new FOREIGN KEY constraint on Customer_Number.Customer_ID since the necessary criteria are met (same data type as referenced column, comparable index or primary key on referenced column).
An ALTER statement succeeds in my testing:
ALTER TABLE Customer_Number ADD FOREIGN KEY (Customer_ID) REFERENCES Customer (Customer_ID);
Being unfamiliar with how PhpMyAdmin abstracts some RDBMS errors, it is hard to say for sure what exactly has gone wrong in the GUI. But if you run the ALTER statement manually and encounter errors about failed foreign key constraints, that's an indication the referencing table already contains values in the column which do not reference a valid row value in the parent table. To uncover those rows so you can address them, execute a query like:
SELECT * FROM Customer_Number WHERE Customer_ID NOT IN (SELECT Customer_ID FROM Customer)
Once you have found the problematic rows, you can either delete them (if unneeded) or update their values to the value of a valid row value in the referenced table. If the column's definition allowed NULL (which yours does not) you could also UPDATE them to set them NULL then run the ALTER statement again.
It is also possible to disable foreign key checks temporarily, add the constraint, update the rows to match valid parent table values, the reenable foreign key checks.
please try this.
alter table Customer_Number add foreign key(customer_ID) references Customer (Customer_ID);
I'm trying to achieve a "One to one" relationship in a MySQL database. For example, let's say I have a Users table and an Accounts table. I want to be sure that a User can have only one Account. And that there can be only one Account per User.
I found two solutions for this but don't know what to use, and are there any other options.
First solution:
DROP DATABASE IF EXISTS test;
CREATE DATABASE test CHARSET = utf8 COLLATE = utf8_general_ci;
USE test;
CREATE TABLE users(
id INT NOT NULL AUTO_INCREMENT,
user_name VARCHAR(45) NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
CREATE TABLE accounts(
id INT NOT NULL AUTO_INCREMENT,
account_name VARCHAR(45) NOT NULL,
user_id INT UNIQUE,
PRIMARY KEY(id),
FOREIGN KEY(user_id) REFERENCES users(id)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
In this example, I define the foreign key in accounts pointing to the primary key in users.
And then I make foreign key UNIQUE, so there can't be two identical users in accounts.
To join tables I would use this query:
SELECT * FROM users JOIN accounts ON users.id = accounts.user_id;
Second solution:
DROP DATABASE IF EXISTS test;
CREATE DATABASE test CHARSET = utf8 COLLATE = utf8_general_ci;
USE test;
CREATE TABLE users(
id INT NOT NULL AUTO_INCREMENT,
user_name VARCHAR(45) NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
CREATE TABLE accounts(
id INT NOT NULL AUTO_INCREMENT,
account_name VARCHAR(45) NOT NULL,
PRIMARY KEY(id),
FOREIGN KEY(id) REFERENCES users(id)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
In this example, I create a foreign key that points from the primary key to a primary key in another table. Since Primary Keys are UNIQUE by default, this makes this relation One to One.
To join tables I can use this:
SELECT * FROM users JOIN accounts ON users.id = accounts.id;
Now the questions:
What is the best way to create One to One relation in MySQL?
Are there any other solutions other than these two?
I'm using MySQL Workbench, and when I design One To One relation in EER diagram and let MySQL Workbench produce SQL code, I get One to Many relation :S That's what's confusing me :S
And if I import any of these solutions into MySQL Workbench EER diagram, it recognizes relations as One to Many :S That's also confusing.
So, what would be the best way to define One to One relation in MySQL DDL. And what options are there to achieve this?
Since Primary Keys are UNIQUE by default, this makes this relation One to One.
No, that makes the relation "one to zero or one". Is that what you actually need?
If yes, then then your "second solution" is better:
it's simpler,
takes less storage1 (and therefore makes cache "larger")
hes less indexes to maintain2, which benefits data manipulation,
and (since you are using InnoDB) naturally clusters the data, so users that are close together will have their accounts stored close together as well, which may benefit cache locality and certain kinds of range scans.
BTW, you'll need to make accounts.id an ordinary integer (not auto-increment) for this to work.
If no, see below...
What is the best way to create One to One relation in MySQL?
Well, "best" is an overloaded word, but the "standard" solution would be the same as in any other database: put both entities (user and account in your case) in the same physical table.
Are there any other solutions other than these two?
Theoretically, you could make circular FKs between the two PKs, but that would require deferred constraints to resolve the chicken-and-egg problem, which are unfortunately not supported under MySQL.
And if I import any of these solutions into MySQL Workbench EER diagram, it recognizes relations as One to Many :S Thats also confusing.
I don't have much practical experience with that particular modeling tool, but I'm guessing that's because it is "one to many" where "many" side was capped at 1 by making it unique. Please remember that "many" doesn't mean "1 or many", it means "0 or many", so the "capped" version really means "0 or 1".
1 Not just in the storage expense for the additional field, but for the secondary index as well. And since you are using InnoDB which always clusters tables, beware that secondary indexes are even more expensive in clustered tables than they are in heap-based tables.
2 InnoDB requires indexes on foreign keys.
Your first approach creates two candidate keys in the accounts table: id and user_id.
I therefore suggest the second approach i.e. using the foreign key as the primary key. This:
uses one less column
allows you to uniquely identify each row
allows you to match account with user
What about the following approach
Create Table user
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Create Table account with a unique index on user_id and account_id with a foreign key relation to user/account and a primary key on user_id and account_id
CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Create Table user2account
CREATE TABLE `user2account` (
`user_id` int(11) NOT NULL,
`account_id` int(11) NOT NULL,
PRIMARY KEY (`user_id`,`account_id`),
UNIQUE KEY `FK_account_idx` (`account_id`),
UNIQUE KEY `FK_user_idx` (`user_id`),
CONSTRAINT `FK_account` FOREIGN KEY (`account_id`) REFERENCES `account` (`id`),
CONSTRAINT `FK_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
While this solution has the largest footprint in the database, there are some advantages.
Putting the FK_Key in either the user table or the account table is something that I expect to be a one to many releation (user has many accounts ...)
While this user2account approach is mainly used to define a many to many relationship, adding a UNIQUE constraint on user_id and on account_id will prevent creating something else than a one to one relation.
The main advantage I see in this solution is that you can divide the work in different code layers or departements in a company
Department A is responsible for creating users, this is possible even without write permission to accounts table
Departement B is responsible for creating accounts, this is possible even without write permission to user table
Departement C is responsible for creating the mapping, this is possible even without write permission to user or account table
Once Departement C has created a mapping neither the user nor the account can be deleted by departement A or B without asking departement C to delete the mapping first.
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
I've two tables one about Customers and the second one is about their Accounts as 'Customer_Account_Information'. we know that one customer can have only one account, so I'm trying to enforce one to one relationship, but i don't know the procedure/syntax in mySql or mySqlyog.
is there any one who can help me?
You should create a Foreign Key contraint on table Accounts to table Customers using CustomerID.
Have a look at FOREIGN KEY Constraints
Also then make this a Unique Column in table Accounts
Have a look at MySQL foreign keys - how to enforce one-to-one across tables?
You could also use the same CustomerID from table Customers as PRIMARY KEY column in table Accounts as PRIMARY KEY.
A simple implementation
This is not a perfect solution but it is easy to understand with a little bit of experience with mysql and referential integrity.
You need a FOREIGN KEY in customers to refer to customer_account_inforamtion. Since the account_id in customers is the only way to join the tables, no customer can have more than one record in customer_account_information.
In the sample definitions below, I've included a FOREIGN KEY CONSTRAINT which causes the customer to be deleted when their account information is deleted. You may or may not want/need this.
CREATE TABLE customer_account_information (
id int(11) NOT NULL auto_increment,
some_attribute varchar(20) NOT NULL,
another_attribute varchar(30) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE customers (
id int(11) NOT NULL auto_increment,
account_id int(11) NOT NULL,
firstname varchar(30) NOT NULL,
surname varchar(30) NOT NULL,
PRIMARY KEY (id),
INDEX account_id (account_id),
CONSTRAINT account_id FOREIGN KEY (account_id) REFERENCES customer_account_information (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
The account_id in customers is the foreign key which refers to a record in customer_account_information
Alternative implementation
It might be an idea to revisit the design of the two tables. If each customer really can have only one account then perhaps all of the attributes of each can be merged into one table. Yes this business rule may change in the future but it would be a better idea to defer this work until really necessary - don't over engineer the solution today.