MySQL foreach loop - mysql

I have to iterate each row in my table User in MySQL.
I need to create a new row Address for each iteration in User with some conditions described below.
I have 3 tables:
User: id, stuff, id_person, email
Person: id, stuff, id_address
Address: id, email
I need to create a new row in Address if the User.id_person is NOT NULL and that person.id_address IS NULL.
I have to create the row with the same email that User.email.
I have to do that for each row in User.
I tried to use MySQL cursor's but I do not know how to use them very well.
How can I do that? Is there any other way instead of using cursor's for that?
Thanks in advance.
EDIT: I have just realized that I also have to update person.id_address with the id of the address' row I have just created.

From what I can gather, the following should suffice, so long as the fields are what you have provided.
INSERT INTO Address (email)
SELECT User.email
FROM User JOIN person ON User.id_person = person.id
WHERE person.id_address IS NULL
;
EDIT (with Cursor)
This should be pretty simple with a cursor, however I highly advise you familiarize yourself with these and the implications.
DROP PROCEDURE IF EXISTS _tmp_update_address;
DELIMITER $$
CREATE PROCEDURE _tmp_update_address()
BEGIN
DECLARE cursor_List_isdone BOOLEAN DEFAULT FALSE;
DECLARE cur_userId, cur_personId INT;
DECLARE cur_email VARCHAR(250) DEFAULT '';
DECLARE cursor_List CURSOR FOR
SELECT User.id, person.id_address, User.email
FROM User JOIN person ON User.id_person = person.id
WHERE person.id_address IS NULL
;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET cursor_List_isdone = TRUE;
OPEN cursor_List;
loop_List: LOOP
FETCH cursor_List INTO cur_userId, cur_personId, cur_email;
IF cursor_List_isdone THEN
LEAVE loop_List;
END IF;
INSERT INTO Address (email) VALUES (cur_email);
UPDATE person SET person.id_address = LAST_INSERT_ID()
WHERE person.id = cur_personId;
END LOOP loop_List;
CLOSE cursor_List;
END
$$
DELIMITER ;
CALL _tmp_update_address();

You don't want to use a cursor for this. Based on what you describe:
insert into address (address)
select u.email
from user u join
person p
on u.id_person = p.id;

When I search "mysql foreach loop" this is what I found, and since I found a better alternative than the cursor for doing a foreach loop, here we are :
delimiter $$
create or replace procedure _tmp_procedure() begin
for var_user in (
select User.id, person.id_address, User.email
from User join person on User.id_person = person.id
where person.id_address is null
) do
INSERT INTO Address (email) VALUES (var_user.email);
UPDATE person SET person.id_address = LAST_INSERT_ID()
WHERE person.id = var_user.id_person;
end for;
end;
$$
call _tmp_procedure();

Related

Check condition and send out parameter value from procedure in MySQL

