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.
Related
I want to create some kind of "unique constraint" that counts null fields as a match.
CREATE TABLE person (
id int,
firstname varchar,
lastname varchar,
dob date,
primary key (id)
);
I want to prevent creating duplicates that who match either the exact values or an empty field.
Example:
INSERT (john, doe, 2000-01-01);
INSERT (john, null, null); //should not be possible, there is already a 'john'
INSERT (null, doe, null); //should not be possible, there is already a 'doe'
INSERT (jane, doe, null); //should be possible, as there is no jane doe yet.
On persist, I want to check if there is already an entry that matches:
WHERE (firstname='john' or firstname is null) and (lastname = 'doe' or lastname is null) and (dob = '2000-01-01' or dob is null)...
And if there is a match: prevent the insertion.
I know this is not a real unique key, but I'm probably missing the correct term here?
Question: is there some kind of generic solution for this type of problem?
you can try a trigger, something like that
CREATE TRIGGER [dbo].[person_Dup_Trigger]
ON [dbo].[person]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS (
SELECT 1
FROM dbo.person S
INNER JOIN Inserted I ON
-- Test for a duplicate
S.firstname = I.firstname
OR S.lastname = I.lastname
-- But ensure the duplicate is a *different* record - assumes a unique ID
AND S.ID <> I.ID
)
BEGIN
THROW 51000, 'should not be possible, there is already a record', 1;
END;
END;
Notice that condition you want to check
WHERE (firstname='john' or firstname is null) and (lastname = 'doe' or lastname is null) and (dob = '2000-01-01' or dob is null)...
forbids any null insertion, for example it would be impossible to insert
INSERT (john, null, null);
at all, even if it is the first insert in person table. Is this what you want to achieve?
If you only what to prevent multiple null values I'd try this:
create unique index idx1 on person(coalesce(firstname, 'null'), coalesce(lastname, 'null'), coalesce(dob, 'null'));
Edit: Above solution works only with assumption that we do not expect literal value 'null' to represent firstName neither lastName or dob :) because it will not be allowed to insert.
On the other hand if you want only not-null values to be unique (every null is unique for MySQL DB) then index
create unique index idx1 on person(firstname, lastname, dob);
should do the job.
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.
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
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.
Hello I'm working on a database assignment and I'm stuck on how to do this one stored procedure. Although It works, sort of...
DELIMITER //
CREATE PROCEDURE AddANewCustomer(IN firstName char(20), IN lastName char(20), IN companyName char(45), IN streetAddress char(60), IN city char(30), IN province char(45), IN postalCode char(6), IN phoneNumber int(10))
BEGIN
DECLARE PersonID INT;
SELECT idPerson FROM Persons WHERE Persons.firstName = firstName AND Persons.lastName = lastName INTO PersonID;
IF PersonID IS NULL THEN
INSERT INTO Persons(firstName, lastName, streetAddress, city, province, postalCode, phoneNumber) VALUES (firstName, lastName, streetAddress, City, Province, postalCode, phoneNumber);
SELECT idPerson FROM Persons WHERE firstName = firstName AND lastName = lastName INTO PersonID;
END IF;
INSERT INTO Customers(idCustomer, companyName) VALUES (Last_Insert_ID(), companyName);
END //
DELIMITER ;
Basically I'm working with Super/Sub types. I want to take the information from the user and then update my parent table (Persons) and pass on the remaining information to my child table (Customers). idPerson is the auto-incrementing PK for Persons table, and I want to use that as a PK/FK for the Customers table's id, idCustomer.
If I run the procedure once, it'll spit out an error 'Result consist of more than one row' and only the Parent table gets updated... But if I run it again, it'll update the Child table properly. Which makes me think that the Last_Insert_ID() parameter is null the first time around and the idPerson only gets updated after the procedure is done.
I've researched for a fix all night and now I'm absolutely stumped on how to solve this.
Ouch.
Basically I'm working with Super/Sub
types.
I don't think so, but I could be wrong. Customer usually describes a relationship between two parties, one a buyer and the other a seller.
If I run the procedure once, it'll
spit out an error 'Result consist of
more than one row'
What do you think that means? Does this query return any rows?
SELECT lastname, firstname, count(*)
FROM Persons
GROUP BY lastname, firstname
HAVING count(*) > 1;
You check for a NULL id number,
IF PersonID IS NULL THEN
but you ignore the possibility that your SELECT statement might return 2 or 3 or 42 different id numbers, all for people who have the same first and last name. Is that wise? Phrased another way, do you have a UNIQUE constraint on {firstname, lastname}?
If PersonID is null, you insert a row into Persons, which sets a value that LAST_INSERT_ID() can return. But your second INSERT tries to use LAST_INSERT_ID() without regard to whether a row was previously inserted into Persons.
Finally, you have two slightly different versions of
SELECT idPerson
FROM Persons
WHERE Persons.firstName = firstName
AND Persons.lastName = lastName
INTO PersonID;
I'm pretty sure you need one at most.