Inserting Information into Parent/Child Tables via a Stored Procedure? - mysql

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.

Related

How to create unique key that also matches null or empty fields?

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.

MySQL insert using continuous number

I'm copying a row in a table using this statement:
insert into Buyer (
version, creationDate, password, token, username, zip, city, lastname, firstname, preferredLanguage_id, title_id, contactEmail_id, active
) select
version, creationDate, password, token, "loadtest_1#example.com", zip, city, lastname, firstname, preferredLanguage_id, title_id, contactEmail_id, active
from Buyer where username="developer_de#example.com";
Only thing I change is the username/email. Now the number in the new username to be inserted, "loadtest_1#example.com", should increment every time. So the second should be loadtest_2..., loadtest_3 and so on. I don't really care at what number it starts as long as it's continuous, so taking the ID of the newly inserted row or the like would be totally okay.
Extra kudos for ideas on how to actually create a batch of these inserts so I don't have to run it X times.
You are selecting and inserting to same table and only change is username. what I see is you need a UPDATE statement rather like
update Buyer set username = 'loadtest_1#example.com'
where username="developer_de#example.com";
If it's test and you do really want to insert test data saying loadtest_1#example.com .. loadtest_100#example.com then you can use a while loop like
CREATE PROCEDURE usp_inserttestdata(total INT)
AS
BEGIN
DECLARE counter INT;
DECLARE uname varchar(20);
SET counter = 1;
label1: WHILE counter <= total DO
SET uname = concat('loadtest_', counter, '#example.com');
insert into Buyer (
version, creationDate, password, token, username, zip, city, lastname,
firstname, preferredLanguage_id, title_id, contactEmail_id, active)
select version, creationDate, password, token, uname, zip, city, lastname, firstname,
preferredLanguage_id, title_id, contactEmail_id, active
from Buyer where username="developer_de#example.com";
SET counter = counter + 1;
END WHILE label1;
END
Then call the procedure saying
CALL usp_inserttestdata 1000
You can use AFTER Trigger ON INSERT operation to achive your goal. In the body of this trigger update email and set corresponding value depends on auto increment id value.

MySQL: Creating a table with auto increment and concatenating the values generated with the values of a different column of the same table

I want to create a table employee with id,name,dept,username attributes.
The id column values are auto_increment. I want to concatenate the values of id with dept to generate the username.
Following is the query that I wrote:
create table employee emp_id MEDIUMINT NOT NULL AUTO_INCREMENT, name char(30) NOT NULL, dept char(6)NOT NULL, username varchar NOT NULL PRIMARY KEY(emp_id);
How to insert values into this table properly? Please help.
If your usernames will always be the concatenation of id and dept, you don't need to create a separate column for it, you can just select it using the MySQL CONCAT function, like this:
SELECT id, name, dept, CONCAT(id, '-', dept) AS username FROM employee
This will return results for username like 13-sales.
If you want to actually store the username (maybe because you want the ability to customize it later), you'll have to first insert the row to get the id, and then update the row setting the username to the concatenated id and dept.
You can use null on MySQL for auto traslate as the last id:
INSER INTO employee (name, dept) VALUES (?,?);
UPDATE employee SET username = concant(emp_id,dept) WHERE emp_id = NULL;

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.

Update Junction table in many-to-many relationship

This is a follow up question to this one:
Query examples in a many-to-many relationship
regarding updating the junction table. To do this, I would have to use both key values in the junction table, in the WHERE clause.
Users UserAddresses Addresses
======= ============= =========
FirstName UserId City
LastName AddressId State
Zip
In this example, for instance, say I wanted to update the AddressId field in the UserAddresses table, because a user changed his address. I would have to use both the existing UserId and the address AddressId in the update WHERE clause.
I'm using a stored procedure, and passing in UserId and the new AddressId as parameters.
I've tries this:
CREATE PROCEDURE dbo.test
(
#UserId int,
#AddressId int
)
AS
create table #temp
(
UserId int not null,
AddressId int not null
)
insert into #temp select UserId, AddressId from UserAddresses where UserId = #UserId
update UserAddresses
set AddressId = #AddressIdD
WHERE (UserId+AddressId in #temp table = UserId+AddressId passed in as parameters)??
I've tried all sorts of combinations, but I can't seem to get the syntax right.
The UserId passed in, would ofcourse be the same as the one in the UserAddresses table, but this is just me trying some things. The WHERE clause is where it seems to go wrong.
Any thoughts?
This actually looks like a many-to-one relationship. If it's not you'll need the old address id as well as the new address id and user id to make the change. If it's a many to one relationship then a simple update should work since only one user id/address id pair will exist for each user id:
update UserAddresses
set AddressId = #AddressId
where UserId = #UserId
If it truly is a many-to-many relationship you need to find the existing pair out of many possible ones and update that one -- that's where you'll need both the new and old address ids in addition to the user id.
update UserAddresses
set AddressId = #NewAddressId
where UserId = #UserId and #AddressId = #OldAddressId
Why use the temp table?
CREATE PROCEDURE dbo.test
(
#UserId int,
#AddressId int
)
AS
update UserAddresses
set AddressId = #AddressIdD
WHERE UserId = #UserId
tvanfossom pointed out the problem in your code correctly, I think.
With the tables above, your operation could be done in various ways:
INSERT the link to the new address and DELETE the link to the old address, either keeping or deleting the address record that's being linked to.
UPDATE the link record as tvanfossom described (LuckyLindys query will set all registered addresses of the user to the same one).
UPDATE the address record that's being linked to.
Which one you use depends on what you want in your application. I'd probably just update the linked address, or do you need to keep the old one?