MySQL Problem with foreign key on n:m relation - mysql

I have this database
create table ticket
(
id int auto_increment primary key,
name varchar(100)
);
create table document
(
id int auto_increment primary key,
name varchar(100)
);
create table ticket_document
(
ticket_id int,
document_id int
);
insert into ticket (id, name) VALUES (1, "a"),(2,"b");
insert into document (id, name) VALUES (1, "x"),(2,"y");
insert into ticket_document (ticket_id, document_id) VALUES (1,1),(1,2),(2,2);
So every ticket can have multiple documents and each document can be referenced to multiple tickets.
I want to achieve, that if I delete a ticket all references to his documents are deleted AND if there is no more reference to one of the document from an other ticket the document is also deleted.
But I don't know how to set the foreign keys.

Create foreign keys:
ALTER TABLE ticket_document
ADD FOREIGN KEY (ticket_id) REFERENCES ticket (id) ON DELETE CASCADE ON UPDATE CASCADE,
ADD FOREIGN KEY (document_id) REFERENCES document (id) ON DELETE CASCADE ON UPDATE CASCADE;
After ticket(s) deletion delete rows from document explicitly (or use service event procedure):
DELETE
FROM document
WHERE NOT EXISTS ( SELECT NULL
FROM ticket_document
WHERE ticket_document.document_id = document.id );
fiddle
Alternatively you may use AFTER DELETE trigger for auto-clearing document table:
CREATE TRIGGER tr_ad_ticket
AFTER DELETE
ON ticket
FOR EACH ROW
DELETE
FROM document
WHERE NOT EXISTS ( SELECT NULL
FROM ticket_document
WHERE ticket_document.document_id = document.id );
fiddle

Related

MySQL doesn't prevent deletion of rows where primary key is associated with another table

tested this on postgreSQL and it doesn't let me delete any row(primary key) associated with another table. Is MySQL different or is there is a way to do that here in Mysql?.
CREATE TABLE account (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(30)
);
CREATE TABLE transaction (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
account_id BIGINT REFERENCES account(id)
);
INSERT INTO account (name) VALUES ('john'); //account_id = 1
INSERT INTO transaction (account_id) VALUES (1);
DELETE FROM account where id = 1;
);
when I delete an account row in account table, mySQL doesn't prevent the deletion of it.
Here is the example for MYSQL. You need add the foreign key with update and delete constrains. You can also read through the documents for more details. Foreign Key
CREATE TABLE account (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(30)
);
CREATE TABLE transaction (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
account_id BIGINT,
FOREIGN KEY (account_id) REFERENCES account(id) ON UPDATE CASCADE ON DELETE RESTRICT
);
INSERT INTO account (name) VALUES ('john');
INSERT INTO transaction (account_id) VALUES (1);
DELETE FROM account where id = 1;
-- 18:32:14 DELETE FROM account where id = 1 Error Code: 1451. Cannot delete or update a parent row: a foreign key constraint fails (`test`.`transaction`, CONSTRAINT `transaction_ibfk_1` FOREIGN KEY (`account_id`) REFERENCES `account` (`id`) ON UPDATE CASCADE) 0.039 sec

SQL issue with multiple foreign

