In SQL, is it OK for two tables to refer to each other?
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)
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
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.
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.