MySQL MyISAM query still optimized when the first column is null - mysql

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 ;

Related

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 Error Code: 1526. Table has no partition for value from column_list

I am using MySql and I am trying to create a Partition in a table, using a stored procedure.
I have the following table in MySql -
CREATE TABLE `snapshot` (
`user_id` bigint(20) NOT NULL,
`latest_transaction_id` bigint(20) NOT NULL,
`balance` decimal(15,2) NOT NULL DEFAULT '0.00',
`last_transaction_timestamp` bigint(20) NOT NULL,
`last_transaction_date` datetime NOT NULL,
`snapshot_date` date NOT NULL,
`year` smallint(6) NOT NULL
)
/*!50500 PARTITION BY LIST COLUMNS(`year`)
(PARTITION snapshot_2017 VALUES IN (2017),
PARTITION snapshot_2016 VALUES IN (2016),
PARTITION snapshot_2018 VALUES IN (2018)) */
I have written a stored procedure which is -
1. Getting current year name
2. Adding a prefix to the year
3. And then creating a new partition, using the prefix+year name
The idea is the data of a certain year, should belong to that particular year’s partition.
Following is the stored procedure -
CREATE DEFINER=`root`#`localhost` PROCEDURE `process_snapshot`()
BEGIN
DECLARE snapshot_year INT;
DECLARE partition_name VARCHAR(17);
DECLARE isPartionPresent VARCHAR(64);
SET snapshot_year := DATE_FORMAT(NOW(),'%Y');
SET partition_name := CONCAT('snapshot_',snapshot_year);
SET isPartionPresent := (SELECT DISTINCT PARTITION_NAME
FROM
INFORMATION_SCHEMA.PARTITIONS
WHERE
TABLE_NAME = 'snapshot'
AND PARTITION_NAME = partition_name);
IF isPartionPresent IS NULL THEN
SET #addPartition = CONCAT('ALTER TABLE snapshot ADD PARTITION (PARTITION ', partition_name, ' VALUES IN (', snapshot_year,'))');
PREPARE _stmt FROM #addPartition;
EXECUTE _stmt;
DEALLOCATE PREPARE _stmt;
END IF;
END
I am getting the following error when I run the SP -
Error Code: 1526. Table has no partition for value from column_list
However, if I run the following SP separately, I am able to create a partition successfully,
CREATE DEFINER=`root`#`localhost` PROCEDURE `create_table_partition`()
BEGIN
DECLARE snapshot_year INT;
DECLARE partition_name VARCHAR(17);
SET snapshot_year := DATE_FORMAT(NOW(),'%Y');
SET partition_name := CONCAT('snapshot_',snapshot_year);
SET #addPartition = CONCAT('ALTER TABLE snapshot ADD PARTITION (PARTITION ', partition_name, ' VALUES IN (', snapshot_year,'))');
PREPARE _stmt FROM #addPartition;
EXECUTE _stmt;
DEALLOCATE PREPARE _stmt;
END
But if I call this SP, from my main SP, it gives the same error,
What could be the issue?

the count of row( user) for each table begining with cm in the schema