Issue:
I'm using PostgreSQL Database.
I have one table (Albums) to be linked to two other tables (Clients, Domains). So if you are Client or Domain you can have Album. But in Albums table owner can handle only single foreign key. How can I solve this issue?
Dream: Single Album can own only (1) Client or Domain. Need fix issue with foreign keys. Albums: id | owner (multiple foreign -> Clients:id or Domains:id) --> can not do this | name. I just need some smart rework.
Tables (now can have Album only Domain):
Albums
Clients
Domains
Albums (table with foreign key yet):
id | owner (foreign key -> Domains:id) | name
Clients:
id | first_name | last_name
Domains:
id | owner | name
Add 2 FK columns, and a CHECK constraint, to enforce only one of them is NOT NULL...
Something like this:
CREATE TABLE albums (
id serial PRIMARY KEY,
client_id integer,
domain_id integer,
name varchar(255) NOT NULL,
FOREIGN KEY (client_id) REFERENCES clients(id),
FOREIGN KEY (domain_id) REFERENCES domains(id),
CHECK ((client_id IS NULL) <> (domain_id IS NULL))
);
To query you can use something like this:
SELECT a.id, COALESCE(c.id, d.id) AS owner_id, COALESCE(c.name, d.name) AS owner_name,
a.name AS title
FROM albums a
LEFT JOIN clients c ON a.client_id = c.id
LEFT JOIN domains d ON a.domain_id = d.id
#e_i_pi's version
CREATE TABLE entities (
id serial PRIMARY KEY,
type integer, -- could be any other type
-- any other "common" values
);
CREATE TABLE client_entities (
id integer PRIMARY KEY, -- at INSERT this comes from table `entities`
name varchar(255) NOT NULL,
);
CREATE TABLE domain_entities (
id integer PRIMARY KEY, -- at INSERT this comes from table `entities`
name varchar(255) NOT NULL,
);
CREATE TABLE albums (
id serial PRIMARY KEY,
owner_id integer FOREIGN KEY REFERENCES entities(id), -- maybe NOT NULL?
name varchar(255) NOT NULL,
);
Query:
SELECT a.id, owner_id, COALESCE(c.name, d.name) AS owner_name, a.name AS title
FROM albums a
LEFT JOIN entities e ON a.owner_id = e.id
LEFT JOIN client_entities c ON e.id = c.id AND e.type = 1 -- depending on the type of `type`
LEFT JOIN domain_entities d ON e.id = d.id AND e.type = 2
Righto, so as suggested in the comment to the answer by #UsagiMiyamoto, there is a way to do this that allows declaration of entity types, with cascading. Note that this solution doesn't support unlimited entity types, as we need to maintain concrete FK constraints. There is a way to do this with unlimited entity types, but involves triggers and quite a bit of nastiness.
Here's the easy to understand solution:
-- Start with a test schema
DROP SCHEMA IF EXISTS "entityExample" CASCADE;
CREATE SCHEMA IF NOT EXISTS "entityExample";
SET SEARCH_PATH TO "entityExample";
-- We'll need this to enforce constraints
CREATE OR REPLACE FUNCTION is_entity_type(text, text) returns boolean as $$
SELECT TRUE WHERE $1 = $2
;
$$ language sql;
-- Unique entity types
CREATE TABLE "entityTypes" (
name TEXT NOT NULL,
CONSTRAINT "entityTypes_ukey" UNIQUE ("name")
);
-- Our client entities
CREATE TABLE clients (
id integer PRIMARY KEY,
name TEXT NOT NULL
);
-- Our domain entities
CREATE TABLE domains (
id integer PRIMARY KEY,
name TEXT NOT NULL
);
-- Our overaching entities table, which maintains FK constraints against clients and domains
CREATE TABLE entities (
id serial PRIMARY KEY,
"entityType" TEXT NOT NULL,
"clientID" INTEGER CHECK (is_entity_type("entityType", 'client')),
"domainID" INTEGER CHECK (is_entity_type("entityType", 'domain')),
CONSTRAINT "entities_entityType" FOREIGN KEY ("entityType") REFERENCES "entityTypes" (name) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "entities_clientID" FOREIGN KEY ("clientID") REFERENCES "clients" (id) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "entities_domainID" FOREIGN KEY ("domainID") REFERENCES "domains" (id) ON DELETE CASCADE ON UPDATE CASCADE
);
-- Our albums table, which now can have one owner, but of a dynam ic entity type
CREATE TABLE albums (
id serial PRIMARY KEY,
"ownerEntityID" integer,
name TEXT NOT NULL,
CONSTRAINT "albums_ownerEntityID" FOREIGN KEY ("ownerEntityID") REFERENCES "entities"("id")
);
-- Put the entity type in
INSERT INTO "entityTypes" ("name") VALUES ('client'), ('domain');
-- Enter our clients and domains
INSERT INTO clients VALUES (1, 'clientA'), (2, 'clientB');
INSERT INTO domains VALUES (50, 'domainA');
-- Make sure the clients and domains are registered as entities
INSERT INTO entities ("entityType", "clientID")
SELECT
'client',
"clients".id
FROM "clients"
ON CONFLICT DO NOTHING
;
INSERT INTO entities ("entityType", "domainID")
SELECT
'domain',
"domains".id
FROM "domains"
ON CONFLICT DO NOTHING
;
If you don't like the idea of inserting twice (once in client, once in entites, for example) you can have a trigger on inserts in the clients table, or alternately create an insert function that inserts to both tables at once.

