Throwing Custom Errors in MySQL store procedures - mysql

I'm playing around with MySQL stored procedures and I need a little help wrapping my head around some things. Below I'm attempting to;
1) Check if the student_id exist in the database and if it does then display "alumni already exist"
2) Check to see if the department and degree parameter entered don't exist and if it doesn't, then display "_ does not exist" (side note : these two columns are foreign keys)
Right now, my IF statement doesn't work and throws arbitrary errors. (ex. student_id doesn't exist in table but the error "Alumni Exist Already" is thrown, this is one of many)
I'd like to know what I'm doing wrong. Also, If the way I'm approaching this makes sense and if it doesn't, what's a more pragmatic way of going about this?
Thanks
DELIMITER //
DROP PROCEDURE IF EXISTS sp_add_alumni//
CREATE PROCEDURE sp_add_alumni (
IN student_id INT(20),
IN first_name VARCHAR(255),
IN last_name VARCHAR(255),
IN street VARCHAR(255),
IN city VARCHAR(255),
IN state VARCHAR(2),
IN zip_code VARCHAR(15),
IN email VARCHAR(255),
IN telephone VARCHAR(22),
IN degree VARCHAR(255),
IN department VARCHAR(255)
)
BEGIN
DECLARE studentID INT(20);
DECLARE departmentVAL VARCHAR(255);
DECLARE degreeVal VARCHAR(255);
DECLARE EXIT HANDLER FOR SQLWARNING
BEGIN
ROLLBACK;
SELECT 'ALUMNI INSERT HAS FAILED';
END;
SET studentID = student_id;
SET departmentVal = department;
SET degreeVal = degree;
IF EXISTS (SELECT 1 FROM alumni WHERE student_id = studentID ) THEN
SELECT 'ALUMNI ALREADY EXISTS';
ELSEIF NOT EXISTS (SELECT 1 FROM valid_departments WHERE UCASE(department) = UCASE(departmentVal)) THEN
SELECT 'DEPARTMENT DOES NOT EXISTS';
ELSEIF NOT EXISTS (SELECT 1 FROM valid_degrees WHERE UCASE(degree) = UCASE(degreevVal)) THEN
SELECT 'DEGREE DOES NOT EXISTS';
ELSE
SELECT 'ALUMNI ADDED';
END IF;
START TRANSACTION;
INSERT INTO alumni (student_id, pwd ,first_name, last_name, street, city, state, zip_code, email, telephone, degree, department, role_id, donation_total) VALUES (student_id, NULL ,first_name, last_name, street, city, state, zip_code, email, telephone, degree, department, 1, 0.00);
COMMIT;
END//

I'd like to know what I'm doing wrong.
As documented under Restrictions on Stored Programs:
Name Conflicts within Stored Routines
The same identifier might be used for a routine parameter, a local variable, and a table column. Also, the same local variable name can be used in nested blocks. For example:
CREATE PROCEDURE p (i INT)
BEGIN
DECLARE i INT DEFAULT 0;
SELECT i FROM t;
BEGIN
DECLARE i INT DEFAULT 1;
SELECT i FROM t;
END;
END;
In such cases, the identifier is ambiguous and the following precedence rules apply:
A local variable takes precedence over a routine parameter or table column.
A routine parameter takes precedence over a table column.
A local variable in an inner block takes precedence over a local variable in an outer block.
The behavior that variables take precedence over table columns is nonstandard.
In your case student_id is a routine parameter and studentID is a local variable; therefore (given the precedence rules above) the filter criterion WHERE student_id = studentID is comparing those two things with eachother and at no time is inspecting a table column.
Since the local variable was set to the value of the routine parameter, this filter always evaluates to true.
You could avoid this either by using different names for your parameters/variables, or else by qualifying your column reference with a table prefix:
WHERE alumni.student_id = studentID
Also, If the way I'm approaching this makes sense and if it doesn't, what's a more pragmatic way of going about this?
Define suitable UNIQUE and foreign key constraints, then attempts to insert invalid data will fail without you explicitly having to check anything:
ALTER TABLE alumni
ADD UNIQUE KEY (student_id), -- is this not already a PRIMARY KEY ?
ADD FOREIGN KEY (department) REFERENCES valid_departments (department),
ADD FOREIGN KEY (degree ) REFERENCES valid_degrees (degree )
;
To make the foreign keys use a case-insensitive lookup, ensure that the respective columns use a case insensitive collation.
Note the restrictions to which foreign keys are subject in the article linked above.