I'm creating a procedure where it needs to check the value of a column IsLoggedIn and send the outparameter with -1 if "IsLoggedIn" is 1 else it needs to send the user information.
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `sp_bh_loginverify1`(IN `user_email` VARCHAR(100), OUT `p_islogged` INT)
NO SQL
BEGIN
IF (EXISTS(select * FROM user_details WHERE user_email = user_email))
THEN
SET p_islogged = 1; SELECT 0;
END IF;
(select UD.user_id, UD.user_name, UD.password, UD.password_salt, UD.user_email, R.role_id as role, R.role_name as role_name from user_details UD
JOIN roles R on R.role_id = UD.role
where (UD.user_email = user_email OR UD.user_name = user_email));
END$$
DELIMITER ;
First of all, always name your procedure input parameters something different from the columns of your table. For example you do this:
IF (EXISTS(select * FROM user_details WHERE user_email = user_email))
How is MySQL supposed to know that user_email is the column in the table, but user_email is the procedure parameter of that name? It doesn't -- it assumes both are references to the column. It compares the column user_email to itself, which will return every row where the column is not NULL, because it's always true that user_email = user_email. In other words, that the column is equal to itself (unless it's NULL).
You shoudl name the procedure parameter something distinct from the column name:
CREATE DEFINER=`root`#`localhost` PROCEDURE `sp_bh_loginverify1`(IN `p_user_email` VARCHAR(100), OUT `p_islogged` INT)
You used NO SQL which means: "indicates that the routine contains no SQL statements." But that's not true, because you do have SQL statement in this procedure.
You should use:
READS SQL DATA
You said you want to return -1 in the OUT parameter, but you set 1. So you should set -1.
Next you mentioned "else it sends user information" which suggests you want that result set to be returned only conditionally. But your code returns that user information outside the IF/END IF block, so it happens regardless of the condition. You should use an ELSE block to do this.
Notice the use of p_user_email in the queries below, to make it distinct from user_email.
BEGIN
IF (EXISTS(select * FROM user_details WHERE user_email = p_user_email))
THEN
SET p_islogged = -1;
ELSE
SELECT UD.user_id, UD.user_name, UD.password, UD.password_salt, UD.user_email,
R.role_id as role, R.role_name as role_name
FROM user_details UD
JOIN roles R ON R.role_id = UD.role
WHERE UD.user_email = p_user_email OR UD.user_name = p_user_email;
END IF;
END
I don't think you need SELECT 0; at all. MySQL stored procedures can return no result set, or one result set, or multiple result sets.

IN clause not working in WHERE condition in MYSQL Trigger

I am creating a trigger that have a MYSQL query having IN clause look like below.
drop trigger onPublish;
delimiter $$
create trigger onPublish AFTER insert on publish_drawing_details
for each row
begin
Declare userIds VARCHAR(255);
set userIds = (select GROUP_CONCAT(refUser) from manpower where refProject=new.refProjectId and refRoleType in (select role from hr_user));
insert into notification_container(loginUserId,mailSubject,projectId,userIds,attachmentIds,ccMails,mailText,metaNotificationEventId)
values (new.submittedBy,new.mailSubject,new.refProjectId,userIds,new.attachmentIds,new.ccEmails,new.mailText,1);
end $$
It works fine if I write this like set userIds = (select GROUP_CONCAT(refUser) from manpower where refProject=new.refProjectId and refRoleType in (1,2,3,4,5));
I try it like set userIds = (select GROUP_CONCAT(refUser) from manpower where refProject=new.refProjectId and refRoleType in (select GROUP_CONCAT(role) from hr_user));
but don't work at all.
You don't need GROUP_CONCAT in inner query, try the following:
SET userIds = (
SELECT GROUP_CONCAT(refUser)
FROM manpower
WHERE refProject=new.refProjectId
AND refRoleType IN (
SELECT role
FROM hr_user
)
);

MSQ can't DELETE from same tabel as it SELECTS - Need help for workaround in my DELETE TRIGGER

I wan't to make the trigger delete the row with the lowest ID, if it is recurring. And by recurring i mean the number and username are the same in two rows.
For example
ROW1: ID: 1 , Nr: 1 , UN: MVJ and
Row2: ID: 2 , Nr: 1 , UN: MVJ
Those are recurring, but if the 'Nr' or 'UN' were different, they wouldn't be.
But as the title explains, MYSQL can't do that, atleast with the way I've made my SQL Statement.
I'm open to solve the problem in an whole other way.
Drop trigger no_double_reservations;
DELIMITER !!
CREATE TRIGGER no_double_reservations
AFTER INSERT ON tilmeldte
FOR EACH ROW BEGIN
IF(
SELECT COUNT(*)
FROM tilmeldte
WHERE (kursus_nr, username)
IN (
SELECT kursus_nr, username
FROM tilmeldte
GROUP BY kursus_nr, username
HAVING COUNT(*) = 2
) = 2
)
THEN
DELETE FROM tilmeldte
Where tilmeldingsid = (
Select MIN(`tilmeldingsid`)
FROM tilmeldte
WHERE (kursus_nr, username)
IN (
SELECT MIN(kursus_nr), username
FROM tilmeldte
GROUP BY kursus_nr, username
HAVING COUNT(*) = 2
)
);
END IF;
END!!
DELIMITER ;
I hope you can help.
Since you haven't given any table definition, here is a logic how to get this done in MySQL.
create stored procedure that takes your username and kursus_id
check whether the username already exists
if yes > delete and insert new id
if no > just insert new record and send message back
in code it would be something like.
CREATE PROCEDURE `SP_NEW_RESERVATION` (in iUsername varchar(50), in iKursus_id int)
BEGIN
DECLARE EXIT HANDLER for sqlexception
BEGIN
rollback;
SELECT 'False' as Result, 'Some error description or diagnose description from mysql' as `Description`;
END;
Start Transaction;
SET #mExists := (SELECT ID from tbl_yourTable where ((kursus_id = iKursus_id) and (username like iUsername)));
if (length(coalesce(#mExists,'')) <> 0) then
-- username does not exists so you can continue with insert a new reservation
insert into tbl_yourTable and continue here
-- send back message
SELECT 'True' as Result, 'New record inserted') as `Description`;
ELSE
-- username exists already and the id would be saved in mExists so delete the id and continue with your insert_method
Delete from Tbl_yourtable where id = #mExists something ligke that;
insert into tbl_yourtable .....
-- you can send back a message like
SELECT 'True' as Result, concat('Old record(', #mExists, ') Deleted and new record inserted') as `Description`;
end if;
Commit;
END
above code is incomplete. To execute this code you need to correct your insert and delete commands. Then just call the procedure like.
call SP_NEW_RESERVATION('KrishKm', 2);
if username 'KrishKm' already exists you will get message back like:
True, Old record(10) Deleted and new record inserted.
if not you get message back:
True, New record inserted.
if anything goes wrong you get message :
False, 'Some error description or diagnose description from mysql'
Good luck.

mysql trigger to change user status after a certain amount of points has been reached

I have a table users
in it there are columns
userPoints and userStatus
each user begins as a newbie.
if the userPoints reach 100
user status should change to something else
there is another table userStatuses
statusId statusName minPoints maxPoints
can I create a trigger that will fire off when userPoints reach 100 their userStatus changes, according to the userStatuses table?
table statuses
id statusName minimumPoints maximumPoints
1 lvl1 0 100
2 lvl2 101 1000
3 lvl3 1001 5000
4 lvl4 5001 20000
5 lvl5 20000 100000
Try this:
delimiter //
CREATE TRIGGER changeUserStatus BEFORE UPDATE ON users
FOR EACH ROW
BEGIN
IF NEW.userPoints > 100 THEN
SET NEW.userStatus = 'lvl2';
END IF;
END;//
delimiter ;
delimiter allows the user of semicolons within in the trigger definition.
Of course, assumes that you hard code it. If you instead want to reference a table, I would use a different approach.
EDIT: If you want to reference your table statuses (which you provided in your edited question), then your approach depends on how many records you are updating.
If you are updating one at a time,
delimiter //
CREATE TRIGGER changeUserStatus BEFORE UPDATE ON users
FOR EACH ROW
BEGIN
SET #status = (
SELECT statusName
FROM statuses
WHERE NEW.userPoints BETWEEN s.minimumPoints AND s.maximumPoints
);
IF #status <> NEW.userStatus:
SET NEW.userStatus = #status;
END IF;
END;//
delimiter ;
However, if you are updating many records at a time, it is likely more performant to run one query at the end. Unfortunately, MySQL only allows triggers that perform one operation per row.
So, I would create a stored procedure
CREATE PROCEDURE refreshUserStatuses
UPDATE users u
JOIN statues s ON u.userPoints BETWEEN s.minimumPoints AND s.maximumPoints
SET u.userStatus = s.statusName;
You will have to run CALL refreshUserStatuses; after updating users.
You can do:
CREATE TRIGGER update_status BEFORE UPDATE ON users
FOR EACH ROW
BEGIN
SET #NEWSTATUS = (SELECT statusId
FROM userStatuses
WHERE NEW.userPoints BETWEEN minPoints AND maxPoints);
IF #NEWSTATUS != OLD.userStatus THEN
SET NEW.userStatus = #NEWSTATUS;
END IF;
END;
This will get the status for the user's points and validate if he was already on that level or not. If not, the user will change status.
sqlfiddle demo
This code enables users to change their password:
CREATE TABLE users (
username VARCHAR(20) NOT NULL UNIQUE,
First_name VARCHAR(20),
Last_name VARCHAR(20),
password VARCHAR(20),
PRIMARY KEY (username)
);
CREATE TABLE change_password (
id INT NOT NULL auto_increment,
username VARCHAR(20),
old_password VARCHAR(20),
new_password VARCHAR(20),
comfirm_password VARCHAR(20),
PRIMARY KEY (id)
);
DELIMITER $$
create trigger nn before insert on change_password
for each row
begin
if new.comfirm_password!=new.new_password then
signal sqlstate '42000' set message_text="New password is not the same as comfirm password";
end if ;
if (new.old_password != (select password from users where username=new.username))then
signal sqlstate '42000' set message_text="Incorrect old password";
end if ;
if ((new.old_password = (select password from users where username=new.username)) &&
(new.new_password=new.comfirm_password)) then
update users
set password =new.new_password where username=new.username;
end if;
END $$
DELIMITER ;

Having issues on a validation for email on a table

What i'm trying to do is to validate the email of a certain register on a trigger. For example if a customer is registered for the first time it should create an email with both first and last name (example: user.last#email.com) however if there's already a register with the same name it should create the email the same way with the first and last name following the customer_id (example: user.last2#email.com).
The code that i have so far is the following:
delimiter $
create trigger before_customer_insert
before insert on customer
for each row
begin
declare emailVal varchar(45);
declare checkData int;
declare checkData2 int;
set checkData = (select count(first_name) from customer where first_name = new.first_name);
set checkData2 = (select count(last_name) from customer where last_name = new.last_name);
if((checkData = 1) and (checkData2 = 1)) then
set new.email = (concat(new.first_name,'.', new.last_name, '#sakilacustomer.org'));
else
set new.email = (concat(new.first_name,'.', new.last_name, new.customer_id, '#sakilacustomer.oeg'));
end if;
if(new.kind_customer is null) then
set new.kind_customer = '1';
end if;
set new.active = 1;
end $
The problem that i'm having is that when it's the first register it inserts the email but with a 0 for example "name.last0#email.com" and if i try and insert with the same information shows the same email. I tried to change the logic in the if statements but still showed the same issues.
Your logic is backward. You have:
if((checkData = 1) and (checkData2 = 1)) then
But you want:
if((checkData = 0) and (checkData2 = 0)) then
You want to use the name without a customer ID when there AREN'T any existing records in the database with those names.
Note I'm not sure your check of looking for first names and last names separately is quite what you want. Wouldn't you want to check if the whole address is found as-is?
The way it's written, if someone named Joe Bloggs tries to join, he'll be joe.bloggs1 if there's anybody else named Joe in the system, even if there is no other Joe Bloggs.