Equivalent of Firebird "when any" for SQL exception trapping in InterBase - exception

I need to extend an app written with Embarcadero Firemonkey for Windows to Android, so I need InterBase.
Besides there is no tutorial (none found so far at least) on how to use Firebird under Android, I have found a very important difference not only in the language itself, but in the way I can trap exceptions with Firebird and InterBase (i.e. into an insert stored procedure).
Here is a sample Firebird code:
ALTER PROCEDURE AD_0_LIST_UPD_ORDER (
AD_0_NAME VARCHAR(20),
AD_0_ORDER INTEGER)
RETURNS (N_ERROR INTEGER)
AS
begin
n_error=0;
begin
update ad_0_list
set ad_0_order = :ad_0_order
where (ad_0_name = :ad_0_name);
when any do begin
n_error=sqlcode;
exit;
end
end
end^
What's beautiful in those few lines (when any ...) is that I have an exception that traps any SQL error, while in InterBase I have to (imagine) and write situations like -803, -625, theoretically from -1 to -999 for every procedure.
I can't fine a way to translate it. Is there a way to do this in InterBase?

InterBase PSQL language has had support for WHEN ANY ... for a long time. See https://docwiki.embarcadero.com/InterBase/2020/en/Handling_Errors and https://docwiki.embarcadero.com/InterBase/2020/en/Examples_of_Error_Behavior_and_Handling
Where it seems to be limited is that it does not recognize SQLCODE as a context variable that you can get a value from for assignment in your code within the WHEN ANY block. It would be a useful enhancement, I agree.
As a sample, the following code works in InterBase, albeit not knowing the exact SQLCODE generated. But, you can catch ANY exception in InterBase PSQL as well.
set echo on;
/* run some bad statements; see exception code(s) */
/* Expected exception: SQLCODE -413 */
insert into employee (emp_no, first_name, last_name, dept_no, job_code, job_grade, job_country, salary)
values (2000, 'Two', 'Thousand', 2000, 'SALES', 2000, 'USA', 1);
/* Expected exception: SQLCODE -297 */
insert into employee (emp_no, first_name, last_name, dept_no, job_code, job_grade, job_country, salary)
values (2000, 'Two', 'Thousand', 'ABC', 'SALES', 2000, 'USA', 1);
rollback;
/* Now, do the same with procedures. */
drop procedure TEST_PROC;
COMMIT;
set term ^;
CREATE PROCEDURE TEST_PROC (runcase INTEGER)
RETURNS (N_ERROR INTEGER)
AS
begin
n_error=0;
begin
/* Give bad DEPT_NO value; integral instead of CHAR(3) */
/* Expected exception: SQLCODE -413 */
if (:runcase = 1) then
insert into employee (emp_no, first_name, last_name, dept_no, job_code, job_grade, job_country, salary)
values (2000, 'Two', 'Thousand', 2000, 'SALES', 2000, 'USA', 1);
/* Give bad SALARY value violating a value constraint */
/* Expected exception: SQLCODE -297 */
if (:runcase = 2) then
insert into employee (emp_no, first_name, last_name, dept_no, job_code, job_grade, job_country, salary)
values (2000, 'Two', 'Thousand', 'ABC', 'SALES', 2000, 'USA', 1);
/* good SQL that will succeed; no exception */
if (:runcase = 3) then
INSERT INTO country (country, currency) VALUES ('India', 'Rupee');
/* check for errors */
when any do begin
n_error = :runcase;
exit;
end
end
end^
set term ;^
commit;
/* Now test above procedures */
/* see if any work getting done below, by setting count option on */
set count on;
execute procedure test_proc (1);
rollback;
execute procedure test_proc (2);
rollback;
execute procedure test_proc (3);
select * from country where country='India';
rollback;
execute procedure test_proc (99);
rollback;

Related

Create sql procedure but it's not appeared in the table

