Syntax error in converting SQL Server trigger to MySQL - mysql

I have to convert SQLServer trigger to MySQL. I was trying to recreate it in MySQL but the thing is that I get syntax error every time I try different combinations and I don't know what's not working and why.
I'm creating cinema database and the following trigger is started when an entry is removed from the Hall table. Suppose a hall is being renovated and we have to move the performances in that hall to other locations. In this case, they are moved to a hall with an index 1 smaller than the distant hall.
Here is SQLServer query:
CREATE TRIGGER tDeleteHall ON Hall
FOR DELETE
AS
BEGIN
DECLARE #id int
SELECT #id = id_hall FROM deleted
UPDATE Spectacle set id_hall = #id - 1
END
DELETE FROM Hall WHERE id_hall = 3;
MySQL code
CREATE TRIGGER tDeleteHall BEFORE DELETE ON Hall
DECLARE #id int
SELECT #id = id_hall FROM deleted
UPDATE Spectacle set id_hall = #id - 1
END
DELETE FROM Spectacle WHERE id_hall = 3;
The error I'm getting:
ERROR: 1064: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'DECLARE #id int
SELECT #id = id_sala FROM deleted
UPDATE Spectacle set id_hall = ' at line 2

It's not totally clear what this trigger is supposed to do. But here's a MySQL trigger that I think does the same thing.
CREATE TRIGGER tDeleteHall BEFORE DELETE ON Hall
FOR EACH ROW
BEGIN
UPDATE Spectacle SET id_hall = OLD.id_hall - 1;
END
MySQL does not have the deleted pseudo-table. Instead, it uses OLD.<column> to refer to the deleted row, or in INSERT and UPDATE triggers, use the NEW.<column> to refer to the new row version.
MySQL's triggers are always row triggers, not statement triggers as in SQL Server. Nevertheless, MySQL triggers require the FOR EACH ROW syntax. Perhaps someday they will also support statement triggers.
You don't need to DECLARE a local variable for the example you show. But if you do use a local variable, know that unlike SQL Server, local variables in MySQL routines must not use the # sigil.
MySQL has two types of variables:
Local variables, which do not use the # sigil. These are created with DECLARE, and assigned a data type. They are local to the code block they are declared in.
User-defined session variables, which do use the # sigil. You can use these inside triggers, but they are not local. In other words, the value you set will remain set in your session after the trigger is done. These variables are created when you set them, so you don't declare them with DECLARE. They don't have a data type.
Statements inside the code block must be terminated with ;. This creates an ambiguity because the CREATE TRIGGER statement itself needs to be terminated. But if you run this in a client that assumes that ; terminates the CREATE TRIGGER, and there are ; characters in the body of the trigger, it becomes confused. The solution is to use DELIMITER to change the terminator of the CREATE TRIGGER, then you can use ; inside the body. You should read https://dev.mysql.com/doc/refman/8.0/en/stored-programs-defining.html
On the other hand, if you execute CREATE TRIGGER using a query interface that only processes the input one statement at a time anyway, there's no ambiguity, so you don't need to change the DELIMITER. Examples would be most query APIs.
Stored routines in MySQL are quite different from SQL Server. You need to make an effort to read the documentation and study examples. It's not an efficient use of your time or ours to try to learn complex syntax via Stack Overflow.

Related

Microsoft SQL to MySQL trigger

