Need help writing the case statement - MySQL Cursor - mysql

I created two columns in student table for my database. I now need to create a cursor for the student table. I need to make use of the input parameters for start and end student Id’s to filter the results in the cursor query.
I also need to open the cursor created and check if the student has an email assigned or not. If the email is not assigned I need to update the email column to have an email assigned to the student.
Finally to why I want help:
I cannot figure out how I would write the case statements for this cursor. I have tried to think of a good way to do this and not succeeded. Please help!!
DELIMITER $
DROP PROCEDURE IF EXISTS CURSOR_DEMO$
CREATE PROCEDURE CURSOR_DEMO(start_student_id INT
,end_student_id INT
)
BEGIN
DECLARE l_table_name VARCHAR(50);
DECLARE iam_done INT DEFAULT 0;
DECLARE l_sql_stmt VARCHAR(5000);
SET #l_sql_stmt='ALTER TABLE STUDENT ADD EMAIL VARCHAR';
SELECT #l_sql_stmt;
prepare stmt from #l_sql_stmt;
execute stmt;
SET #l_sql_stmt='ALTER TABLE STUDENT ADD PHONE int(10)';
SELECT #l_sql_stmt;
prepare stmt from #l_sql_stmt;
execute stmt;
DECLARE TBL_CUR CURSOR FOR
SELECT EMAIL FROM STUDENT.TABLES WHERE TABLE_SCHEMA='MYSQLDB';
BEGIN
DECLARE CONTINUE HANDLER FOR NOT FOUND SET IAM_DONE=1;
OPEN TBL_CUR;
tbl_loop:LOOP
FETCH tbl_cur INTO l_table_name;
IF IAM_DONE = 1 THEN
LEAVE tbl_loop;
END IF;
CASE WHEN l_table_name = 'STUDENT' THEN
ELSE BEGIN END;
END CASE;
END LOOP tbl_loop;
CLOSE TBL_CUR;
END;
END$
DELIMITER ;

Is this the kind of thing you need?
/*
drop table student;
delimiter $$
CREATE TABLE `student` (
`id` int(11) NOT NULL ,
`name` char(1) NOT NULL,
`email` varchar(1),
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8$$
drop table email;
delimiter $$
CREATE TABLE `email` (
`id` int(11) NOT NULL ,
`type` varchar(8) NOT NULL,
`person_id` int,
`email` varchar(1),
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8$$
;
*/
DELIMITER $
DROP PROCEDURE IF EXISTS CURSOR_DEMO$
CREATE PROCEDURE CURSOR_DEMO(start_student_id INT
,end_student_id INT)
BEGIN
declare email_person_id int;
declare email_address varchar(50);
DECLARE done INT DEFAULT 0;
DECLARE CUR CURSOR FOR
SELECT person_id,email FROM sandbox.email where type = 'student';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=1;
OPEN CUR;
cur_loop:LOOP
IF done = 1 THEN
LEAVE cur_loop;
END IF;
FETCH CUR INTO email_person_id,email_address;
update student
set email = email_address
where id = email_person_id
and email is null
;
END LOOP cur_loop;
CLOSE CUR;
commit;
END$
DELIMITER ;
truncate table student;
insert into student
values
(1,'A','1'),
(2,'B','1'),
(3,'C',null),
(4,'D',null),
(5,'E',null),
(6,'G',null),
(7,'F',null)
;
truncate table email;
insert into email
values
(1,'student',1,'a'),
(2,'student',2,'b'),
(3,'faculty',7,'z'),
(4,'student',3,'c')
;
select * from student;
call cursor_demo(1,10);
select * from student;

Related

MySQL MyISAM query still optimized when the first column is null

