MySQL deleting multiple columns from two table - mysql

I have Two tables like this:
Table categories:
columns: id, name, parent
1, Foods, 0
2, Drinks, 0
3, FastFood, 1
4, Hamburger, 3
Table documents:
columns: id, name, categoryID
1, CheseBurger, 4
2, shop, 3
the parent column has the parent category's id. So When i want to delete Foods entry from categories, i want to delete all child categories and documents.
How can I do this?

As mentioned before, you could use FOREIGN KEY CONSTRAINTS to achieve such a task. Below would be your new table structure for MySQL to support automatically deleting both documents and child categories:
CREATE TABLE categories (
id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(30) NOT NULL,
parent INT(11) UNSIGNED,
INDEX(parent),
FOREIGN KEY (parent) REFERENCES categories(id) ON DELETE CASCADE
) engine=InnoDB;
CREATE TABLE documents (
id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(30) NOT NULL,
categoryID INT(11) UNSIGNED NOT NULL,
INDEX(categoryID),
FOREIGN KEY (categoryID) REFERENCES categories(id) ON DELETE CASCADE
) engine=InnoDB;

I would either use a trigger, or create a more detailed "delete" sproc that would handle it.

Some databases support enforcing referential integrity through foreign keys. I've done it with Oracle, but I'm no mysql expert. It's done as an attribute of the foreign key through the keyword 'CASCADE DELETE'. The database automatically handles it for you.
Here's a quick Oracle example:
ALTER TABLE Things ADD CONSTRAINT FK_Things_Stuff
FOREIGN KEY (ThingID) REFERENCES Stuff (ThingID)
ON DELETE CASCADE
;

You have 2 choices - in either case I recommend that you create foreign key constraints for your relationships.
Choice 1 is to use ON DELETE CASCADE. I think that this is not a good practice, though, because an unintended delete can have quite surprising consequences.
Choice 2 is to walk the tree and find the records that need to be deleted. You can use a self-join for your categories table to identify all the children in n levels of the hierarchy. This is hte prefered approach, imo.
Other ideas like triggers are just a variation of 2.

Related

When to use an auto-increment key?

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;

Primary key couple unique in both ways

Here is my problem :
I want to create relations between two items of the same type.
Basically, I have a 'tag' table and I want to establish relations between tags.
I created a table 'tag_relations'. In this table, each line would represent one relation. It will be composed with 2 atributes : tag_id (which is the id of the tag concerned by the relation), and relation (which is the id of the tag related to the first tag represented by tag_id).
I set the primary key of my 'tag_relations' table as the couple of these 2 attributes.
Here is how I created my table :
CREATE TABLE `tag_relation` (
`tag_id` int(11) NOT NULL,
`relation` int(11) NOT NULL,
PRIMARY KEY (`tag_id`,`relation`),
KEY `relation` (`relation`),
CONSTRAINT `tag_relation_ibfk_1` FOREIGN KEY (`tag_id`) REFERENCES `tag` (`id`),
CONSTRAINT `tag_relation_ibfk_2` FOREIGN KEY (`relation`) REFERENCES `tag` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
My question is : how can I make sure, if I already have a couple (1,2) in my table (so tag 1 is related to tag 2), that it's impossible for me to insert the couple (2,1) (because this relation would already exist implicitly).
Am I forced to create a trigger ?
Thanks in advance
Some databases support indexes on expressions. Since 5.7, MySQL has generated columns with indexes. This allows you to do:
alter table tag_relation add tag1 generated always as (least(tag1, relation));
alter table tag_relation add tag2 generated always as (greatest(tag1, relation));
create unique index unq_tag_relation_tag1_tag2 on tag_relation(tag1, tag2);
In earlier versions, you would need an insert (and possibly update) trigger to ensure data integrity.

On delete cascade, records can be deleted on the parent table, but parent table can not be dropped

I have two tables
CREATE TABLE `category` (`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `item` (`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
categoryid` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`), KEY `fk_categoryid_item` (`categoryid`),
CONSTRAINT `fk_categoryid_item` FOREIGN KEY (`categoryid`)
REFERENCES `category` (`id`) ON DELETE CASCADE)
ENGINE=InnoDB DEFAULT CHARSET=utf8
In the table category I have a record with id 2.
In the item I have a record with id = 1, categoryid = 2, with 2 as the foreign key referring to the category table. If I delete the row in the category table with the id 2, the record in the item table that has the categoryid as 2 also gets deleted. This is as expected because of on delete cascade. But If I try to drop the table category, I get the error Error Code:
1217. Cannot delete or update a parent row: a foreign key constraint fails
Why does this happen ? Of course, setting foreign_key_checks = 0 dropping the table becomes possible. But I would like to know why does this happen that we can delete the records, but can not drop the table with on cascade delete option. Does this option only apply for deleting records, but not for dropping tables.
I checked the documentation, I could not find any explanation for this.
Please let me know if there is something fundamental that I am missing or if you point out to the related documentation it would be helpful. I am using MySQL 5.7.
Thanks in advance.
If you delete the table category but do not remove/alter the foreign key, then that will be left pointing to nothing. Internally the database has a management system that reinforces the referential constraints and that prevents you from creating lose ends. See also this, this and this questions.
It has something to do also with the math behind it, it is called relational algebra. I am not at that level either, but I think it breaks the definition of a FK if you delete one of the associated tables.
In database relational modeling and implementation, a unique key is a set of zero or more attributes, the value(s) of which are guaranteed to be unique for each tuple (row) in a relation.

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