I need help with converting MS SQL command for trigger creation to similar command in MySQL. It works fine in MS SQL but I know nothing of MySQL.
The command is as it follows:
USE [Project]
GO
/****** Object: Trigger [General].[TR_tblFirmaZaposleni_U] Script Date: 7.1.2014 17:47:35 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER [General].[TR_tblFirmaZaposleni_U]
ON [General].[tblFirmaZaposleni]
FOR UPDATE
AS UPDATE [General].[tblFirmaZaposleni]
SET [A_tblFirmaZaposleniUpdatedBy] = SUBSTRING(SUSER_SNAME(),(CHARINDEX('\',SUSER_SNAME()) + 1),LEN(SUSER_SNAME())), [A_tblFirmaZaposleniUpdatedOn] = GetDate()
FROM [General].[tblFirmaZaposleni] s
INNER JOIN [INSERTED] i ON s.[ZaposleniID] = i.[ZaposleniID]
INSERT INTO ProjectAudit.[General].[tblFirmaZaposleni] SELECT 'B','U',* FROM DELETED
INSERT INTO ProjectAudit.[General].[tblFirmaZaposleni] SELECT 'A','U',* FROM INSERTED
GO
Despite their apparent similarities, SQL Server and MySQL are actually very different products: each of which deviate from the SQL standards in different ways. Indeed, the divergence significantly widens as one delves deeper into their features and functionality.
Thus there is not a 1:1 mapping between what you have in SQL Server and what can be done in MySQL. You would do well to read around on the differences between the systems and how migrations can best be managed. It may well be that significant elements of application design should be engineered specifically to take advantage (or overcome lacks) of features or functionality that exist in one system or the other.
To the extent that you do keep the databases similar, an equivalent set of commands in MySQL would look something like this:
USE Project
DELIMITER ;;
CREATE TRIGGER TR_tblFirmaZaposleni_U
BEFORE UPDATE ON tblFirmaZaposleni
FOR EACH ROW BEGIN
SET NEW.A_tblFirmaZaposleniUpdatedBy = SUBSTRING_INDEX(CURRENT_USER, '#', 1),
NEW.A_tblFirmaZaposleniUpdatedOn = CURRENT_DATE;
INSERT INTO ProjectAudit.tblFirmaZaposleni VALUES
('B','U', OLD.colA, OLD.colB, OLD.colC, ....),
('A','U', NEW.colA, NEW.colB, NEW.colC, ....);
END;;
It may be worth noting that, like SUSER_SNAME() in SQL Server, CURRENT_USER in MySQL returns the user as which the trigger is running, rather than the user as which the client was authenticated. You may wish to use MySQL's USER() function instead.
It may also be worth noting that MySQL offers Automatic Initialization and Updating for TIMESTAMP and DATETIME, which saves you having to set such values within a trigger.
As for the initial SET statements (which have no effect on the trigger), MySQL has no equivalent to SQL Server's ANSI_NULLS setting but you can set its ANSI_QUOTES SQL mode if you wish to achieve the same effect as setting SQL Server's QUOTED_IDENTIFIER.

Syntax exception on trigger with multiple statements with MySQL and JDBC

I am trying to create a trigger that performs multiple operations in MySQL 5.5.28 with InnoDB.
I have two tables, "test" and "test_watcher": changes to the first are recorded in the watcher table with the help of triggers. The last trigger needs to perform 2 operations on DELETE, it works in MySQL Workbench (with DELIMITER) but doesn't if I create it with JDBC.
CREATE TRIGGER `AD_test_FER` AFTER DELETE
ON `test`
FOR EACH ROW
BEGIN
-- if it's been inserted, modified and deleted but never synced,
-- the revision is NULL: no one needs to know about it
DELETE FROM test_watcher WHERE pk = OLD.id AND revision IS NULL;
-- if it has been synced already, we just update the flag
UPDATE test_watcher SET flag = -1 WHERE pk = OLD.id;
END;
I keep getting com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax.
I know it works with DELIMITER $$ in Workbench, but JDBC doesn't support it.
I've achieved it in PostgreSQL and would post the code if necessary.
This behavior might be caused by the connection property allowMultiQueries=true. My guess is this property will make MySQL break up queries on the ; as a query separator and then execute those as separate queries, essentially breaking your trigger creation code.
As you said in a - now deleted - answer that adding allowMultiQueries=true actually solved the problem (contrary to my expectiation), the problem might actually be the last ; in your query. So another thing to check is if the problem goes away by removing the last ; (in END;) in your script (and not using allowMultiQueries=true). Some database don't consider ; to be valid at the end of a statement (as it is actually a delimiter to separate statements).
(this answer is based on my comment above)
If JDBC does not support delimiters (DELIMITER is a client command), then execute these two statements separately - one by one.

MySQL: trigger with clause "instead of update"

I'm trying to create a database with history in mind (experience shows you'll have to do this one day or another).
I've asked here database-design-how-to-handle-the-archive-problem but there's no better anser than the link here.
My problem is about where to do the code and technically, how (MySQL gives me headaches). First I've started doing this in Php: before doing any insert, duplicate the record mark it as "obsolete" then modify the record.
But there's a dependency problem (manytomany and manytoone associations must be updated as well) which implies coding (one way or another) all the dependancies and updates that come with the tables (which is not acceptable).
So I'm thinking about doing all the work on the database server side. This would greatly simplify my Php code.
The problem is that I have to "archive" the current record before modifying it. To do so, the code must be in a trigger "before update".
Here's my code:
DELIMITER ;;
DROP TRIGGER IF EXISTS produit_trigger_update_before;
CREATE TRIGGER produit_trigger_update_before
BEFORE UPDATE ON produit
FOR EACH ROW BEGIN
/* */
INSERT INTO produit SET
id_origine = OLD.id_origine,
date_v_creation = OLD.date_v_creation,
date_v_start = OLD.date_v_debut,
date_v_end = NOW(),
...
last_record = OLD.last_record;
/* Dependancies : */
SET #last=LAST_INSERT_ID();
UPDATE categorie_produit SET id_produit=#last
WHERE id_produit = OLD.id;
UPDATE produit_attribut SET id_produit=#last
WHERE id_produit = OLD.id;
END;;
DELIMITER ;;
If I get this code working, all my problems are gone. But damn it, it's not working:
mysql> update produit set importance=3;
ERROR 1442 (HY000): Can't update table 'produit' in stored function/trigger because it is already used by statement which invoked this stored function/trigger.
mysql> Bye
In this page there's a working sample, which uses INSTEAD OF UPDATE clause in the trigger. MySQL doesn't seem to support this.
So my question is both conceptual (= have you any other "principle" that could work) and/or technical (= can you make this trigger work).
If I get this code working, all my problems are gone. But damn it, it's not working:
As a rule you can't have a trigger on table A trigger inserts into table A - since that could cause an endless loop. (Trigger mutation in Oracle terms)
Personally I would not do this using triggers. Triggers can do "audit logging" - but this is not what you want here.
I suggest you solve it programatically - either with a PHP function or a MySQL stored procedure (whatever your preference) that you call something like "ModifyProduit".
The code would then do basically what you have the trigger above do. (It might be easier to just have the code set date_v_end on the current row, and then insert a completly new row. That way you don't have to mess around with updating your referenced tables)
you can do history of a table with an auxiliary table like this (i've done this for many tables on mysql and the speed is very good):
table produit_history has the same structure as produit + 2 additional columns: "history_start DATETIME NOT NULL" and "history_stop DATETIME DEFAULT NULL".
there are 3 triggers on produit table:
AFTER INSERT: in this trigger there is a simple insert into produit_history of the same data with history_start = NOW() and history_stop = NULL (NULL means the current row is valid)
AFTER UPDATE: this trigger performs two queries. The first is un update like this:
UPDATE produit_history set history_stop = NOW() WHERE id_origine = OLD.id_origine AND history_stop IS NULL;
The second query is an insert identical to the one in the AFTER INSERT trigger.
AFTER DELETE: this triggers there is a simple update which is identical to the one in the AFTER UPDATE.
You can then query this history table and obtain snapshots at whatever time you're interested in with the following where condition:
WHERE (history_start &lt= "interesting_time" AND (history_stop IS NULL OR history_stop &gt "interesting_time"))

Is it possible to add a check constraint that calls a user defined function in a different database?

I'm trying to add a user defined function that actually calls the SQL# CLR function RegEx_IsMatch to a column, but I get this error:
A user-defined function name cannot be prefixed with a database name in this context.
But if the function is in a different db, I'm not sure how to do this.
You shouldn't need to do this in a CHECK CONSTRAINT. An AFTER INSERT, UPDATE Trigger should be able to provide the same functionality as the CHECK CONSTRAINT. You just need to cancel the INSERT or UPDATE operation if the desired condition is (or is not) met. And this is easily done simply by issuing a ROLLBACK, which works due to Triggers existing within the transaction that is the DML statement itself. Hence, just do something along the lines of:
CREATE TRIGGER dbo.trCheckSomeField
ON dbo.SomeTable
AFTER INSERT, UPDATE
AS
SET NOCOUNT ON;
IF (EXISTS(
SELECT *
FROM Inserted ins
WHERE Utility.SQL#.RegEx_IsMatch(ins.SomeField, ...) = 0
)
)
BEGIN;
ROLLBACK TRAN;
RAISERROR('Your data suck!', 16, 1);
RETURN;
END;
Never tried it, but maybe you can create a helper function in the same DB which in turn calls into the other DB?
This may however fail because check constraints are supposed to be determinstic AFAIR, and calls into other databases aren't deterministic. In general it doesn't seem like a good idea to call into another DB, even if it is only for a regex check. Why not add the CLR assembly to this DB as well?

Can a VIEW in MySQL read from one table and store into another?

Does MySQL have anything like aufs? I want to use my production tables read-only and store changes into another table or database if possible.
Thank you!!
Does MySQL have anything like aufs?
No
Option1 - Use replication
You can put the (read-only) production database on one server and the change database on another server (can be on the same machine).
If you set the change database as a slave of the production database, all changes that happen (from some other source perhaps) will be replicated to your change database.
Changes in the change database will not be passed on to the production database.
The fact that the change database is not in sync with the production database may cause problems with the (one way) synchronization but you'd have to experiment with that.
See: http://dev.mysql.com/doc/refman/5.0/en/replication.html
Option 2 - dirty hack with triggers
You can put a trigger on your production table and have the trigger diverge the data to somewhere else.
warning this is a filthy hack and not recommend use of triggers at all.
DELIMITER $$
CREATE TRIGGER bu_prod_table1_each BEFORE UPDATE ON prod_table1 FOR EACH ROW
BEGIN
UPDATE change_table1 c SET c.field1 = NEW.field1, c.field2 = NEW.field2
WHERE c.id = OLD.id;
IF NEW.id <> OLD.id THEN
UPDATE change_table1 SET c.id = NEW.id WHERE c.id = OLD.id;
END IF;
/*reverse the changes in the production table*/
SET NEW.id = OLD.id;
SET NEW.field1 = OLD.field;
.....
END $$
DELIMITER ;
You'd have to create these triggers for UPDATE, DELETE, and INSERT.
And you'd have to put triggers on each and every table in production.
In other SQL implementations (e.g. SQL Server), an INSTEAD OF trigger on a VIEW can achieve the goal of reading from one table and writing to another. However, mySQL does not to my knowledge support INSTEAD OF triggers.