I want all the tables' list with corresponding count of number of NULL entries in column 'user'. I have printed all table's name beginning with cm using
SELECT
TABLE_NAME
FROM
INFORMATION_SCHEMA.TABLES WHERE (TABLE_NAME LIKE 'cm%') ;
but for each table i want to run
SELECT COUNT(1) FROM <TABLENAME> WHERE `create_user` IS NULL
OR `create_time` IS NULL
and print
Create the table below, replacing it with the correct database name:
CREATE TABLE `db`.`tbl_count_null` (
`tableschema` varchar(64) NOT NULL DEFAULT '',
`tablename` varchar(64) NOT NULL DEFAULT '',
`qtd` char(0) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
See if the procedure below meets you:
CREATE DEFINER=`user`#`%` PROCEDURE `nameprocedure`()
BEGIN
DECLARE x, y LONGTEXT;
DECLARE done INT DEFAULT 0;
DECLARE databasesCursor CURSOR FOR
SELECT TABLE_SCHEMA, TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE 'cm%';
DECLARE CONTINUE HANDLER
FOR SQLSTATE '02000' SET done = 1;
OPEN databasesCursor;
myLoop: LOOP
FETCH databasesCursor INTO x, y;
IF NOT done THEN
SET #query = CONCAT("INSERT INTO`db`.`tbl_count_null` (tableschema, tablename, qtd) SELECT '",x,"' AS `schema`,'",y,"' AS `table`,COUNT(1) FROM `",x,"`.`",y,"` WHERE `create_user` IS NULL OR `create_time` IS NULL;");
PREPARE stmt FROM #query;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END IF;
END LOOP myLoop;
CLOSE databasesCursor;
END;
After creating the above procedure, call with the command:
call db.nameprocedure;
Verify that the records have been inserted into the table db.tbl_count_null

Need help writing the case statement - MySQL Cursor

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;

syntax error in mysql procedure error code 1064

I am trying to write one procedure, i am getting syntax error. I was trying to fix the same with the help of net, but failed.
Here is my stored procedure. Any help please?
The scenario is i am trying to take workspaceid column values from table hotelings and trying to make that value as my column for another table. Then i am trying to update the same column value with ; for a given start and end time of hoteling table in newly created table2.
My two tables are
CREATE TABLE `hotelings` (
`HotelingId` int(11) NOT NULL AUTO_INCREMENT,
`Description` longtext,
`StartDate` datetime NOT NULL,
`EndDate` datetime NOT NULL,
`BookedBy` longtext,
`BookingType` int(11) NOT NULL,
`RepeatType` longtext,
`RepeatDay` longtext,
`ProjectId` int(11) NOT NULL,
`WorkSpaceId` int(11) NOT NULL,
`starttime` varchar(45) DEFAULT NULL,
`endtime` varchar(45) DEFAULT NULL,
PRIMARY KEY (`HotelingId`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
CREATE TABLE `hotelingtime` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`Time` time DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
and my procedure is:
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `new_procedure`()
BEGIN
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'MAX(CASE WHEN workspaceid = ''',
workspaceid,
''' then "" ELSE NULL end) AS ',
CONCAT('`',workspaceid,'`')
)
) INTO #sql
FROM sms.hotelings;
SET #sql = CONCAT('CREATE TABLE IF NOT EXISTS table2 AS SELECT t.Time as Time, ', #sql, ' FROM sms.hotelings h, sms.hotelingtime t
GROUP BY t.Time');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
begin
declare num_rows int;
declare i int;
declare col_name varchar(50);
declare v varchar(10);
DECLARE v_finished INTEGER DEFAULT 0;
-- cursor to fetch column names
DECLARE col_names CURSOR FOR
SELECT column_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = 'table2'
ORDER BY ordinal_position;
-- declare NOT FOUND handler
DECLARE CONTINUE HANDLER
FOR NOT FOUND SET v_finished = 1;
OPEN col_names;
temp_hotelingloop: LOOP
FETCH col_names INTO col_name;
IF v_finished = 1 THEN
LEAVE temp_hotelingloop;
END IF;
begin
declare starttime time;
declare endtime time;
-- cursor to fetch start and end for a given workspaceid
DECLARE startendTime CURSOR FOR
SELECT starttime, endtime from hotelings
where workspaceid = col_name;
OPEN startendTime;
FETCH startendTime INTO starttime, endtime;
-- i am getting error here and not giving me the result.
SET #sql = CONCAT('update table2 set ''',#col_name ,''' = '';'' where time between ''',#starttime,''' and ''',#endtime,'''');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
CLOSE startendTime;
end;
SET i = i + 1;
END LOOP temp_hotelingloop;
CLOSE col_names;
end;
select * from table2;
DROP TABLE table2;
END
Any help please?
Thanks in advance
SET #sql = CONCAT('update table2 set ''',#col_name ,''' = '';'' where time between ''',#starttime,''' and ''',#endtime,'''');
You are using #col_name here, but on your declaration missing the #
declare col_name varchar(50);