Creating an immutable field in mysql - mysql

I'd like to make a TIMESTAMP field DEFAULT CURRENT_TIMESTAMP, for 'creation time' purpose. But if someone or somehow something changes that TIMESTAMP, my data won't be consistent.
Is there a way I can ensure it won't change unless I delete the row and reinsert it, other than application level?
With the suggested answer provided, i could work around with something like this
CREATE TRIGGER consistency1 BEFORE UPDATE ON table1
FOR EACH ROW
BEGIN
IF NEW.creationtime != OLD.creationtime THEN
SET NEW.creationtime = OLD.creationtime;
END IF;
END;

Since my comment has been appreciated, here's the extended version.
I personally don't think that it's possible.
Anyway, there are a couple of things you can try:
Make sure that only your application can write on the database
Write a trigger like this (pseudocode!)
create trigger prevent_change_timestamp on tbl_name
before update
#fetch old row value
#verify if the timestamp field has been changed
#raise an error (any SQL error will do)
Or like this
create trigger revert_change_timestamp on tbl_name
after update
#fetch pre-change row value
#update the row with the "old" value in place of the new one
I'd personally go with the 3rd option, if possible. Anyway, the 2nd one is good too. I'd not rely on the 1st option unless necessary (eg: no access to trigger functionality)
More info here: reference

It's funny in a way that database apps don't offer this functionality as standard: not only for a "created" timestamp field, but for things like autoincrement id fields, and any miscellaneous values which you may want to set on creating a record and then never allow to be changed... wonder what the rationale is?

What you can do here is, you can write a TRIGGER on the table when a row is being updated. In that trigger, you can compare the old and new values, and if they are different then you can just overwrite the new value with the old one.

I tried this in MySQL 5.1 and got an error
DELIMITER //
CREATE TRIGGER member_update_0
-> AFTER UPDATE ON members
-> FOR EACH ROW
-> BEGIN
-> IF NEW.id != OLD.id THEN
-> SET NEW.id = OLD.id;
-> END IF;
-> END;//
ERROR 1362 (HY000): Updating of NEW row is not allowed in after trigger
The same trigger with AFTER replaced by BEFORE is accepted;
to me, this is a counter-intuitive way to do it, but it works
delimiter ;
UPDATE members SET id=11353 WHERE id=1353;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 1 Changed: 0 Warnings: 0

It is actually possible to do this very neatly if you are using InnoDB.
Create another table with just one column. That column should have a foreign key (hence the innodb requirement in this solution) that points to the immutable column of the original table in question.
Put a restriction like "ON UPDATE RESTRICT".
In summary:
CREATE TABLE original (
....
immutable_column ...
INDEX index1(immutable_column)
....
) ENGINE=INNODB;
CREATE TABLE restricter (
.....
col1,
INDEX index2(col1),
FOREIGN KEY (col1) REFERENCES original (immutable_colum) ON UPDATE RESTRICT ON DELETE CASCADE
) ENGINE=INNODB;

Taking the idea a step further (for those of us still stuck with a legacy version of MySQL) you can have BOTH a protected & defaulted create_stamp AND an auto-updating update_stamp as follows:
If you have a table such as
CREATE TABLE `csv_status` (
`id` int(11) NOT NULL primary key AUTO_INCREMENT,
`create_stamp` datetime not null,
`update_stamp` timestamp default current_timestamp on update current_timestamp,
`status` enum('happy','sad') not null default 'happy'
);
Then you can define these triggers on it
drop trigger if exists set_create_stamp ;
create definer = CURRENT_USER trigger set_create_stamp BEFORE INSERT on
csv_status for each row
set NEW.create_stamp = now();
drop trigger if exists protect_create_stamp ;
delimiter //
create definer = CURRENT_USER trigger protect_create_stamp BEFORE UPDATE on
csv_status for each row
begin
if NEW.create_stamp != OLD.create_stamp then
set NEW.create_stamp = OLD.create_stamp;
end if;
end;//
delimiter ;

Related

MySQL CHECK constraint to ensure record does not refer to itself with autoincrement id?

