MySQL: Update a table in its own trigger - mysql

Apparently MySQL have the really annoying restriction of not being able to update a table inside a trigger defined for that same table.
I'm using MySQL version 5.1 and I get the error: "Can't update table in stored function/trigger because it is already used by statement which invoked this function/trigger".
What I have is this:
create table folder(
id int unsigned not null auto_increment PRIMARY KEY ,
name varchar(100) not null ,
parentId int unsigned not null
) ;
It's a hierarchical folder structure. A folder has a name and possibly a parent folder (if not then parentId is zero).
When a folder is deleted I need to change the parentId of all subfolders which were inside it, so that they don't become children of a nonexistent folder.
It's rather simple (almost trivial):
CREATE DEFINER=root#localhost TRIGGER onFolderDelete after delete ON folder
FOR EACH ROW update folder set parentId=0 where parentId=old.id ;
However, such a simple trigger is not allowed by MySQL because, as I said above, you cannot update a table inside its own trigger.
Is there any way of implementing such a trigger by emulating its effects in some way??
P.S.: Please dont suggest sending both statements in sequence (the DELETE and the UPDATE). That's obviously the last solution if nothing else is possible.
Edit:
I'm using MyISAM engine (performance reasons) so I can't use foreign keys.

Can't you add a foreign key with ON DELETE SET NULL (or DEFAULT) ?
UPDATE (DEFAULT is still not implemented;SET NULL is the only option...)
So you will have something like
create table folder(
id int unsigned not null auto_increment PRIMARY KEY ,
name varchar(100) not null ,
parentId int unsigned null ,
FOREIGN KEY(parentId) REFERENCES folder(id) ON UPDATE CASCADE ON DELETE SET NULL
) ;

Related

Why are there are two rows in MariaDB database violating unique constraint?

I have written an application in Javascript which inserts data into two tables via a connection to a MariaDB server.
There should be a 1:1 correspondance between the rows in these tables when first running the application.
One table stores (simulated) data about properties, the other table stores data about prices. There should be 1 price for each property. At a later date, the price might change, so there could be more than one entry for the price, but this cannot happen when the application is first run. These entries also cannot be in violation of a unique index - but they are.
Perhaps I have misconfigured something in MariaDB? Here is the code which generates the tables.
drop table if exists property_price;
drop table if exists property;
create table property
(
unique_id bigint unsigned not null auto_increment primary key,
web_id bigint unsigned not null,
url varchar(256),
street_address varchar(256),
address_country varchar(64),
property_type varchar(64),
num_bedrooms int,
num_bathrooms int,
created_datetime datetime not null,
modified_datetime datetime not null
);
create table property_price
(
property_unique_id bigint unsigned not null,
price_value decimal(19,2) not null,
price_currency varchar(64) not null,
price_qualifier varchar(64),
added_reduced_ind varchar(64),
added_reduced_date date,
created_datetime datetime not null
);
alter table property_price
add constraint fk_property_unique_id foreign key(property_unique_id)
references property(unique_id);
alter table property
add constraint ui_property_web_id
unique (web_id);
alter table property
add constraint ui_url
unique (url);
alter table property_price
add constraint ui_property_price
unique (property_unique_id, price_value, price_currency, price_qualifier, added_reduced_ind, added_reduced_date);
Below is a screenshot from DBeaver showing that a select statement returns two identical rows.
I don't understand why the unique constraint appears to be violated. The constraint does sometimes work, because if I run my application again, it fails because it attempts to insert a duplicate row which already exists in the DB. (Not the same as the one shown below.)
Can anyone point me in the right direction as to how I might debug this?
MariaDB permits multiple values on columns which form part of a unique constraint.
My solution would be to put the logic for checking for duplicate rows into the application, rather than this being on the database side. Essentially this means the unique constraint is not being used.

MySQL auto assign foreign key ID

I have a main table called results. E.g.
CREATE TABLE results (
r_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
r_date DATE NOT NULL,
system_id INT NOT NULL,
FOREIGN KEY (system_id) REFERENCES systems(s_id) ON UPDATE CASCADE ON DELETE CASCADE
);
The systems table as:
CREATE TABLE systems (
s_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
system_name VARCHAR(50) NOT NULL UNIQUE
);
I'm writing a program in Python with MySQL connector. Is there a way to add data to the systems table and then auto assign the generated s_id to the results table?
I know I could INSERT into systems, then do another call to that table to see what the ID is for the s_name, to add to the results table but I thought there might be quirk in SQL that I'm not aware of to make life easier with less calls to the DB?
You could do what you describe in a trigger like this:
CREATE TRIGGER t AFTER INSERT ON systems
FOR EACH ROW
INSERT INTO results SET r_date = NOW(), system_id = NEW.s_id;
This is possible only because the columns of your results table are easy to fill in from the data the trigger has access to. The auto-increment fills itself in, and no additional columns need to be filled in. If you had more columns in the results table, this would be harder.
You should read more about triggers:
https://dev.mysql.com/doc/refman/8.0/en/create-trigger.html
https://dev.mysql.com/doc/refman/8.0/en/triggers.html