Best MySQL Table Structure: 2 Parents, 1 Child

I have two parent tables, BusinessGroup and SocialGroup, and one child table, Members. A Member can belong to either parent, but not both.
As far as I can see, there are two options for constructing the child table.
Opt 1: Include a field for ParentType, and another for ParentID. The ParentType would be an enum (Business, Social) and the ParentID would be the PK from the respective parent table.
Opt 2: Include a field for BusinessGroupID, and another for SocialGroupID. In this case, the fields would need to be nullable, and only one could contain a value.
Any ideas on which approach is best?
I tried option 1 in MySQL, and created two foreign keys from the child back to the parents. I ran into trouble when inserting values though, since MySQL was expecting a corresponding value in BOTH parent tables.
As a supplementary question: how do things change if I have a larger number of parents, e.g. 6?
Thanks!
CREATE TABLE Group (
GroupID integer NOT NULL
, Name varchar(18)
, Description varchar(18)
, GroupType varchar(4) NOT NULL
-- all columns common to any group type
);
ALTER TABLE Group ADD CONSTRAINT pk_Group PRIMARY KEY (GroupID) ;
CREATE TABLE BusinessGroup (
GroupID integer NOT NULL
-- all columns specific to business groups
);
ALTER TABLE BusinessGroup
ADD CONSTRAINT pk_BusinessGroup PRIMARY KEY (GroupID)
, ADD CONSTRAINT fk1_BusinessGroup FOREIGN KEY (GroupID) REFERENCES Group(GroupID) ;
CREATE TABLE SocialGroup (
GroupID integer NOT NULL
-- all columns specific to social groups
);
ALTER TABLE SocialGroup
ADD CONSTRAINT pk_SocialGroup PRIMARY KEY (GroupID)
, ADD CONSTRAINT fk1_SocialGroup FOREIGN KEY (GroupID) REFERENCES Group(GroupID) ;
CREATE TABLE Person (
PersonID integer NOT NULL
, GroupID integer NOT NULL
);
ALTER TABLE Person
ADD CONSTRAINT pk_Person PRIMARY KEY (PersonID)
, ADD CONSTRAINT fk1_Person FOREIGN KEY (GroupID) REFERENCES Group(GroupID) ;
"parent tables" is probably a misnomer - in the relational model I'd flip your relationship. e.g. make members the master records table, and a join table that allows 1-1 mapping, so have a PK of member_id or whatever the right field is, and map to the FK in either BusinessGroup or SocialGroup.