I have a SQL table that can reference another record in the table as its parent but should not reference itself. I have attempted to enforce this with a CHECK constraint but my attempts have failed as the id is an auto-increment column. Is there any other way to ensure that parent_id <> id?
My current attempt, which fails with error Check constraint 'not_own_parent' cannot refer to an auto-increment column. (errno 3818):
CREATE TABLE `content` (
`id` serial PRIMARY KEY NOT NULL,
`item_id` int NOT NULL,
`nested_item_id` int,
`block_id` int,
`order` int NOT NULL,
CONSTRAINT not_own_parent CHECK (nested_item_id <> id)
);
Here's a demo of using a trigger to cancel an insert that violates the condition you describe. You must use an AFTER trigger because in a BEFORE trigger the auto-increment value has not yet been generated.
mysql> delimiter ;;
mysql> create trigger t after insert on content
-> for each row begin
-> if NEW.nested_item_id = NEW.id then
-> signal sqlstate '45000' set message_text = 'content cannot reference itself';
-> end if;
-> end;;
mysql> delimiter ;
mysql> insert into content set item_id = 1, nested_item_id = 1, `order` = 1;
ERROR 1644 (45000): content cannot reference itself
mysql> insert into content set item_id = 1, nested_item_id = 2, `order` = 1;
Query OK, 1 row affected (0.01 sec)
Don't put this kind of thing in a constraint. For one thing, you can't do it directly in MySql. You'd have to use a trigger or something.
Instead:
write your CRUD code carefully, so it avoids generating incorrect rows. You have to do that anyway.
write a little program called "database_consistent" or something. Have it run a bunch of queries looking for any errors like the one you're trying to avoid. Have it send emails or SMSs if it finds problems. Run it often during development and at least daily in production.
One way to control auto-generated live values is by using triggers to manage new values.
For example, create instead of insert trigger to control newly generated ID. In triggers, you can make decisions based on the new value.

Cannot update value on insert if salary is greater than

I have a problem creating a trigger for a basic table that will check on insert if one of the values inserted is bigger than 3000 and replace it with 0. It throws this error:
Can't update table 'staff' in stored function/trigger because it is already used by statement which invoked this stored function/trigger
The structure of the table is very simple:
CREATE TABLE `staff` (
`ID` int(11) NOT NULL,
`NAZWISKO` varchar(50) DEFAULT NULL,
`PLACA` float DEFAULT NULL
)
And the trigger for it looks like this:
BEGIN
IF new.placa >= 3000 THEN
UPDATE staff SET new.placa = 0;
END IF;
END
I don't understand fully what occurs here, but I suspect some recursion, but I am quite new to the topic of triggers and I have lab coming, so I want to be prepared for it.
MySQL disallows triggers from doing UPDATE/INSERT/DELETE against the same table for which the trigger executed, because there is too great a chance of causing an infinite loop. That is, in UPDATE trigger, if you could UPDATE the same table, that would cause the UPDATE trigger to execute, which would UPDATE the same table, and so on and so on.
But I guess you only want to change the value of placa on the same row being handled by the trigger. If so, just SET it:
BEGIN
IF new.placa >= 3000 THEN
SET new.placa = 0;
END IF;
END
Remember that you must use a BEFORE trigger when changing column values.

MySQL Auto Update Date when any row value is updated

I have an existing table with values already filled in. Last column is of type date.
My requirement is that if any value is updated, that row's corresponding date column should be automatically updated to the current date.
For example, if the table is as follows:
and if I change any of the values of col1 (78) /col2 (nvvb) /col3 (566) of say row no. 2 on date 18Nov2018, then the col4 of row 2 should automatically update to today's date of 18Nov2018 from existing 13-Oct-17.
I've checked existing topics by could not find an answer.
The closest I can find is this code, but it is not working for the above requirement:
create table if not exists my_table (
index1 char(32) not null primary key,
title varchar(50),
my_timestamp timestamp not null default current_timestamp on update current_timestamp
)
Any help will be greatly appreciated.
Thanks
First, you need to create code blocks for whatever code you are displaying in your question/answer by highlighting the lines of code and pressing ctrl-k
You will need a trigger that changes the date value in the row to the current date using the CURDATE() function. BEFORE UPDATE simply means apply the current block enclosed in "BEGIN... END" before the actual update call is made by MySQL. You access the row to be updated with NEW.<field>. To store a value in a particular tuple, use the SELECT ... INTO .... statement
DELIMITER $$
CREATE TRIGGER trigger_name
BEFORE UPDATE ON table_name
FOR EACH ROW
BEGIN
SELECT CURDATE() INTO NEW.`col4(date)`;
END $$
DELIMITER ;
For more info, see: https://dev.mysql.com/doc/refman/8.0/en/trigger-syntax.html
https://dev.mysql.com/doc/refman/8.0/en/ansi-diff-select-into-table.html
UPDATE: YOU CANNOT UPDATE WITHIN AN UPDATE TRIGGER, MySQL does not prevent an update trigger from being called by itself, nor does it prevent deadlock issues, so you cannot use this. Must use the method below.
Using the method described in the comments (if your version supports it):
CREATE TABLE last_mod (
last_modified_date DATE NOT NULL,
PRIMARY KEY (last_modified_date)
)
CREATE TABLE IF NOT EXISTS my_table (
my_date DATETIME NOT NULL,
INDEX par (my_date),
FOREIGN KEY (my_date) REFERENCES last_mod(mast_modified_date) ON UPDATE CASCADE
);
DELIMITER $$
CREATE TRIGGER trigger_name
BEFORE UPDATE ON my_table
FOR EACH ROW
BEGIN
UPDATE last_mod SET last_modified_date = CURDATE();
END $$
DELIMITER ;