MySQL : Storing directory structure and query it

I need to be able to store files, directories within MySQL, but I don't know how to do it so I can also also have a fairly efficient query to get data from the table for example /swatcat/superscecret.txt
My initial thought was :
DROP TABLE IF EXISTS objects;
CREATE TABLE objects
(
ID INT NOT NULL AUTO_INCREMENT,
filename VARCHAR(200) NOT NULL,
objectTypeID INT NOT NULL,
parentID INT,
PRIMARY KEY (ID),
FOREIGN KEY (objectTypeID) REFERENCES objectTypes(ID)
);
Object type is either a file or directory:
CREATE TABLE objectTypes
(
ID INT NOT NULL AUTO_INCREMENT,
name VARCHAR(200) NOT NULL UNIQUE,
PRIMARY KEY (ID)
);
My thought process was parentID would be the directory that the file or directory resides in... The issue though is finding /swatcat/supersecret.txt and what if I decide to rename swatcat to test101 then how do I create a query that could cope with it?
Plan A: Have the directory path in a row in a separate table from the filename.
Plan B: Like Plan A, but with the path being multiple rows in a "hierarchical" arrangement in the new table.
Plan C: Leave the "dir/fn" as is, then use LIKE or RLIKE to locate a row, then use SUBSTRING_INDEX (or REGEXP_REPLACE in MariaDB) to substitute.

Need comments on MySQL events

SET GLOBAL event_scheduler = ON;
CREATE TABLE question(
qid INT AUTO_INCREMENT PRIMARY KEY,
name CHAR(30) NOT NULL,
text CHAR(100) NOT NULL,
variation BOOLEAN NOT NULL,
url CHAR(100) NOT NULL UNIQUE,
expired TIMESTAMP NOT NULL
);
CREATE TABLE alternativ(
aid INT AUTO_INCREMENT PRIMARY KEY,
name CHAR(30) NOT NULL,
text CHAR(50) NOT NULL,
number_chosen INT,
qid INT NOT NULL
);
ALTER TABLE alternativ
ADD FOREIGN KEY (qid)
REFERENCES question(qid);
CREATE EVENT delete_expired
ON SCHEDULE
EVERY 1 DAY
DO
DELETE FROM alternativ WHERE alternativ.qid IN (SELECT qid FROM question WHERE question.expired<CURRENT_TIMESTAMP)
DELETE FROM question WHERE question.expired < CURRENT_TIMESTAMP;
My question is: Should this event work with the specified database? I have tried, but it dosen't seem to work. The idea is that the database itself will delete questions that has expired. Help would much be appreciated.
If you want to specify multiple statements within the event body, you will need to wrap them within a compound statement block such as BEGIN ... END (in order to make such a command work, one must configure one's client to use an alternative statement delimiter in order that it does not think the first encountered semicolon terminates the CREATE EVENT statement—in the mysql command-line tool, one can use the DELIMITER command):
DELIMITER ;;
CREATE EVENT ... DO BEGIN
DELETE ... ;
DELETE ... ;
END ;;
DELIMITER ;
That said, one can delete from multiple tables with a single DELETE command using the multiple-table syntax:
DELETE alternativ, question
FROM alternativ JOIN question USING (qid)
WHERE question.expired < CURRENT_TIMESTAMP
However, all that said, you might fare better specifying foreign key constraints that cascade record deletions:
FOREIGN KEY (qid) REFERENCES question(qid) ON DELETE CASCADE
Then, one only need DELETE the referenced record (i.e. in the question table) and MySQL will delete the referencing records (i.e. in the alternativ table) for you.

Reverse of ON DELETE CASCADE

Suppose I have the following schema:
CREATE TABLE `users` (
`id` int(10) unsigned auto_increment,
`historyId` varchar(255),
PRIMARY KEY (`id`)
);
CREATE TABLE `histories` (
`id` int(10) unsigned auto_increment,
`history` TEXT,
PRIMARY KEY (`id`)
);
A User only ever has one History, and the purpose of not having histories point to users is that many other tables (not mentioned in this schema) also have histories.
What's the simplest way to make it so that deleting a User will also delete its History?
You can use trigger like this:
DELIMITER $$
CREATE TRIGGER delete_user_history_on_delete_user
AFTER DELETE ON `users`
FOR EACH ROW
BEGIN
DELETE FROM `histories` WHERE id = old.historyId;
END$$
DELIMITER ;
I don't think you can use a foreign key to cascade deletes here, because the data types don't match. You have VARCHAR(255) in one table, and INT(10) in the other. (What's up with that?)
I think you'll have to use either a trigger or a stored procedure, neither of which is entirely satisfactory. Triggers are theoretically the safest, in that client code can't sneak around them. (Client code could just avoid calling a stored procedure.) But there are APIs that don't activate MySQL triggers.
MySQL triggers are activated by SQL
statements only. They are not
activated by changes in tables made by
APIs that do not transmit SQL
statements to the MySQL Server
If User-histories it's a 1-1 relation you can put the constraint in the users table and not in the 'hisories' one