primary and foreign key relationship in mysql

I want to create two tables, one is having primary key and other for foreign key.
So my requirement is the values in users should be available which are present only in customers table other vice show error constraint violation.
Did something like this, but still am able to insert values in users which are not in customers.
create table if not exists customers
(
cust_user_id int,
primary key (cust_user_id)
);
create table if not exists users
( prid INT,
foreign key (prid) references customers(cust_user_id) ON UPDATE CASCADE
);
Any suggestion ?
Thanks

Why is ON DELETE CASCADE not removing referenced record?

I've created a SQLFiddle with the code shown below. The problem is that after the DELETE statement the associated credit_card record is supposed to be deleted as well.
CREATE TABLE person (
id BIGINT AUTO_INCREMENT PRIMARY KEY
);
CREATE TABLE credit_card (
id BIGINT AUTO_INCREMENT PRIMARY KEY
);
CREATE TABLE person_credit_card (
person_id BIGINT NOT NULL,
credit_card_id BIGINT NOT NULL UNIQUE, -- Please note that this is UNIQUE
PRIMARY KEY(person_id, credit_card_id),
CONSTRAINT fk__person_credit_card__person
FOREIGN KEY (person_id)
REFERENCES person(id),
KEY pkey (credit_card_id),
CONSTRAINT fk__person_credit_card__credit_card
FOREIGN KEY (credit_card_id)
REFERENCES credit_card(id)
ON DELETE CASCADE
ON UPDATE CASCADE
);
INSERT INTO person (id) VALUES (1);
INSERT INTO credit_card (id) VALUES (1);
INSERT INTO person_credit_card (person_id, credit_card_id) VALUES (1, 1);
DELETE FROM person_credit_card WHERE credit_card_id = 1;
I'm not sure why this is not working. With the UNIQUE constraint on the credit_card_id this is not possible:
+--------------------------------------+
| person_credit_card |
+--------------------------------------+
| person_id | credit_card_id |
+--------------------------------------+
| 1 | 1 |
+--------------------------------------+
| 2 | 1 |
+--------------------------------------+
So what am I doing wrong here and how can I make it work?
I also tried to e.g. delete a person and remove all his credit_card records (see this other SQLFiddle):
CREATE TABLE person (
id BIGINT AUTO_INCREMENT PRIMARY KEY
);
CREATE TABLE credit_card (
id BIGINT AUTO_INCREMENT PRIMARY KEY
);
CREATE TABLE person_credit_card (
person_id BIGINT NOT NULL,
credit_card_id BIGINT NOT NULL UNIQUE,
PRIMARY KEY(person_id, credit_card_id),
CONSTRAINT fk__person_credit_card__person
FOREIGN KEY (person_id)
REFERENCES person(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT fk__person_credit_card__credit_card
FOREIGN KEY (credit_card_id)
REFERENCES credit_card(id)
ON DELETE CASCADE
ON UPDATE CASCADE
);
INSERT INTO person (id) VALUES (1);
INSERT INTO person (id) VALUES (2);
INSERT INTO credit_card (id) VALUES (1);
INSERT INTO credit_card (id) VALUES (2);
INSERT INTO credit_card (id) VALUES (3);
INSERT INTO person_credit_card (person_id, credit_card_id) VALUES (1, 1);
INSERT INTO person_credit_card (person_id, credit_card_id) VALUES (1, 2);
INSERT INTO person_credit_card (person_id, credit_card_id) VALUES (2, 3);
DELETE FROM person WHERE id = 1;
but the outcome is that only the resolution table is losing its entries but the credit_card records are still there.
From the documentation:
CASCADE: Delete or update the row from the parent table, and
automatically delete or update the matching rows in the child table.
Both ON DELETE CASCADE and ON UPDATE CASCADE are supported.
Deleting from person_credit_card won't cascade person nor credit_card.
Cascade works by deleting/updating records from tables that reference the record being deleted.
In other words, since person doesn't have a column with reference to person_credit_card, then it won't be deleted.

Fail to trigger BEFORE INSERT INTO a table that only has one auto increment column

I'm planning to make a notification system that sends out different types of messages to users (private messages among users, message about their posts being published etc) What I'm looking for is some sort of conditional foreign key on the column this_id in notification table so that it can find a specific row from either table comments or pm, and I have found this thread that's exactly what I want. So I create a table "supertable" with just one column (SERIAL PRIMARY KEY) to be referenced by comments and PM. And to make sure a new row is insert into the supertable first to generate a key before any new row inserts into either comments and PM, I set up two BEFORE INSERT INTO triggers
CREATE TRIGGER `before_insert_comments`
BEFORE INSERT ON `comments`
FOR EACH ROW BEGIN
INSERT INTO supertable (this_id) VALUES ('')
END;
CREATE TRIGGER `before_insert_pm`
BEFORE INSERT ON `PM`
FOR EACH ROW BEGIN
INSERT INTO supertable (this_id) VALUES ('')
END;
But when inserting a record into table comments or PM, I'm still getting the error
Cannot add or update a child row: a foreign key constraint fails ( CONSTRAINT comments FOREIGN KEY (this_id) REFERENCES supertable (this_id) ON DELETE CASCADE ON UPDATE CASCADE). Anyone know what's the problem with the triggers?
Table schema
CREATE TABLE notification (
id INT NOT NULL AUTO_INCREMENT,
this_id SERIAL PRIMARY KEY,
user_id INT,
is_read TINYINT,
FOREIGN KEY (this_id) REFERENCES supertable(this_id)
ON UPDATE CASCADE
ON DELETE CASCADE
);
CREATE TABLE supertable (
this_id SERIAL PRIMARY KEY
);
CREATE TABLE comments (
this_id SERIAL PRIMARY KEY,
user_id INT ,
post TEXT,
is_approved TINYINT,
...
FOREIGN KEY (this_id) REFERENCES supertable(this_id)
ON UPDATE CASCADE
ON DELETE CASCADE
);
CREATE TABLE PM (
this_id SERIAL PRIMARY KEY,
sender_id INT,
recipient_id INT ,
msg TEXT,
...
FOREIGN KEY (this_id) REFERENCES supertable(this_id)
ON UPDATE CASCADE
ON DELETE CASCADE
);
Check again your column mapping. Few key to point here.
your notification table primary key should set to id instead of this_foreign_id(I rename it to avoid confuse).
datatype for this_foreign_id should be BIGINT UNSIGNED to map with SERIAL, since 1 table only can have 1 auto increment column and it should be primary key.
on your trigger, insert null instead of '' cause SERIAL column is meant for BIGINT UNSIGNED
Let me know if it work.
CREATE TABLE supertable (
this_id SERIAL PRIMARY KEY
);
CREATE TABLE notification (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
this_foreign_id BIGINT UNSIGNED,
user_id INT,
is_read TINYINT,
FOREIGN KEY (this_foreign_id) REFERENCES supertable(this_id)
ON UPDATE CASCADE
ON DELETE CASCADE
);
CREATE TABLE comments (
this_id SERIAL PRIMARY KEY,
user_id INT ,
post TEXT,
is_approved TINYINT,
FOREIGN KEY (this_id) REFERENCES supertable(this_id)
ON UPDATE CASCADE
ON DELETE CASCADE
);
CREATE TABLE PM (
this_id SERIAL PRIMARY KEY,
sender_id INT,
recipient_id INT ,
msg TEXT,
FOREIGN KEY (this_id) REFERENCES supertable(this_id)
ON UPDATE CASCADE
ON DELETE CASCADE
);
CREATE TRIGGER before_insert_comments
BEGIN
BEFORE INSERT ON comments
FOR EACH ROW BEGIN
INSERT INTO supertable (this_id) VALUES (NULL);
END;
CREATE TRIGGER before_insert_pm
BEGIN
BEFORE INSERT ON PM
FOR EACH ROW BEGIN
INSERT INTO supertable (this_id) VALUES (NULL);
END;