How to create a Trigger within sql

I have been trying to create a Trigger, however my attempts have been unsuccessful. I seem to be getting an error (#1064), which I have no solution for. Can somebody explain or demonstrate any faults in the syntax.
Let me specify:
I have delivery_id as primary key in delivery table,
I also have delivery_id as a foreign key in entry_log table.
By comparing both id's(if true), will return a text referring to the output of the bit (either 0 or 1)
DELIMITER //
DROP TRIGGER IF EXISTS entry_trigger//
CREATE TRIGGER entry_trigger BEFORE INSERT ON entry_log
FOR EACH ROW
BEGIN
DECLARE #xentry VARCHAR(45)
DECLARE #inta bit
SET #inta = SELECT allowed
FROM delivery
WHERE delivery.delivery_id = entry_log.delivery_id;
CASE
when #inta = 0 then #xentry = 'Acces Denied'
when #inta = 1 then #xentry = 'Acces Allowed'
END CASE
INSERT INTO entry_log(entry_time,access_allowed) VALUES(now(),#xentry);
END
//
This is assuming that you use MySQL. In the body of the trigger you use
WHERE delivery.delivery_id = entry_log.delivery_id;
I think you want to compare to the entry_log entry that the trigger is running on, right? In that case you must use this syntax:
WHERE delivery.delivery_id = NEW.delivery_id;
see here for more examples.
UPDATE
I see that also you try to do an INSERT INTO entry_log within the TRIGGER. This will of course not work, because you would create an infinite recursive loop. Within the
body of the trigger you can do unrelated table access, but not into the table you are inserting. You can change the values to be inserted by the trigger by setting NEW.xyz = whatever
UPDATE 2
I doubt, that your CASE statement is correct. At least it must end with END CASE. You can use IF here, since you don't have many cases to address. If you must use CASE this post might help you: MYSQL Trigger set datetime value using case statement
UPDATE 3
I am not sure, but I think you need brackets around the variable setting statement. try this trigger definition:
DELIMITER //
DROP TRIGGER IF EXISTS entry_trigger//
CREATE TRIGGER entry_trigger BEFORE INSERT ON entry_log
FOR EACH ROW
BEGIN
SET #inta = (SELECT allowed
FROM delivery
WHERE delivery.delivery_id = NEW.delivery_id);
SET NEW.access_allowed = #inta;
SET NEW.entry_time = NOW();
END
//
Note, that this is written out of my head, so beware of syntax errors in my script.

How to create a CHECK constraint in MySQL that requires negative values?

I want to alter an integer column on a table to add a check constraint preventing the value from being zero or a positive number. For example:
CREATE TABLE example (id INTEGER)
ALTER TABLE example ADD CONSTRAINT chk_negID CHECK (id<0)
MySQL happily complies with these yet then allows the following:
INSERT INTO example VALUES (-1);
INSERT INTO example VALUES (1);
Are my constraints not actually being added? Is there a way to list constraints that have been added to a table after it was created?
It's not supported by mysql (even though it accepts it as a valid syntax)
The CHECK clause is parsed but ignored by all storage engines
http://dev.mysql.com/doc/refman/5.5/en/create-table.html
Here is something that would work, but may be hard to debug. This is a trigger:
DELIMITER $$;
CREATE TRIGGER my_trigger AFTER INSERT ON my_table
FOR EACH ROW
BEGIN
IF(OLD.id <= 0)
THEN
DELETE FROM my_table where id = OLD.id;
END IF;
END