I'm trying to create a MySQL Before Insert trigger with the following code which would do what I want it to do if I could find a way to execute the prepared statement generated by the trigger.
Are the any alternative ways to execute prepared statements from within triggers? Thanks
BEGIN
SET @CrntRcrd = (SELECT AUTO_INCREMENT FROM information_schema.TABLES
WHERE TABLE_SCHEMA=DATABASE()
AND TABLE_NAME='core_Test');
SET @PrevRcrd = @CrntRcrd-1;
IF (NEW.ID IS NULL) THEN
SET NEW.ID = @CrntRcrd;
END IF;
SET @PrevHash = (SELECT Hash FROM core_Test WHERE Record=@PrevRcrd);
SET @ClmNms = (SELECT CONCAT('NEW.',GROUP_CONCAT(column_name
ORDER BY ORDINAL_POSITION SEPARATOR ',NEW.'),'')
FROM information_schema.columns
WHERE table_schema = DATABASE()
AND table_name = 'core_Test');
SET @Query = CONCAT("SET @Query2 = CONCAT_WS(',','",@PrevHash,"','", @CrntRcrd, "',", @ClmNms, ");");
PREPARE stmt1 FROM @Query;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
SET NEW.Hash = @Query2;
END
UPDATE / CLARIFICATION: The data will be stored in a table as below.
+------------+-----+------+----------------+
| Record (AI)| ID | Data | HASH |
+------------+-----+------+----------------+
| 1 | 1 | ASDF | =DHFBGKJSDFHBG | (Hash Col 1)
| 2 | 2 | NULL | =UEGFRYJKSDFHB | (Hash Col 1 + Col 2)
| 3 | 1 | VBNM | =VKJSZDFVHBFJH | (Hash Col 2 + Col 3)
| 4 | 4 | TYUI | =KDJFGNJBHMNVB | (Hash Col 3 + Col 4)
| 5 | 5 | ZXCV | =SDKVBCVJHBJHB | (Hash Col 4 + Col 5)
+------------+-----+------+----------------+
On each insert command the table will generate a Hash value for that row by appeding the pervious row's Hash value to a CONCAT() of the entire new row, then re-hashing the entire string. This will create a running record of Hash values for auditing purposes / use in another part of the application.
My constraints are that this has to be done before the INSERT as rows cannot be updated afterwards.
UPDATE: I'm currently using the following code until I can find a way to pass the column names to CONCAT dynamically:
BEGIN
SET @Record = (
SELECT AUTO_INCREMENT FROM information_schema.TABLES
WHERE TABLE_SCHEMA=DATABASE()
AND TABLE_NAME='core_Test' #
);
SET @PrevRecrd = @Record-1;
IF (new.ID IS NULL) THEN
SET new.ID = @Record;
END IF;
SET @PrevHash = (
SELECT Hash FROM core_Test #
WHERE Record=@PrevRecrd
);
SET new.Hash = SHA1(CONCAT_WS(',',@PrevHash, @Record,
/* --- UPDATE TABLE COLUMN NAMES HERE (EXCLUDE "new.Record" AND "new.Hash") --- */
new.ID, new.Name, new.Data
));
END
解决方案
The short answer is that you can't use dynamic SQL in a TRIGGER.
I'm confused by the query of the auto_increment value, and assigning a value to the ID column. I don't understand why you need to set the value of the ID column. Isn't that the column that is defined as the AUTO_INCREMENT? The database will handle the assignment.
It's also not clear that your query is guaranteed to return unique values, especially when concurrent inserts are run. (I've not tested, so it might work.)
But the code is peculiar.
It looks as if what you're trying to accomplish is to get the value of a column from the most recently inserted row. I think there are some restrictions on querying the same table the trigger is defined on. (I know for sure there is in Oracle; MySQL may be more liberal.)
If I needed to do something like that, I would try something like this:
SELECT @prev_hash := t.hash AS prev_hash
FROM core_Test t
ORDER BY t.ID DESC LIMIT 1;
SET NEW.hash = @prev_hash;
But again, I'm not sure this will work (I would need to test). If it works on a simple case, that's not proof that it works all the time, in the case of concurrent inserts, in the case of an extended insert, et al.
I wrote the query the way I did so that it can make use of an index on the ID column, to do a reverse scan operation. If it doesn't use the index, I would try rewriting that query (probably as a JOIN, to get the best possible performance.
SELECT @prev_hash := t.hash AS prev_hash
FROM ( SELECT r.ID FROM core_Test r ORDER BY r.ID DESC LIMIT 1 ) s
JOIN core_Test t
ON t.ID = s.ID
Excerpt from MySQL 5.1 Reference Manual
E.1 Restrictions on Stored Programs
SQL prepared statements (PREPARE, EXECUTE, DEALLOCATE PREPARE) can be
used in stored procedures, but not stored functions or
triggers. Thus, stored functions and triggers cannot use
dynamic SQL (where you construct statements as strings and then
execute them).
[sic]