there is no error but also doesn't show success
at first, it shows on only table registrations and not the registration_details, and now doesn't appear in both table.
set foreign_key_checks = 0;
drop procedure if exists createRegist;
delimiter //
create procedure createRegist()
begin
declare total_credit float;
declare registration_id INT;
declare credit float;
-- create first registration for student 1
set total_credit = 0;
insert into `student_regist`.`registrations` (`registration_id`, `student_id`,`total_credit`)
values (1, 1, total_credit);
SELECT LAST_INSERT_ID() INTO registration_id;
-- create registration detail 1
SELECT
`student_regist`.`courses`.`credit`
INTO credit FROM
`student_regist`.`courses`
WHERE
`student_regist`.`courses`.`course_id` = 1
LIMIT 1;
set total_credit = total_credit + credit;
insert into `student_regist`.`registration_details` (`registration_details_id`, `registration_id`, `course_id`, `semester`)
values (1, 1, 1, 1);
SELECT 'Success';
end//
delimiter ;
You have not provided nearly enough detail for us to provide any concrete answers. Adding the DDL for your tables to your question is the minimum required for a real answer.
That said, here are some suggestions.
We know nothing of the values you need to store in credit (and total_credit) but it seems likely that it should be DECIMAL, and not FLOAT. Searching decimal vs float on here returns Float vs Decimal in ActiveRecord as the first result.
If you are using MySQL Workbench the errors/warnings should be displayed in the Output Area (View -> Panels -> Show Output Area). Or you could run SHOW WARNINGS; after calling your SP.
CALL createRegist();
SHOW WARNINGS;
Your first insert into registrations uses a hardcoded value of 1 for registration_id, which is presumably the primary key (PK) for the table. The second time you execute the SP and it tries to insert 1 into your PK, it will fail with a duplicate key error -
Error Code: 1062. Duplicate entry '1' for key 'registrations.PRIMARY'
You then follow up with the call for LAST_INSERT_ID() which will not work as you are expecting. From the MySQL docs -LAST_INSERT_ID()
The value of LAST_INSERT_ID() is not changed if you set the AUTO_INCREMENT column of a row to a non-“magic” value (that is, a value that is not NULL and not 0).
Changing the value passed in the insert statement to NULL or 0 (or removing completely) will resolve this -
/* Passing in value of NULL */
set total_credit = 0;
insert into `student_regist`.`registrations` (`registration_id`, `student_id`,`total_credit`)
values (NULL, 1, total_credit);
/* or removing completely */
set total_credit = 0;
insert into `student_regist`.`registrations` (`student_id`,`total_credit`)
values (1, total_credit);

inserting multiple(many ) rows into table in one time

