MYSQL If statement in transaction causing error - mysql

I have the following MYSQL query:
START TRANSACTION;
SELECT sport_id INTO #a FROM sports WHERE sport_id = 2 FOR UPDATE;
UPDATE sports SET sport_name = 'Table Tennis' WHERE sport_id = #a;
if (#a > 1) then
COMMIT;
ELSE
ROLLBACK;
END IF;
The problem is that it returns an error at the if statement:
#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 'if (#a > 1) then COMMIT' at line 1
I've looked on stack overflow and there is an answer showing a similar query, written in pretty much the same way, but they are using the variable without an # symbol. Removing the # for my query does not resolve the issue.
This is just a test query to try out some transactions using MYSQL, hence why the query seems a little pointless. I'm a little stuck.

MySQL doesn't recognize a statement beginning with the keyword IF as as a valid SQL statement.
The IF statement works only in the context of a compound statement (that is, a block of statements enclosed between BEGIN and END. Currently, the compound statement is only supported in the context of a stored program (stored procedure, function or trigger.)
http://dev.mysql.com/doc/refman/5.5/en/begin-end.html
For testing, try...
DELIMITER //
CREATE PROCEDURE usp_test_transaction()
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK;
START TRANSACTION;
-- whatever DML operations and SELECT statements you want to perform go here
IF (1=1) THEN
COMMIT;
ELSE
ROLLBACK;
END IF;
END//
DELIMITER ;
CALL usp_test_transaction;
(NOTE: I am not advocating here that transactions be handled inside of stored procedure. My personal preference is to NOT do this, and instead handle transactions at a higher level. But the example above should work; I believe MySQL does support calling START TRANSACTION, COMMIT and ROLLBACK within the context of a stored procedure.)

Related

Unable to Run Query in MySQL syntax error unexpected

I'm running Workbench 5.2.47.
I have a long procedure I wrote with basic data checking. If a record did not exist in the database, the record would be inserted.
The procedure saved with no problems, but MySQL 5.5 throws an error when I try running it.
It is long, and has a lot of company sensitive data in it, or I would post it here.
I am trying to debug the procedure by executing small chunks of the code, but I can't seem to get Workbench to allow anything I try.
MySQL shows how to create a stored procedure in 5.1.5 Working with Stored Procedures.
Let me show you something very basic I am trying to write:
DELIMITER $$
DROP PROCEDURE IF EXISTS my_test;
CREATE PROCEDURE my_test()
BEGIN
SELECT * FROM Employees;
END $$
DELIMITER ;
With that, Workbench gives me the error, "syntax error, unexpected CREATE, expecting $end".
I don't understand that, but I need to get something done, so I am moving on.
I make a simpler query:
SET #Count=(SELECT Count(*) FROM tbl_object_users WHERE username='jp2code');
IF (#Count < 1) THEN
INSERT INTO tbl_object_users (username, date_time) VALUES ('jp2code', NOW());
END IF;
Again, I get an error, this time on my IF statement.
Next, I go into PhpMyAdmin to try running something from there using its database:
SET #Count=Count(id) FROM `tbl_object_users` WHERE `username`='jp2code';
It, too, tells me I have an error in my SQL syntax.
I did download and install the newest Workbench 6, but it did not solve the problem - and I did not like the interface, so I uninstalled it and went back to Workbench 5.2.
What is going on? SQL isn't that hard, so what is with these hurdles?
Problem with this:
DELIMITER $$
DROP PROCEDURE IF EXISTS my_test;
CREATE PROCEDURE my_test() ...
is that MySQL isn't seeing the semicolon at the end of the DROP PROCEDURE statement line as the end of the statement. This is because the preceding line told MySQL that the statement terminator was something other than a semicolon. You told MySQL that statements were going to be terminated with two dollar signs. So MySQL is reading the DROP PROCEDURE line, looking for the statement terminator. And the whole blob it reads is NOT a valid MySQL statement, it generates a syntax error.
The fix: either move the DROP PROCEDURE line before the DELIMITER $$ line; or terminate the DROP PROCEDURE statement with the specified delimiter rather than a semicolon.
The second problem you report is a syntax error. That's occurring because MySQL doesn't recognize IF as the beginning of a valid SQL statement.
The IF statement is valid only within the context of a MySQL stored program (for example, within a CREATE PROCEDURE statement.)
The fix: Use an IF statement only within the context of a MySQL stored program.
The third problem you report is also a syntax error. That's occurring because you don't have a valid syntax for a SET statement; MySQL syntax for SET statement to assign a value to user variable is:
SET #uservar = expr
MySQL is expecting an expression after the equals sign. MySQL is not expecting a SQL statement.
To assign a value to a user variable as the result from a SELECT statement, do the assignment within the SELECT statement, for example:
SELECT #Count := Count(id) FROM `tbl_object_users` WHERE `username`='jp2code'
Note that the assignment operator inside the SELECT statement is := (colon equals), not just =.
try this
DELIMITER $$
DROP PROCEDURE IF EXISTS my_test$$
CREATE PROCEDURE my_test()
BEGIN
SELECT * FROM `customer_to_pay`;
END $$
DELIMITER ;

Multiple statements Delphi TZquery (Zeos) error

im trying to make a multiple statement query like this :
// without the second insert the query works fine.
// i need 2 querys to work because later, i'll do inserts on different kind of tables.
// that's why i need 2 querys, not a single query which insert 2 records.
with ZQuery1 do
begin
SQL.Clear;
SQL.Add('insert into client (name,age) values ('+QuotedStr('john')+','+QuotedStr('20')+');');
SQL.Add('insert into client (name,age) values ('+QuotedStr('doe')+','+QuotedStr('21')+');');
ExecSQL;
end;
i got this error message : SQL Error: 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 'insert into client (name,age) values ('doe','21')' at line 2;
i already check the manual, The components TZQuery and TZUpdateSql (from zeos lib ) provide the possibility to execute multiple statements, internally.
EDIT [SOLVED]
Thank you GregD, after run several tests, transactions works fine for me!
that's how i use, to help others in the future.
try
ZConnection.AutoCommit := True;
ZConnection.StartTransaction;
With ZQuery Do
begin
SQL.Clear;
SQL.Add('insert into clients (name,age) values ('+QuotedStr('john')+','+QuotedStr('20')+')');
ExecSQL;
SQL.Clear;
SQL.Add('insert into clients (name,age) values ('+QuotedStr('doe')+','+QuotedStr('21')+')');
ExecSQL;
end;
ZConnection.Commit;
except
ZConnection.Rollback
end;
This is how AutoCommit property actually works in Zeos:
when AutoCommit is True, then the transactions are commited automatically after each executed SQL statement, but you can use the StartTransaction command explicitely to prevent this auto commiting, until you explicitely call Commit.
when AutoCommit is False, you should not call StartTransaction. Then the transaction is started automatically, but it will not commit automatically after every executed statement.
procedure StartTransaction The StartTransaction procedure starts a new transaction within the connected database. It should be only used when AutoCommit property is TRUE. Whenever you try to call it with AutoCommit set to false, an SInvalidOpInNonAutoCommit will be raised. This behaviour is expected, as StartTransaction should be used as a escape to the AutoCommit mode. When you call StartTransaction, the AutoCommit is "turned off", and then, when you call Commit or Rollback, the AutoCommit is "turned on" again. If you're working with AutoCommit set to false, new transactions are created automatically and you choose how you will close them (Commit or Rollback).
procedure Commit Commit current statements to the database. Should be used only in non-AutoCommit mode (where every statement is auto-commited, making this procedure useless) or when you are in AutoCommit mode and want to finish a transaction opened by StartTransaction procedure. Commiting finishes the current transaction, if there's any. If you don't want to save your satatements to the database, you should use the Rollback procedure.
procedure Rollback Rolls back all previous statements in current transaction. Should be used only in non-AutoCommit mode (where every statement is auto-commited, making this procedure useless) or when you are in AutoCommit mode and want to finish a transaction opened by StartTransaction procedure. The Rollback finishes the current transaction, if there's any. If you don't want to loose your satatements, you should use the Commit procedure.
I have no idea about Zeos and multiple statements, but that's not really the problem here. You've caused a major security issue with your query (SQL injection) and a slow method of performing them at all (concatenated strings that can't be cached and reused).
If you properly stop using string concatenation to form your queries, and use parameterized statements instead, you don't need to worry about the multiple statements at all:
with ZQuery1 do
begin
SQL.Clear;
SQL.Add('insert into client (name,age)');
SQL.Add('values (:Name, :Age);'
ParamByName('Name').AsString := 'John';
ParamByName('Age').AsInteger := 20;
ExecSQL;
ParamByName('Name').AsString := 'Doe';
ParamByName('Age').AsInteger :- 21;
ExecSQL;
end;
The query will now run faster (because the DBMS can compile it once and reuse it multiple times (the "caching" I mentioned), you don't have the SQL injection risk any longer, and the multiple statements are no longer needed.
Try this code and let us know if the same problem arises:
with ZQuery1 do
begin
SQL.Clear;
SQL.Add('insert into client (name,age) values ('+QuotedStr('john')+','+QuotedStr('20')+'),('+QuotedStr('doe')+','+QuotedStr('21')+');');
ExecSQL;
end;
This way you can also speed up the MySQL handling of this INSERT query, as it does in one batch and not twice.
EDIT #1:
I'm not an expert in Zeos, but with other languages, you could try to execute the query one by one:
with ZQuery1 do
begin
SQL.Clear;
SQL.Add('insert into client (name,age) values ('+QuotedStr('john')+','+QuotedStr('20')+');');
ExecSQL;
SQL.Clear;
SQL.Add('insert into client (name,age) values ('+QuotedStr('doe')+','+QuotedStr('21')+');');
ExecSQL;
end;
EDIT #2: Transactions
One question on Stackoverflow has many good examples about using transactions in MySQL. Although, the examples are written for PHP, I'm sure you could find some good pointers there. Make sure that your tables on the MySQL server are InnoDB not MyISAM.
I'm not an expert in ZEOS either, but looking at the source, have you set MultiStatements property of TZUpdateSQL to true?
Have you tried TZSQLProcessor? Said that the component was made for such needs ( as in ZSqlProcessor.pas unit):
{**
Implements a unidatabase component which parses and executes SQL Scripts.
}

START TRANSACTION inside BEGIN ... END context or outside and LOOP syntax

I have two questions about Compound-Statement and Transactions in MySQL.
FIRST:
There are two notes in MySQL Manual:
Note
Within all stored programs, the parser treats BEGIN [WORK] as the
beginning of a BEGIN ... END block. To begin a transaction in this
context, use START TRANSACTION instead.
Note
Within all stored programs (stored procedures and functions, triggers,
and events), the parser treats BEGIN [WORK] as the beginning of a
BEGIN ... END block. Begin a transaction in this context with START
TRANSACTION instead.
I can't understand what exactly is meant. They mean that I have to put START TRANSACTION instead of BEGIN or right after BEGIN?
// 1st variant:
BEGIN
START TRANSACTION
COMMIT
END
// 2nd variant:
START TRANSACTION
COMMIT
END
Which one is the right way, 1st variant or 2nd variant?
SECOND:
I don't want to create a Stored Procedure or Function. I just want to create a Compound-Statement Block with a loop inside it in the general flow, like this:
USE 'someDb';
START TRANSACTION
... create table statement
... insert statement
// now I want to implement some insert/select statements using loop, I do as follows:
DELIMITER $
BEGIN
SET #n = 1, #m = 2;
lab1: LOOP
... some insert, select statements here
END LOOP lab1;
END $
DELIMITER ;
END
COMMIT
Is it possible such kind of structure? Because I have an error thrown:
Query: BEGIN SET #n = 1, #m = 2; lab1: LOOP SELECT ...
Error Code: 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 'SET #n = 1, #m = 2;
lab1: LOOP SELECT ...
My questions are:
Is it allowed to use BEGIN...END just in general flow without creating and using Stored Procedures or Functions?
Is it allowed to use BEGIN...END inside of START TRANSACTION...COMMIT or I have to put START TRANSACTION...COMMIT inside of BEGIN...END?
BEGIN
START TRANSACTION
COMMIT
END
// vs.
START TRANSACTION
BEGIN
END
COMMIT
Do I by all means have to use BEGIN...END if I want to use only LOOP? May I just use LOOP syntax without starting BEGIN...END? The only example in the manual for LOOP is this:
CREATE PROCEDURE doiterate(p1 INT)
BEGIN
label1: LOOP
...
Is it allowed to use BEGIN...END just in general flow without creating and using Stored Procedures or Functions?
No: compound statements can only be used within the body of stored programs.
Is it allowed to use BEGIN...END inside of START TRANSACTION...COMMIT or I have to put START TRANSACTION...COMMIT inside of BEGIN...END?
START TRANSACTION; and COMMIT; are separate statements. If you want the body of a stored program to contain multiple statements, it will need to enclose those statements in some sort of compound statement block such as BEGIN ... END (which is similar to enclosing a block of statements in braces { ... } within a C-like language).
That said, you could have a stored program which contains only the single-statement START TRANSACTION; or COMMIT;—such a program would not require any compound statement block and would merely commence a new / commit the current transaction respectively.
Outside of a stored program, where compound statement blocks are not permitted, you can issue START TRANSACTION; and COMMIT; statements as & when required.
Do I by all means have to use BEGIN...END if I want to use only LOOP? May I just use LOOP syntax without starting BEGIN...END?
LOOP is also a compound statement block, which is only valid within a stored procedure. It is not necessary to enclose a LOOP block within a BEGIN ... END block, although it is usual (as otherwise it is difficult to perform any required loop initialisation).
In your case, where you apparently want to insert data into a table from a looping construct, you will either need to:
define a stored program in which you use LOOP;
iterate a loop in an external program that executes database queries on each iteration; or
redefine your logic in terms of sets upon which SQL can directly operate.

MySQL transaction conundrum

I need to perform several inserts in a single atomic transaction. For example:
start transaction;
insert ...
insert ...
commit;
However when MySQL encounters an error it aborts only the particular statement that caused the error. For example, if there is an error in the second insert statement the commit will still take place and the first insert statement will be recorded. Thus, when errors occur a MySQL transaction is not really a transaction. To overcome this problem I have used an error exit handler where I rollback the transaction. Now the transaction is silently aborted but I don't know what was the problem.
So here is the conundrum for you:
How can I both make MySQL abort a transaction when it encounters an error, and pass the error code on to the caller?
How can I both make MySQL abort a transaction when it encounters an error, and pass the error code on to the caller?
MySQL does pass error code to the caller and based on this error code the caller is free to decide whether it wants to commit work done up to the moment (ignoring the error with this particular INSERT statement) or to rollback the transaction.
This is unlike PostgreSQL which always aborts the transaction on error and this behavior is a source of many problems.
Update:
It's a bad practice to use an unconditional ROLLBACK inside the stored procedures.
Stored procedures are stackable and transactions are not, so a ROLLBACK within a nested stored procedure will roll back to the very beginning of the transaction, not to the state of the stored procedure execution.
If you want to use transactions to restore the database state on errors, use SAVEPOINT constructs and DECLARE HANDLER to rollback to the savepoints:
CREATE PROCEDURE prc_work()
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK TO sp_prc_work;
SAVEPOINT sp_prc_work;
INSERT …;
INSERT …;
…
END;
Failure in either insert will roll back all changes made by the procedure and exit it.
Using Mr. Quassnoi's example, here's my best approach to catching specific errors:
The idea is to use a tvariable to catch a simple error message, then you can catch sql states you think may happen to save custom messages to your variable:
DELIMITER $$
DROP PROCEDURE IF EXISTS prc_work $$
CREATE PROCEDURE prc_work ()
BEGIN
DECLARE EXIT HANDLER FOR SQLSTATE '23000'
BEGIN
SET #prc_work_error = 'Repeated key';
ROLLBACK TO sp_prc_work;
END;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
SET #prc_work_error = 'Unknown error';
ROLLBACK TO sp_prc_work;
END;
START TRANSACTION;
SAVEPOINT sp_prc_work;
INSERT into test (id, name) VALUES (1, 'SomeText');
COMMIT;
END $$
DELIMITER ;
Then you just do your usual call, and do a select statement for the variable like:
call prc_work(); select #prc_work_error;
This will return either NULL if no error, or the message error in case of an error. If you need persistent error message you can optionally create a table to store it.
It's tedious and not very flexible because requires a DECLARE EXIT HANDLER segment for each status code you want to catch, it won't also show detailed error messages but hey, it works.

Trying to learn MySQL and transactions

Over the last couple of days I have tried to write an Stored procedure in MySQL and I have some truble getting it to work. Hope someone here can give me some input :)
The example I post is for asp.Net Membership provider to create a new user. I expect to send email and password to the DB and get an int return to verify that the userdeatils was written to the DB.
I use a MySQL DB 5.1 (I think) and write the SQL to a webinterface.
I got 2 sidequestions, can someone explain that too :):
1) I use a DELIMITER, but do not know what it does.
2) I am not sure if I have to do other things then to set autocommit = 0 to get transactions to work, or if I even have to do that.
I know that I could have used a IF / ELSE statement instead of a transaction, but would like to do it with one to find out how it works. (I expect to use it alot later)
The code I can not get to work:
DELIMITER //
CREATE DEFINER=`websharp_dk`#`%` PROCEDURE `CreateUser`(
IN _username VARCHAR(100),
IN _Password VARCHAR(100))
RETURNS INT
BEGIN
SET autocommit = 0;
DECLARE return_value INT;
BEGIN TRY
START TRANSACTION
INSERT INTO User
(Email
,Password
,Failed_Password_Count
,Creation_Date)
VALUES
(_username
,_Password
,0
,Datetime.Now())
SET return_value = 1;
COMMIT;
END TRY
BEGIN CATCH
ROLLBACK
SET return_value = 0;
END CATCH
BEGIN FINALLY
RETURN return_value;
END FINALLY
END//
DELIMITER ;
Edit:
The error message I get is:
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 'INT BEGIN SET autocommit = 0; DECLARE return_value INT; ' at line 4
To get support for transactions, make sure you are using the InnoDB storage engine rather than the default MyISAM.
As far as that code itself, my first question would be, why are you wrapping that single query in a transaction? Also, what errors are you seeing?
The delimiter redefines what sequence of characters you use to end an sql statement. The entire create procedure is one big statement and you need to tell MySQL where it ends with something (would normally be ';'). But since you have a bunch of other statements in the "body" (between BEGIN and END) of the create procedure statement that all need to be ended too you need to redefine the delimiter so you don't end the create procedure statement at the first ';'.
Without redefining the delimiter, MySQL would think that the create procedure statement looked like this and then begin a new statement:
CREATE DEFINER=`websharp_dk`#`%` PROCEDURE `CreateUser`(
IN _username VARCHAR(100),
IN _Password VARCHAR(100))
RETURNS INT
BEGIN
SET autocommit = 0;
Using DELIMITER ; at the end of the script changes the delimiter back to ';' and is not needed although it's good practice to do so.