Related

how to apply constraint by Stored procedures that ID should start with 'E' and has 2 numerics aftarwards?

EID should only be inserted if it starts with 'E' and has 2 numerics aftarwards ....
i was trying to write a procedure for the purpose like:
create proc emp_checks(#eid varchar(20), #name varchar(20),#age int, #salary int, #city varchar(20))
AS
Begin
Declare #citycount int= (select count(*) from employee where #city=city)
if (#citycount < 2 and #eid like 'E--%')
insert into employee values(#eid,#name,#age,#salary,#city)
else
print 'city exceeded'
End
here the issue is that -- or spaces would insert non-numerics also
here this code also includes few extras *just homework things :)
Why would you write a stored procedure? MySQL now supports check constraints:
alter table employee add constraint chk_employee_eid
check (eid regexp '^E[0-9]{2}')
Note that this allows anything after the first 3 characters. If you don't want anything, then add $ to the pattern:
alter table employee add constraint chk_employee_eid
check (eid regexp '^E[0-9]{2}$')

unknow system variable: "new.id" is not recognized

Let's say 6,7,8 are all taken, and l tried to insert data with id = 6. The purpose of the trigger is to find the next available number. However, MySQL does not recognize new.id
Or, can l alter auto_increment's value?
Why am l doing this? Because in OracleDB, the sequence generator will increment(and eventually find that available number) whereas MySQL's sequence generator won't increment if a duplicate primary key is generated somehow.
tried to replace "new.id = #valid" with "alter table wooster_brush_employee auto_increment = #valid" in trigger, but it did not work.
create table wooster_brush_employee(
id int primary key auto_increment,
first_name varchar(15),
last_name varchar(20),
username varchar(10),
password varchar(15),
email varchar(30)
);
delimiter //
create trigger validate_id before insert on wooster_brush_employee
for each row
set #old = last_insert_id();
set #valid = last_insert_id();
call wooster_brush_employee_id_validator(#old, #valid);
set NEW.id = #valid;
end;
delimiter;
I believe that your whole approach here is off kilter, and you don't even need to use this trigger. The point of an auto increment column, in either MySQL or Oracle, is that the database handles the problem of finding the next available number in the sequence. While the auto increment contract does not guarantee that the next value found will be greater than every value already in the column, it does guarantee that the next value will be unique.
So, if you want to make use of MySQL's auto increment functionality, then next time you insert, simply omit id from the column list:
INSERT INTO wooster_brush_employee (first_name, last_name, username, password, email)
VALUES
('Jon', 'Skeet', 'jonskeet', '*****", 'jon.skeet#google.com');
Since id was omitted, MySQL will automatically generate the next value in the sequence behind the scenes.

Problems with stored procedure

someone can help me with procedure?
i get error:
Where is problem?
If you write a procedure with more than one statement in the body, you need to use BEGIN...END.
CREATE PROCEDURE addItemToRepository(IN name VARCHAR(50), IN rfid VARCHAR(20),
IN type VARCHAR(20), IN manufacturer INT, IN model VARCHAR(30), IN toRent TINYINT)
NOT DETERMINISTIC MODIFIES SQL DATA SECURITY DEFINER
BEGIN
SET #typeid = (SELECT id FROM dictionary WHERE value = type LIMIT 1);
INSERT INTO depository (name, rfidtag, type, manufacturer, model, torent)
VALUES (name, rfid, #typeid, manufacturer, model, torent);
END
Note I changed select(#typeid) in your insert statement. It's unnecessary to use select, and you can't do it without putting it inside a subquery in parentheses anyway.
I have some more comments:
Make sure your IN parameter names are distinct from your column names, or else you might create ambiguous SQL statements, that is MySQL doesn't know if you mean manufacturer the in parameter or manufacturer the column in the depository table. It's not an error, but it might insert a NULL because it's using manufacturer from the non-existing row. So I suggest the habit of naming in parameters with a prefix like "p" indicating parameter.
Declare a local variable instead of using session variables. MySQL treats them differently.
Consider using the alternative INSERT...SET syntax.
Here's my suggestion:
CREATE PROCEDURE addItemToRepository(IN pName VARCHAR(50), IN pRfid VARCHAR(20),
IN pType VARCHAR(20), IN pManufacturer INT, IN pModel VARCHAR(30), IN pToRent TINYINT)
NOT DETERMINISTIC MODIFIES SQL DATA SECURITY DEFINER
BEGIN
DECLARE vTypeid INT;
SELECT id INTO vTypeid FROM dictionary WHERE value = pType LIMIT 1;
INSERT INTO depository
SET name = pName,
rfidtag = pRfid,
type = vTypeid,
manufacturer = pManufacturer,
model = pModel,
toRent = pToRent;
END

Accidentally deleted row from table with primary key

If you "accidentally" (Be gentle, it's been a tough morning) delete a row from a table that has a primary key, is it possible to re-insert the record with it's previous key, even if the table auto increments the primary key?
TSQL I've got a really weird environment - very restricted in some ways and sadly open in others. Won't allow insert of already used PK values, had to recreate the whole table...
/****
create protoTable w/ same structure as your mainTable that has the data you are trying to fix in this example the fieldname of the primary key is FldPK
assumption here is that your primary key is getting auto incremented
get a list of the fields in the mainTable that are NOT NULL.
in this example those fields are <not null fields>
get a list of all fields in the mainTable
in this example <all fields>, rather than spell out the fields. DO NOT INCLUDE the primary key field name
***/
declare #x int, #y int, #iLast int
select #iLast = (select MAX( FldPK ) from mainTable)
set #x = 1
while #x <= #iLast
begin
select #y = (select COUNT(*) from mainTable where FldPK = #x)
if #y = 1
begin
insert into protoTable(<all fields>)
select <all fields> from mainTable where FldPK = #x
end
else
begin
insert into protoTable (<not null fields> )values('N','xyz'+convert(varchar,#x)) /*or whatever values are valid to fulfill not null*/
/* this is where you keep one or more of the missing rows to update later with the lost data */
if #x <> 126
begin
delete protoTable where FldPK = #x
end
end
set #x=#x+1
end
Then rename the mainTable to backup and rename protoTable to mainTable
hth
The AUTO_INCREMENT feature will assign a value if one is not provided in the INSERT or REPLACE statement. Otherwise, if you supply a value and it does not conflict with the existing keys, it will be accepted as-is.

Select....into error mysql

I have this stored procedure:
delimiter /
drop procedure if exists registration / create procedure registration(email varchar(50), pass varchar(50), first_name varchar(30), last varchar(30), address varchar(100), city varchar(30), state_id int, zip varchar(20), phone varchar(15), alt_phone varchar(15), outlet varchar(100), url varchar(255), bio text, out response int, out photo_location varchar(40), image_type varchar(10), out emailer varchar(20), out max_row int)
start_:begin
start transaction;
insert into registration_application values(null, email, PASSWORD(pass), first_name, last, address, city, state_id, zip, phone, alt_phone, outlet, url, bio);
set emailer=email;
select id into max_row from registration_application where email=email order by id;
commit;
set response= 1;
end start_;
The id is a primary key, which is auto_incremented, and the email field has unique index; so, it's impossible for 2 rows to contain the same email; however, anytime I call that procedure, the 'select...into' always return more than 1 row, even though only row exists with that email address.
I understand that I can constrict it to returning only one row by using limit 1, and 'using order by id desc'; however, my questions are these:
Is the problem a bug in mysql? I've seen many people online with a similar problem.
Since I have no idea what position the id I'm seeking is (since the select..into obviously returned more than one row), is there a way to ensure the correct id will always be returned?
Thank you.
#user705339 as you mentioned above the LAST_INSERT_ID() might not return the desired id under certain circumstances, for example if you try to insert a duplicate email, the insert statement returns error and the value of LAST_INSERT_ID() is undefined.
Your problem is an ambiguous reference to email, where email=email is always true returning all rows in table registration_application. And not the expected single row.
What you could do, since you are setting emailer=email, is:
select id into max_row from registration_application where email=emailer order by id;
The LAST_INSERT_ID() function is the correct way to get the last auto increment value.