In MySQL offcial document for count,there are descriptions as below:
For MyISAM tables, COUNT(*) is optimized to return very quickly if the SELECT retrieves from one > table, no other columns are retrieved, and there is no WHERE clause. For example:
mysql> SELECT COUNT(*) FROM student;
This optimization only applies to MyISAM tables, because an exact row count is stored for this >storage engine and can be accessed very quickly. COUNT(1) is only subject to the same >optimization if the first column is defined as NOT NULL.
I want to test it for myself, and make a test as below,I make a table called system_user and the first column is type,all the value of type is null,however when I use SELECT COUNT(1) and SELECT COUNT(*) to query, I found the time cost is nearly the same,even if tried for serval times.
I am wonder why the first column is null and the optimization in MyISAM is still working?
When I use SQL_NO_CACHE,the time cost is still nearly the same:
related table:
CREATE TABLE `system_user` (
`type` varchar(10) DEFAULT NULL,
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(8) NOT NULL,
`age` int(11) DEFAULT NULL,
`tag` varchar(8) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
related procdure to create massive data:
DELIMITER $$
USE `test`$$
DROP PROCEDURE IF EXISTS `add_user_batch`$$
CREATE DEFINER=`root`#`%` PROCEDURE `add_user_batch`(IN COUNT INT)
BEGIN
DECLARE i INT;
DECLARE t_name VARCHAR(8);
DECLARE t_tag VARCHAR(20);
DECLARE t_age INT(2);
DECLARE t_sql_template VARCHAR(100);
DECLARE t_sql TEXT;
DECLARE t_tag_mod_val INT DEFAULT(25);
DECLARE t_commit_mod_val INT DEFAULT(100);
DECLARE t_start_time DATETIME;
DECLARE t_end_time DATETIME;
TRUNCATE TABLE `system_user`;
SET t_start_time=NOW();
SET t_sql_template = “INSERT INTO `system_user`(NAME, age, tag) VALUES“;
SET t_sql = t_sql_template;
SET i = 1;
WHILE i <= COUNT
DO
SET t_age = FLOOR(1 + RAND() * 60);
SET t_name = LEFT(UUID(), 8);
IF MOD(i, t_tag_mod_val) = 0 THEN
SET t_tag = “NULL“;
ELSE
SET t_tag = CONCAT(“'“,LEFT(UUID(), 8),“'“);
END IF;
SET t_sql = CONCAT(t_sql,“('“,t_name,“',“,t_age,“,“,t_tag,“)“);
IF MOD(i,t_commit_mod_val) != 0 THEN
SET t_sql = CONCAT(t_sql,“,“);
ELSE
SET t_sql = CONCAT(t_sql,“;“);
SET #insert_sql = t_sql;
PREPARE stmt FROM #insert_sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
COMMIT;
SET t_sql=t_sql_template;
END IF;
SET i = i + 1;
END WHILE;
IF LENGTH(t_sql) > LENGTH(t_sql_template) THEN
SET t_sql=CONCAT(SUBSTRING(t_sql,1,LENGTH(t_sql)-1),';');
SET #insert_sql = t_sql;
PREPARE stmt FROM #insert_sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
COMMIT;
END IF;
SET t_end_time=NOW();
SELECT CONCAT('insert data success,time cost ',TIMEDIFF(t_end_time,t_start_time)) AS finishedTag;
END$$
DELIMITER ;

Mysql Dynamic SQL is not allowed in stored function or trigger

To not publicly disclose our amount of invoices, we want to add random value between 2 ids.
Instead of [1,2,3] we want something like [69,98,179]
UUID is not an option in that project, unfortunately.
Using Mysql 5.7, 8, or MariaDb get the same results.
Here is the approach is taken:
Consider a simple table invoices as follows:
CREATE TABLE `invoices` (
`id` bigint(20) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=42 DEFAULT CHARSET=utf8mb4;
The function to get random values:
DROP FUNCTION IF EXISTS random_integer;
CREATE FUNCTION random_integer(value_minimum INT, value_maximum INT)
RETURNS INT
LANGUAGE SQL
NOT DETERMINISTIC
RETURN FLOOR(value_minimum + RAND() * (value_maximum - value_minimum + 1));
The function to get the next id:
DROP FUNCTION IF EXISTS next_invoice_id_val;
DELIMITER //
CREATE FUNCTION next_invoice_id_val ()
RETURNS BIGINT(8)
LANGUAGE SQL
NOT DETERMINISTIC
BEGIN
DECLARE lastId BIGINT(8) DEFAULT 1;
DECLARE randId BIGINT(8) DEFAULT 1;
DECLARE newId BIGINT(8) DEFAULT 1;
DECLARE nextId BIGINT(8) DEFAULT 1;
SELECT (SELECT MAX(`id`) FROM `invoices`) INTO lastId;
SELECT (SELECT random_integer(1,10)) INTO randId;
SELECT ( lastId + randId ) INTO nextId;
IF lastId IS NULL
THEN
SET newId = randId;
ELSE
SET newId = nextId;
END IF;
RETURN newId;
END //
DELIMITER ;
SELECT next_invoice_id_val();
and the trigger:
DROP TRIGGER IF EXISTS next_invoice_id_val_trigger;
DELIMITER //
CREATE TRIGGER next_invoice_id_val_trigger
BEFORE INSERT
ON invoices FOR EACH ROW
BEGIN
SET NEW.id = next_invoice_id_val();
END//
DELIMITER ;
That work like a charm, now if we want to generalize the behaviour to all tables.
We need a procedure to execute the query on any specific tables:
DROP PROCEDURE IF EXISTS last_id;
DELIMITER //
CREATE PROCEDURE last_id (IN tableName VARCHAR(50), OUT lastId BIGINT(8))
COMMENT 'Gets the last id value'
LANGUAGE SQL
NOT DETERMINISTIC
READS SQL DATA
BEGIN
SET #s := CONCAT('SELECT MAX(`id`) FROM `',tableName,'`');
PREPARE QUERY FROM #s;
EXECUTE QUERY;
DEALLOCATE PREPARE QUERY;
END //
DELIMITER ;
CALL last_id('invoices', #nextInvoiceId);
SELECT #nextInvoiceId;
The procedure for the next id value:
DROP PROCEDURE IF EXISTS next_id_val;
DELIMITER //
CREATE PROCEDURE next_id_val (IN tableName VARCHAR(50), OUT nextId BIGINT(8))
COMMENT 'Give the Next Id value + a random value'
LANGUAGE SQL
NOT DETERMINISTIC
READS SQL DATA
BEGIN
DECLARE randId BIGINT(8) DEFAULT 1;
SELECT (SELECT random_integer(1,10)) INTO randId;
CALL last_id(tableName, #currentId);
IF #currentId IS NULL
THEN
SET nextId = randId;
ELSE
SELECT ( #currentId + randId ) INTO nextId;
END IF;
END //
DELIMITER ;
CALL next_id_val('invoices', #nextInvoiceId);
SELECT #nextInvoiceId;
and the trigger:
# Call the procedure from a trigger
DROP TRIGGER IF EXISTS next_invoice_id_val_trigger;
DELIMITER //
CREATE TRIGGER next_invoice_id_val_trigger
BEFORE INSERT
ON invoices FOR EACH ROW
BEGIN
CALL next_id_val('invoices', #nextInvoiceId);
SET NEW.id = #nextInvoiceId;
END//
DELIMITER ;
and we get => Dynamic SQL is not allowed in stored function or trigger
I've read that storing in a temporary table might be a workaround, but as all posts have between 5 to 10 years old, I think we might have a better solution for such a straightforward case.
What is the workaround for using dynamic SQL in a stored Procedure
#1336 - Dynamic SQL is not allowed in stored function or trigger
Calling stored procedure that contains dynamic SQL from Trigger
Alternatives to dynamic sql in stored function

MySQL cursor returns only one record

I'm using Hostgator to host my PHP Website with MySQL 5.5.33 and PHPMyAdmin 3.5.5. I want to use cursor to get values from individual record in a table. The followings is the code for that stored procedure:
CREATE DEFINER=`lnutri`#`localhost` PROCEDURE `TestCursor`()
NO SQL
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE cGroupID INT;
DECLARE cGroupName VARCHAR(50);
DECLARE cursor1 CURSOR FOR SELECT GroupID, GroupName FROM Groups;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cursor1;
read_loop: LOOP
FETCH cursor1 INTO cGroupID,cGroupName;
IF done THEN
LEAVE read_loop;
END IF;
SELECT cGroupID AS GroupID, cGroupName as GroupName;
END LOOP;
CLOSE cursor1;
END
But the stored procedure seems to return the first record only:
GroupID GroupName
1 Default Group
This is the structure of the Groups table
CREATE TABLE IF NOT EXISTS `Groups` (
`GroupID` int(11) NOT NULL AUTO_INCREMENT,
`GroupName` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
`Description` varchar(500) COLLATE utf8_unicode_ci NOT NULL,
`DateCreated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`GroupID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=39 ;
And this is the Groups Table's data:
GroupID GroupName Description DateCreated
1 Default Group 0000-00-00 00:00:00
2 Group 2 0000-00-00 00:00:00
29 Regis 0000-00-00 00:00:00
31 Benetas 0000-00-00 00:00:00
32 Domain 0000-00-00 00:00:00
36 none 0000-00-00 00:00:00
37 Aevum 2013-11-11 17:40:56
38 Uniting Aged Care 2013-11-15 07:26:19
Your problem is that the stored procedure returns as many resultset as you have rows in your table and every resultset has only one row. Your client, which happens to be phpmyAdmin, doesn't process multiple resultsets. Thats why you see only one row.
To check that your cursor works just fine all you need is to temporarily create table (let's call it log) and then change
SELECT cGroupID AS GroupID, cGroupName as GroupName;
to
INSERT INTO log(group_id, group_name)
SELECT cGroupID AS GroupID, cGroupName as GroupName;
And you'll see that you after you call your procedure you'll have all your records in log table.
Here is SQLFiddle demo
Now it's absolutely impractical to do what you just did. If you really need to do some processing on a row by row basis and the return some results then
create a temporary table
inserted needed results in it while you iterate over the cursor
return the resultsest from the temp table to the client using select
drop the temporary table
To illustrate this let's say you want your procedure to return only every other row. Then your procedure might look like this
DELIMITER $$
CREATE PROCEDURE `TestCursor`()
BEGIN
DECLARE done, counter INT DEFAULT FALSE;
DECLARE cGroupID INT;
DECLARE cGroupName VARCHAR(50);
DECLARE cursor1 CURSOR FOR SELECT GroupID, GroupName FROM Groups;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
CREATE TEMPORARY TABLE log
(
id int not null auto_increment primary key,
group_id int,
group_name varchar(50)
);
OPEN cursor1;
read_loop: LOOP
FETCH cursor1 INTO cGroupID,cGroupName;
SET counter = counter + 1;
IF done THEN
LEAVE read_loop;
END IF;
IF counter % 2 <> 0 THEN
INSERT INTO log(group_id, group_name)
SELECT cGroupID AS GroupID, cGroupName as GroupName;
END IF;
END LOOP;
CLOSE cursor1;
SELECT * FROM log;
DROP TABLE log;
END
DELIMITER ;
Here is SQLFiddle

Split string procedure can't find table

I am trying to get this procedure to work and it is stumping me. I simply want this procedure to populate a temp table with the separated values from a table of CSV values.
DELIMITER $$
DROP PROCEDURE IF EXISTS String_Split $$
CREATE PROCEDURE String_Split
(
vString VARCHAR(255),
vSeparator VARCHAR(5)
)
BEGIN
DECLARE vDone tinyint(1) DEFAULT 1;
DECLARE vIndex INT DEFAULT 1;
DECLARE vSubString VARCHAR(15);
DROP TABLE IF EXISTS tmpValues;
CREATE TEMPORARY TABLE tmpValues (tmpVal VARCHAR(255));
WHILE vDone > 0 DO
SET vSubString = SUBSTRING(vString, vIndex,
IF(LOCATE(vSeparator, vString, vIndex) > 0,
LOCATE(vSeparator, vString, vIndex) - vIndex,
LENGTH(vString)
));
IF LENGTH(vSubString) > 0 THEN
SET vIndex = vIndex + LENGTH(vSubString) + 1;
INSERT INTO tmpValues VALUES (vSubString);
ELSE
SET vDone = 0;
END IF;
END WHILE;
END; $$
I call on it:
CALL String_Split(my_csv.keywords, ',');
And I get this:
Error Code: 1109. Unknown table 'my_csv' in field list
I'm not getting this because the table is there and the appropriate database is selected.
CREATE TABLE `my_csv` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`keywords` text NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 $$
INSERT INTO `my_csv` () VALUES
(1, 'featured, 3/8, Diamond, Engagement Ring, 14K, White Gold, Gold'),
(2, '1/3, Diamond, Engagement Ring, 14K, White Gold, Gold'),
(3, 'featured') $$
Instead of calling the table name and field in the call function, you need to get the value first, then call the procedure. Here's a working example.
SELECT #keyword :=keywords from my_csv;
CALL String_Split(#keyword, ',');
Edit:
That only grabbed the last row. This one selects all of them. Also, doing this the vSeparator variable in the procedure was too small, so I bumped that up to 255.
SELECT #keyword :=group_concat(keywords) FROM my_csv;
CALL String_Split(#keyword, ',');
select * from tmpValues;

MySQL INSERT with CALL

I have a stored procedure that splits a string and ends with a select.
I would like to run an insert on the stored procedure like you would do an insert on a select
Something like this
INSERT INTO ....
CALL sp_split...
My split looks like this:
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `split_with_id`(id INT, input varchar(1000), delim VARCHAR(10))
BEGIN
declare foundPos tinyint unsigned;
declare tmpTxt varchar(1000);
declare delimLen tinyint unsigned;
declare element varchar(1000);
drop temporary table if exists tmpValues;
create temporary table tmpValues
(
`id` int not null default 0,
`values` varchar(1000) not null default ''
) engine = memory;
set delimLen = length(delim);
set tmpTxt = input;
set foundPos = instr(tmpTxt,delim);
while foundPos <> 0 do
set element = substring(tmpTxt, 1, foundPos-1);
set tmpTxt = replace(tmpTxt, concat(element,delim), '');
insert into tmpValues (`id`, `values`) values (id, element);
set foundPos = instr(tmpTxt,delim);
end while;
if tmpTxt <> '' then
insert into tmpValues (`id`, `values`) values (id, tmpTxt);
end if;
select * from tmpValues;
END
Create a wrapper function and have it call the procedure. Then SELECT it normally.
DELIMITER $$
CREATE FUNCTION `f_wrapper_split` (strin VARCHAR(255))
RETURNS VARCHAR(255)
BEGIN
DECLARE r VARCHAR(255);
CALL sp_split(strin);
RETURN r;
END
$$
Of course, if sp_split returns multiple results, you'll need to adapt the function to, perhaps, take an INT input as well and return you that particular result. Then just call it multiple times.
It's not very pretty, but that's the best I can think of offhand.