how to inset into mysql table,example INSERT INTO users(user_id,time,)VALUES(1-500,1524275145) in my case need insert into TABLE users 500 rows form 1-500(it's users id) with same values time for all users.
I tried this one, it is working:
First you execute this snippet to create a procedure in your database so that we can call it later (the second snippet) and execute the query that will insert the 1-500 values into the users user_id column. (You can see the two variables that have values of 500 and 1, and you can see the line: VALUES (user_id_val, 1524275145) where the query will be executed.)
First snippet: (try to execute it separately from the next one)
DROP PROCEDURE IF EXISTS insert_loop;
DELIMITER #
CREATE PROCEDURE insert_loop()
BEGIN
DECLARE user_id_max INT UNSIGNED DEFAULT 500;
DECLARE user_id_val INT UNSIGNED DEFAULT 1;
START transaction;
WHILE user_id_val < user_id_max+1 DO
INSERT INTO users (user_id, time)
VALUES (user_id_val, 1524275145);
SET user_id_val = user_id_val + 1;
END WHILE;
COMMIT;
END #
Second snippet:
DELIMITER ;
CALL insert_loop();
Execute this after the first one (separately).
An example query for your case would be like this:
INSERT INTO users (user_id,time) VALUES
(1, 1524275145),
(2, 1524275145),
(3, 1524275145),
(4, 1524275145);
And so on, until 500. The best approach would be to create the query in a for loop.

In MySQL how to pass a list of integers to a prepared statement in a stored procedure?

MySQL 5.0
CREATE TABLE `locationcodes` (
`id` int,
`customer` varchar(100),
`locationcode` varchar(50),
`parentid` int
);
insert into locationcodes values (1, 'Test, Inc.', 'California', NULL);
insert into locationcodes values (2, 'Test, Inc.', 'Los Angeles', 1);
insert into locationcodes values (3, 'Test, Inc.', 'San Francisco', 1);
insert into locationcodes values (4, 'Test, Inc.', 'Sacramento', 1);
Stored procedure:
CREATE PROCEDURE test (_location VARCHAR(100))
BEGIN
SET #location = _location;
SET #select='
SELECT id, locationcode
FROM locationcodes
WHERE customer="Test, Inc."
AND id in (?)
';
PREPARE stmt FROM #select;
EXECUTE stmt USING #location;
DEALLOCATE PREPARE stmt;
END
Call procedure:
call test('2, 3')
Result:
2, Los Angeles
The result is just one row, but I would like two rows. The challenge is the prepared statement sees the argument is a varchar, and accordingly puts quotes around the value. I would prefer no quotes so that I can build a IN statement, how can that be done? Ideally MySQL would have a list of ints datatype so I would not have to use a varchar, but that does not exist.
I am required to keep the prepared statement to prevent sql injection attacks.
It seems it's not possible to use a prepared statement with IN() and bind one single value.
For some reason MySQL's API replaces IN() with = (equal) when there is just one single value to bind in the corresponding comparison.
I run some tests with our procedure, that's I got.
With your original procedure that's what MySQL generates (test is my default database):
((`test`.`locationcodes`.`customer` = 'Test, Inc.') and
(`test`.`locationcodes`.`id` = '2,3'))
On this case the field id is integer and MySQL converts '2,3' to 2 in the comparison, that's why it returns Los Angeles, '3,2' would return 'San Francisco' obviously.
Just to make sure this was nothing related to the field type (id being a integer), I tried to compare the value with locationcode (varchar), the result is the same.
I changed your procedure to bind two values instead of one.
...
SET #select='
SELECT id, locationcode
FROM locationcodes
WHERE customer="Test, Inc."
AND id in (?,?)
';
PREPARE stmt FROM #select;
EXECUTE stmt USING #location,#location2;
...
In this case is creates the statement properly.
((`test`.`locationcodes`.`customer` = 'Test, Inc.') and
(`test`.`locationcodes`.`id` in ('2','3')))
That being said, I can tell you'll have to use another approach to accomplish that with a prepared statement.
After my tests I found this bug report: http://bugs.mysql.com/bug.php?id=16564
According to Valeriy, create a temporary table with the values and use JOIN. It should work.

How to write a stored procedure to insert values in multiple tables

How do I write a stored procedure to add a person record with multiple addresses?
It is easy if the person has only one address but I'm not sure how to write a stored procedure to add a person with multiple addresses.
Here is the stored procedure to add a person with one address:
DELIMITER $$
CREATE PROCEDURE `log`.`spAddPerson` (
IN personID INT,
IN personName VARCHAR(100),
IN addressLine1 VARCHAR(45),
IN addressLine2 VARCHAR(45),
IN myCity VARCHAR(45),
IN myState VARCHAR(45),
IN myCountry VARCHAR(45)
)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
END;
START TRANSACTION;
INSERT INTO person VALUES(personID,personName);
-- addressid is automatically generated
INSERT INTO address(Line1, Line2,City,State,Country) VALUES
(addressLine1, addressLine2, myCity,myState, myCountry);
INSERT INTO personAddress(personID, last_insert_id());
COMMIT;
END
The above code works fine. However, I do not know how to handle a person with multiple addresses without writing a separate stored procedure. Is there a simple way to do this?
You cannot pass a variable number of variables to a procedure, nor a non-scalar type.
A possible trick would be building a temporary table with the addresses before calling this procedure. Either the temporary table is pre-determined, or pass its name as a VARCHAR parameter(and use it to build dynamic SQL statements). Eg:
CREATE PROCEDURE spAddPerson (tmp_table VARCHAR(10), ...)
BEGIN
...
PREPARE s AS CONCAT(
'INSERT INTO address (line1, ...) VALUES SELECT * FROM ', tmp_table
);
EXECUTE s;
...
END
-- use it like this
CREATE TEMPORARY TABLE tmp_addresses (line1 VARCHAR(255), ...);
INSERT INTO tmp_addresses VALUES ('1 Cherry Lane'), ... ;
CALL spAddPerson ('tmp_addresses', ...);
However, I would rather split the action in two parts. Do you really want to prevent the creation of the person altogether if its address creation fails? And even then, wouldn't you want to advise your user why the transaction failed (user creation or address creation)?
I would rather treat these two exceptions separately at the application level:
issue a "START TRANSATION"
try to insert a person (call stored proc 1)
if it failed, rollback and notify user
for each address
try to insert an address (call stored proc 2)
if it failed, rollback and notify user
issue a "COMMIT"
> DECLARE #LAST_INSERT_ID INT
> DECLARE #EXECUTION_OK char(1)
> SET #EXECUTION_OK = 1
>
> insert into base_table(imgPath,store,apparelType) values (imgPath,store,apparelType)
>
> SELECT #LAST_INSERT_ID = SCOPE_IDENTITY()
>
> insert into data_table(cvID,color) values (#LAST_INSERT_ID, color)
> GO
>
> If exists( Select cvID from data_table where cvID= #LAST_INSERT_ID)
> Begin
> #EXECUTION_OK = 0
> End

Complex WHERE clause in MySQL query - [Use VLOOKUP in MySQL]

I have a database with following tables (with sample data in parenthesis):
companies {
id: (1, 2, 3)
company_name: ('Goog', 'Micr', 'Apple')
}
companies_ratios {
company_id: (1, 1, 2, 1, 1)
ratio_id: (1, 2, 1, 4, 5)
value: (13, 9, 15, 5, 6)
}
ratios {
id: (1, 2, 3, 4, 5)
ratio_name: ('CAGR', 'Prf. Gwt', 'Sal. Gwt', 'Sales_2012', 'Sales_2011')
ratio_formula: ('...')
-- ratio_formula is not used at the moment
}
I need to parse user query into MySQL to return matching companies. However, I can't find a solution to parse some simple queries.
Eg. User Queries:
Sales_2012 > 1.1 * Sales_2012 AND
CAGR > 13
Prf. Gwt > Sal. Gwt OR
CAGR > Prf. Gwt
Main Problem in parsing above:
I plan to replace all ratio names in user query with their value in companies_ratios. However, I need to cross-reference row data. I need something like value where ratio_id = x for each ratio_name. It might be similar to vlookup in excel (to use row data as column).
I have been able to create a stored procedure based on the structure you have provided that returns the company id from a string. It is a little complex, and may need others to review it, but it appears to provide the correct results.
DELIMITER $$
DROP PROCEDURE IF EXISTS sp_build_query $$
CREATE PROCEDURE sp_build_query(IN userquery VARCHAR(100))
BEGIN
DECLARE no_more_rows BOOLEAN;
DECLARE rname VARCHAR(50);
DECLARE fullsql VARCHAR(1000);
DECLARE ratio_cur CURSOR FOR SELECT ratio_name FROM ratios;
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET no_more_rows = TRUE;
SET fullsql = userquery;
OPEN ratio_cur;
FETCH ratio_cur INTO rname;
rnamewhile: WHILE rname is not null DO
IF INSTR(fullsql, rname) > 0 THEN
SET fullsql = REPLACE(fullsql, rname, CONCAT(' (SELECT `value` FROM companies_ratios INNER JOIN ratios ON companies_ratios.ratio_id = ratios.id WHERE ratios.ratio_name = ''', rname , ''' AND companies_ratios.company_id = cr.company_id ) '));
END IF;
FETCH ratio_cur INTO rname;
IF no_more_rows THEN
CLOSE ratio_cur;
LEAVE rnamewhile;
END IF;
END WHILE rnamewhile;
SET #finalsql = CONCAT('SELECT company_id FROM companies_ratios cr WHERE ', fullsql , ' GROUP BY cr.company_id;');
PREPARE stmt1 FROM #finalsql;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
END$$
I used the following websites to assist creating the query. They should be able to explain the functions a bit more, but basically it loops through the ratios table, and when it finds a ratio name in the user query it replaces it with a select statement. The resulting statement is the joined together at the end to create your final select statement which is then executed, providing a list of unique company id's.
Execute an STRING as a query - Dynamic SQL -> PREPARE
http://forums.mysql.com/read.php?60,27979,30437
MySQL Forums :: Stored Procedures :: MySQL Stored procedure Tutorial
http://forums.mysql.com/read.php?98,358569,358569
MySQL Cursors
http://dev.mysql.com/doc/refman/5.0/en/cursors.html
Loop in Stored Procedures
http://www.mysqltutorial.org/stored-procedures-loop.aspx
SQL Cursor in Stored Procedures
http://www.mysqltutorial.org/sql-cursor-in-stored-procedures.aspx
SQL Syntax for Prepared Statements
http://dev.mysql.com/doc/refman/5.1/en/sql-syntax-prepared-statements.html
CREATE PROCEDURE and CREATE FUNCTION Syntax
http://dev.mysql.com/doc/refman/5.0/en/create-procedure.html