How to efficiently count sequence breaks / holes in MySQL? - mysql

Suppose, that I have a following resultset:
SELECT
*
FROM
(
SELECT 1 as `no`, NULL as `sequence`
UNION ALL
SELECT 2, ''
UNION ALL
SELECT 3, '1'
UNION ALL
SELECT 4, '1,2,3,4,5'
UNION ALL
SELECT 5, '2,4,5'
UNION ALL
SELECT 6, '1, 5'
UNION ALL
SELECT 7, '1,3,5'
) as `sub`;
My task was to count sequence breaks / holes for each sequence listed in below. I've written following stored function:
DELIMITER $$
DROP FUNCTION IF EXISTS `countSequenceBreaks`$$
CREATE FUNCTION `countSequenceBreaks`(`sequence` VARCHAR(1000))
RETURNS INT
DETERMINISTIC
BEGIN
DECLARE `delimiter` CHAR DEFAULT ',';
DECLARE `current`, `last` INT;
DECLARE `result` INT DEFAULT 0;
IF
`sequence` IS NULL
OR
NOT LENGTH(`sequence`)
OR
NOT INSTR(`sequence`, `delimiter`)
THEN RETURN `result`;
END IF;
SET `current` = SUBSTRING_INDEX(`sequence`, `delimiter`, 1);
SET `last` = SUBSTRING_INDEX(`sequence`, `delimiter`, -1);
IF `last` < `current`
THEN
SET `result` = `last`;
SET `last` = `current`;
SET `current` = `result`;
SET `result` = 0;
END IF;
WHILE `current` < `last` DO
IF NOT FIND_IN_SET(`current`, `sequence`)
THEN SET `result` = `result` + 1;
END IF;
SET `current` = `current` + 1;
END WHILE;
RETURN `result`;
END$$
DELIMITER ;
But I'm worried about WHILE-loop might take too much iterations for different sequence members and cause query slowdown.
Questions:
Is there any way to improve the stored function?
If there is a way, then how?
My debug query:
SELECT
`no`, `sequence`, `countSequenceBreaks`(`sequence`)
FROM
(
SELECT 1 as `no`, NULL as `sequence`
UNION ALL
SELECT 2, ''
UNION ALL
SELECT 3, '1'
UNION ALL
SELECT 4, '1,2,3,4,5'
UNION ALL
SELECT 5, '2,4,5'
UNION ALL
SELECT 6, '1, 5'
UNION ALL
SELECT 7, '1,3,5'
) as `sub`;
It's resultset:
no sequence `countSequenceBreaks`(`sequence`)
-----------------------------------------------
1 NULL 0
2 0
3 1 0
4 1,2,3,4,5 0
5 2,4,5 1
6 1,5 3
7 1,3,5 2
Regards.

You can do it with one simple query:
select sequence,
CASE WHEN NOT INSTR(IFNULL(sequence,''), ',') THEN 0
ELSE
(
SUBSTRING_INDEX(sequence,',' ,-1)
-SUBSTRING_INDEX(sequence,',' , 1)
)
-
(LENGTH(sequence)-LENGTH(REPLACE(sequence,',','')))
END countSequenceBreaks
from t
How to find count of sequence breaks?
For example for 1,3,5 sequence.
All we need to know breaks count is to calculate count of missed delimiters. In this case the full string 1,2,3,4,5 contains 5-1=4 delimiters but the 1,3,5 sequence contains only 2 delimiters so count of breaks (missed digits - what is equal to count of missed delimiters as you can see) = 4-2 = 2
How to know count of delimiters in the string?
In our case when delimiter has one symbol length it is (LENGTH(sequence)-LENGTH(REPLACE(sequence,',',''))
SQLFiddle demo

Related

MySQL - Replicate "n" rows based of multiple records in a string [duplicate]

I have table :
id | name
1 | a,b,c
2 | b
i want output like this :
id | name
1 | a
1 | b
1 | c
2 | b
If you can create a numbers table, that contains numbers from 1 to the maximum fields to split, you could use a solution like this:
select
tablename.id,
SUBSTRING_INDEX(SUBSTRING_INDEX(tablename.name, ',', numbers.n), ',', -1) name
from
numbers inner join tablename
on CHAR_LENGTH(tablename.name)
-CHAR_LENGTH(REPLACE(tablename.name, ',', ''))>=numbers.n-1
order by
id, n
Please see fiddle here.
If you cannot create a table, then a solution can be this:
select
tablename.id,
SUBSTRING_INDEX(SUBSTRING_INDEX(tablename.name, ',', numbers.n), ',', -1) name
from
(select 1 n union all
select 2 union all select 3 union all
select 4 union all select 5) numbers INNER JOIN tablename
on CHAR_LENGTH(tablename.name)
-CHAR_LENGTH(REPLACE(tablename.name, ',', ''))>=numbers.n-1
order by
id, n
an example fiddle is here.
If the name column were a JSON array (like '["a","b","c"]'), then you could extract/unpack it with JSON_TABLE() (available since MySQL 8.0.4):
select t.id, j.name
from mytable t
join json_table(
t.name,
'$[*]' columns (name varchar(50) path '$')
) j;
Result:
| id | name |
| --- | ---- |
| 1 | a |
| 1 | b |
| 1 | c |
| 2 | b |
View on DB Fiddle
If you store the values in a simple CSV format, then you would first need to convert it to JSON:
select t.id, j.name
from mytable t
join json_table(
replace(json_array(t.name), ',', '","'),
'$[*]' columns (name varchar(50) path '$')
) j
Result:
| id | name |
| --- | ---- |
| 1 | a |
| 1 | b |
| 1 | c |
| 2 | b |
View on DB Fiddle
I have take the reference from here with changed column name.
DELIMITER $$
CREATE FUNCTION strSplit(x VARCHAR(65000), delim VARCHAR(12), pos INTEGER)
RETURNS VARCHAR(65000)
BEGIN
DECLARE output VARCHAR(65000);
SET output = REPLACE(SUBSTRING(SUBSTRING_INDEX(x, delim, pos)
, LENGTH(SUBSTRING_INDEX(x, delim, pos - 1)) + 1)
, delim
, '');
IF output = '' THEN SET output = null; END IF;
RETURN output;
END $$
CREATE PROCEDURE BadTableToGoodTable()
BEGIN
DECLARE i INTEGER;
SET i = 1;
REPEAT
INSERT INTO GoodTable (id, name)
SELECT id, strSplit(name, ',', i) FROM BadTable
WHERE strSplit(name, ',', i) IS NOT NULL;
SET i = i + 1;
UNTIL ROW_COUNT() = 0
END REPEAT;
END $$
DELIMITER ;
Here is my attempt:
The first select presents the csv field to the split.
Using recursive CTE, we can create a list of numbers that are limited to the number of terms in the csv field.
The number of terms is just the difference in the length of the csv field and itself with all the delimiters removed.
Then joining with this numbers, substring_index extracts that term.
with recursive
T as ( select 'a,b,c,d,e,f' as items),
N as ( select 1 as n union select n + 1 from N, T
where n <= length(items) - length(replace(items, ',', '')))
select distinct substring_index(substring_index(items, ',', n), ',', -1)
group_name from N, T
My variant: stored procedure that takes table name, field names and delimiter as arguments. Inspired by post http://www.marcogoncalves.com/2011/03/mysql-split-column-string-into-rows/
delimiter $$
DROP PROCEDURE IF EXISTS split_value_into_multiple_rows $$
CREATE PROCEDURE split_value_into_multiple_rows(tablename VARCHAR(20),
id_column VARCHAR(20), value_column VARCHAR(20), delim CHAR(1))
BEGIN
DECLARE id INT DEFAULT 0;
DECLARE value VARCHAR(255);
DECLARE occurrences INT DEFAULT 0;
DECLARE i INT DEFAULT 0;
DECLARE splitted_value VARCHAR(255);
DECLARE done INT DEFAULT 0;
DECLARE cur CURSOR FOR SELECT tmp_table1.id, tmp_table1.value FROM
tmp_table1 WHERE tmp_table1.value IS NOT NULL AND tmp_table1.value != '';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
SET #expr = CONCAT('CREATE TEMPORARY TABLE tmp_table1 (id INT NOT NULL, value VARCHAR(255)) ENGINE=Memory SELECT ',
id_column,' id, ', value_column,' value FROM ',tablename);
PREPARE stmt FROM #expr;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
DROP TEMPORARY TABLE IF EXISTS tmp_table2;
CREATE TEMPORARY TABLE tmp_table2 (id INT NOT NULL, value VARCHAR(255) NOT NULL) ENGINE=Memory;
OPEN cur;
read_loop: LOOP
FETCH cur INTO id, value;
IF done THEN
LEAVE read_loop;
END IF;
SET occurrences = (SELECT CHAR_LENGTH(value) -
CHAR_LENGTH(REPLACE(value, delim, '')) + 1);
SET i=1;
WHILE i <= occurrences DO
SET splitted_value = (SELECT TRIM(SUBSTRING_INDEX(
SUBSTRING_INDEX(value, delim, i), delim, -1)));
INSERT INTO tmp_table2 VALUES (id, splitted_value);
SET i = i + 1;
END WHILE;
END LOOP;
SELECT * FROM tmp_table2;
CLOSE cur;
DROP TEMPORARY TABLE tmp_table1;
END; $$
delimiter ;
Usage example (normalization):
CALL split_value_into_multiple_rows('my_contacts', 'contact_id', 'interests', ',');
CREATE TABLE interests (
interest_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
interest VARCHAR(30) NOT NULL
) SELECT DISTINCT value interest FROM tmp_table2;
CREATE TABLE contact_interest (
contact_id INT NOT NULL,
interest_id INT NOT NULL,
CONSTRAINT fk_contact_interest_my_contacts_contact_id FOREIGN KEY (contact_id) REFERENCES my_contacts (contact_id),
CONSTRAINT fk_contact_interest_interests_interest_id FOREIGN KEY (interest_id) REFERENCES interests (interest_id)
) SELECT my_contacts.contact_id, interests.interest_id
FROM my_contacts, tmp_table2, interests
WHERE my_contacts.contact_id = tmp_table2.id AND interests.interest = tmp_table2.value;
Because you have to keep adding "select number union all" in the example above which can be a issue if you need a large number of splits.
select
tablename.id,
SUBSTRING_INDEX(SUBSTRING_INDEX(tablename.name, ',', numbers.n), ',', -1) name
from
(select 1 n union all
select 2 union all select 3 union all
select 4 union all select 5) numbers INNER JOIN tablename
on CHAR_LENGTH(tablename.name)
-CHAR_LENGTH(REPLACE(tablename.name, ',', ''))>=numbers.n-1
order by
id, n
I decided a better way was this which only adds a number row for each digit. Example below is good for 1-1000 add another row makes it good for 1-10000 and so on.
select
tablename.id,
SUBSTRING_INDEX(SUBSTRING_INDEX(tablename.name, ',', numbers.n), ',', -1) name
from(SELECT #row := #row + 1 AS n FROM
(select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as t,
(select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as t2,
(select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as t3,
(SELECT #row:=0) as numbers)as numbers INNER JOIN tablename
on CHAR_LENGTH(tablename.name)
-CHAR_LENGTH(REPLACE(tablename.name, ',', ''))>=numbers.n-1
order by
id, n
CREATE PROCEDURE `getVal`()
BEGIN
declare r_len integer;
declare r_id integer;
declare r_val varchar(20);
declare i integer;
DECLARE found_row int(10);
DECLARE row CURSOR FOR select length(replace(val,"|","")),id,val from split;
create table x(id int,name varchar(20));
open row;
select FOUND_ROWS() into found_row ;
read_loop: LOOP
IF found_row = 0 THEN
LEAVE read_loop;
END IF;
set i = 1;
FETCH row INTO r_len,r_id,r_val;
label1: LOOP
IF i <= r_len THEN
insert into x values( r_id,SUBSTRING(replace(r_val,"|",""),i,1));
SET i = i + 1;
ITERATE label1;
END IF;
LEAVE label1;
END LOOP label1;
set found_row = found_row - 1;
END LOOP;
close row;
select * from x;
drop table x;
END
The original question was for MySQL and SQL in general. The example below is for the new versions of MySQL. Unfortunately, a generic query that would work on any SQL server is not possible. Some servers do no support CTE, others do not have substring_index, yet others have built-in functions for splitting a string into multiple rows.
--- the answer follows ---
Recursive queries are convenient when the server does not provide built-in functionality. They can also be the bottleneck.
The following query was written and tested on MySQL version 8.0.16. It will not work on version 5.7-. The old versions do not support Common Table Expression (CTE) and thus recursive queries.
with recursive
input as (
select 1 as id, 'a,b,c' as names
union
select 2, 'b'
),
recurs as (
select id, 1 as pos, names as remain, substring_index( names, ',', 1 ) as name
from input
union all
select id, pos + 1, substring( remain, char_length( name ) + 2 ),
substring_index( substring( remain, char_length( name ) + 2 ), ',', 1 )
from recurs
where char_length( remain ) > char_length( name )
)
select id, name
from recurs
order by id, pos;
Here is another trick. The number 20 is the maximum number of values in comma separated list.
We use single query, no procedures.
If tbl has more rows than the maximum number of values in single comma separated list you may remove 'inner join tbl a inner join tbl c' part from query. I added this because there are only 2 rows.
CREATE TABLE tbl(id int NOT NULL,name varchar(50),PRIMARY KEY (`id`));
insert into tbl values(1, 'a,b,c'), (2, 'd');
select id ,SUBSTRING_INDEX(SUBSTRING_INDEX(name, ',', k.n), ',', -1) as name
from tbl
INNER JOIN (
SELECT *
FROM (
SELECT #n:=#n+1 AS n
FROM tbl inner join tbl a inner join tbl c
INNER JOIN (SELECT #n:=0) AS _a
) AS _a WHERE _a.n <= 20
)AS k ON k.n <= LENGTH(name) - LENGTH(replace(name, ',','')) + 1
order by id
This is a trick to extract nth value in comma separated list:
SUBSTRING_INDEX(SUBSTRING_INDEX(name, ',', k.n), ',', -1)
Here is my solution
-- Create the maximum number of words we want to pick (indexes in n)
with recursive n(i) as (
select
1 i
union all
select i+1 from n where i < 1000
)
select distinct
s.id,
s.oaddress,
-- n.i,
-- use the index to pick the nth word, the last words will always repeat. Remove the duplicates with distinct
if(instr(reverse(trim(substring_index(s.oaddress,' ',n.i))),' ') > 0,
reverse(substr(reverse(trim(substring_index(s.oaddress,' ',n.i))),1,
instr(reverse(trim(substring_index(s.oaddress,' ',n.i))),' '))),
trim(substring_index(s.oaddress,' ',n.i))) oth
from
app_schools s,
n
Best Practice.
Result:
SELECT
SUBSTRING_INDEX(SUBSTRING_INDEX('ab,bc,cd',',',help_id+1),',',-1) AS oid
FROM
(
SELECT #xi:=#xi+1 as help_id from
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) xc1,
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) xc2,
(SELECT #xi:=-1) xc0
) a
WHERE
help_id < LENGTH('ab,bc,cd')-LENGTH(REPLACE('ab,bc,cd',',',''))+1
First, create a numbers table:
SELECT #xi:=#xi+1 as help_id from
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) xc1,
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) xc2,
(SELECT #xi:=-1) xc0;
| help_id |
| --- |
| 0 |
| 1 |
| 2 |
| 3 |
| ... |
| 24 |
Second, just split the str:
SELECT SUBSTRING_INDEX(SUBSTRING_INDEX('ab,bc,cd',',',help_id+1),',',-1) AS oid
FROM
numbers_table
WHERE
help_id < LENGTH('ab,bc,cd')-LENGTH(REPLACE('ab,bc,cd',',',''))+1
| oid |
| --- |
| ab |
| bc |
| cd |
SELECT id, unnest(string_to_array(name, ',')) AS names
FROM datatable
Hope this helps :D

MySQL: group rows by sum of a binary variable

I have a table that I want to group by a binary variable group_generator that defines end of the group: if it equals 1, then the group contains all previous rows with group_generator = 0
Example:
Numbers group_generator
10 0
20 0
30 1
40 0
50 1
60 1
I need Numbers grouped in three groups:
(10, 20, 30);
(40, 50);
(60)
I tried creating a new column with sum of group_generator for all rows with index less than current, like this:
Numbers group_generator group
10 0 0
20 0 0
30 1 0
40 0 1
50 1 1
60 1 2
and group by the last column, but that's complicated without temporary tables.
Is there an easy way do this in MySQL?
Once you have your new column, this query will give you the desired result:
SELECT GROUP_CONCAT(Numbers) FROM table GROUP BY `group`
Output:
group_concat(numbers)
10,20,30
40,50
60
So the whole query could be:
SELECT GROUP_CONCAT(Numbers),
(SELECT IFNULL(SUM(group_generator), 0) FROM table1 t2 WHERE t2.id < table1.id) AS `group`
FROM table1
GROUP BY `group`
Output
group_concat(Numbers) group
10,20,30 0
40,50 1
60 2
You could also produce this output with a stored procedure:
DELIMITER //
drop PROCEDURE if EXISTS groupit
//
create procedure groupit()
begin
declare num int;
declare numgroup varchar(1024) default '';
declare gnum int default 0;
declare pid int default 1;
declare gg int;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET gg = -1;
repeat
select group_generator, Numbers into gg, num from table2 where id=pid;
if (gg >= 0) then
set numgroup = concat(numgroup, if(numgroup='', '', ','), num);
if (gg = 1) then
select numgroup, gnum;
set numgroup = '';
set gnum = gnum + 1;
end if;
end if;
set pid=pid+1;
until gg = -1
end repeat;
end
//
delimiter ;

how to apply if and else if conditon in mysql Stored procedure

CREATE DEFINER=`root`#`localhost` PROCEDURE `apply_Leave_SP`(
in leavetypeid int ,
in empid int,
in reason varchar(100),
in startdate date,
in enddate date,
in startsession int,
in endsession int,
in compoffid int,
in mangerid int
)
BEGIN
declare leavestatus int(10) default 0;
declare optionalyHolidays int(10) default 0;
declare listofholidays int(10) default 0;
declare totalhours int (10) default 0;
declare hours int (10) default 0;
declare satsun int (10) default 0;
declare manger_id int(10) default 0;
declare paidleave int(10) default 0;
declare days int(10) default 0;
declare leaveappliedid int(10) default 0;
declare lossofpay int(10) default 0;
declare casualleave int (10) default 0;
declare sickleave int (10) default 0;
select count(holiday_id) into optionalyHolidays from Parabola.holidays where holiday_type_id=2 and
DATEDIFF(startdate,curdate())>=20;
select count(*) into listofholidays from Parabola.holidays where date between startdate and enddate;
SELECT
COUNT(*) AS total into hours
FROM
( SELECT ADDDATE(startdate, INTERVAL #i:=#i+1 DAY) AS DAY
FROM (
SELECT a.a
FROM (SELECT 0 AS a UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS a
CROSS JOIN (SELECT 0 AS a UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS b
CROSS JOIN (SELECT 0 AS a UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS c
) a
JOIN (SELECT #i := -1) r1
WHERE
#i < DATEDIFF(enddate, startdate)
) AS dateTable
WHERE WEEKDAY(dateTable.Day) IN (5,6);
SELECT datediff(enddate,startdate) into totalhours;
set hours=(totalhours-listofholidays-satsun)*8;
if(leavetypeid=1)then
set leavestatus=5;
else
set leavestatus=1;
end if;
if(optionalyHolidays>=0) then
set leavestatus=5;
end if;
insert into leave_applied(leave_type_id,hours,employee_id,
created_at,updated_at,start_date,end_date,start_date_session,
end_date_session,reason,
status,reminder_count,personal_calendar_event_id,system_calendar_event_id)
values(leavetypeid,hours,empid,curdate(),curdate(),startdate,enddate,
startsession,endsession,
reason,leavestatus,1,'sdasdas','sadeew');
Select LAST_INSERT_ID() into leaveappliedid ;
if((leaveappliedid=1 or 4) and days >paidleave) then
set lossofpay=days-paidleave;
else if((leaveappliedid=1 or 4) and days> #CL) then
set sickleave=days-#CL;
set casualleave=#CL;
else if((leaveappliedid=1 or 4) and days>#SL) then
set casualleave=days-#SL;
set sickleave=#SL;
else
set mangerid=10;
end if;
end;
insert into leave_actual (leave_applied_id,
leave_type_id,hours,start_date,
end_date,created_at,updated_at) values(leaveappliedid,leavetypeid,
hours,start_date,end_date,curdate(),curdate());
insert into leave_approval (leave_applied_id,
manager_id,status ,created_at,updated_at) values
(leaveappliedid,mangerid,leavestatus,curdate(),curdate());
END
this my code when i try to apply else if condition then there is Syntax Error is coming while only with if there is no Error is coming but i have to apply else if condition please suggest me where am doing wrong it show else if Condition
The syntax of if, else if is like this:
IF condition THEN
... ...
ELSEIF condition THEN
... ...
END IF;
See manual here.
IF search_condition THEN statement_list
[ELSEIF search_condition THEN statement_list] ...
[ELSE statement_list]
END IF

Mysql Row to Column with ';' separator

i have a table (Catalogs) on my Mariadb 10.1
id value
1 one ; two ; one
2 two ; three ; one
3 four ; five
4 one
5 four ; one
how do i count and group the value on Catalogs table like on the table below.
result count
one 5
two 2
three 1
four 2
five 1
or this table
id value
1 one
1 two
1 one
2 two
2 three
2 one
3 four
3 five
4 one
5 four
5 one
Taking reference from link http://www.marcogoncalves.com/2011/03/mysql-split-column-string-into-rows/
Assuming you have table named as table1 which contains two columns id and value and value column contains comma separated values.
Modified procedure:
CREATE PROCEDURE `explode_table`(bound VARCHAR(255))
BEGIN
DECLARE id INT DEFAULT 0;
DECLARE value TEXT;
DECLARE occurance INT DEFAULT 0;
DECLARE i INT DEFAULT 0;
DECLARE splitted_value varchar(25);
DECLARE done INT DEFAULT 0;
DECLARE cur1 CURSOR FOR SELECT table1.id, table1.value
FROM table1
WHERE table1.value != '';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
DROP TEMPORARY TABLE IF EXISTS table2;
CREATE TEMPORARY TABLE table2(
`id` INT NOT NULL,
`value` VARCHAR(56) NOT NULL
) engine=memory;
OPEN cur1;
read_loop: LOOP
FETCH cur1 INTO id, value;
IF done THEN
LEAVE read_loop;
END IF;
SET occurance = (SELECT LENGTH(value)
- LENGTH(REPLACE(value, bound, ''))
+1);
SET i=1;
WHILE i <= occurance DO
SET splitted_value =
trim((SELECT REPLACE(SUBSTRING(SUBSTRING_INDEX(value, bound, i),
LENGTH(SUBSTRING_INDEX(value, bound, i - 1)) + 1), ';', '')));
INSERT INTO table2 VALUES (id, splitted_value);
SET i = i + 1;
END WHILE;
END LOOP;
CLOSE cur1;
SELECT * FROM table2;
END
A plain SQL way of doing it, which will cope with up to 100 split delimited values (easily expanded to cope with more if necessary):-
SELECT result, COUNT(id)
FROM
(
SELECT id, SUBSTRING_INDEX(SUBSTRING_INDEX(value, ' ; ', tens.anum * 10 + units.anum + 1), ' ; ', -1) AS result
FROM Catalogs
CROSS JOIN
(SELECT 1 AS anum UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION SELECT 0) units
CROSS JOIN
(SELECT 1 AS anum UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION SELECT 0) tens
WHERE LENGTH(value) - LENGTH(REPLACE(value, ';', '')) >= ( tens.anum * 10 + units.anum)
) sub0
GROUP BY result

How to remove all the non numeric characters from column in mysql in bulk data

I want to remove all the non numeric characters from the column. I have bulk data in my database.
Currently I am using method as describe in below link:
http://venerableagents.wordpress.com/2011/01/29/mysql-numeric-functions/
The problem is that its taking too much time for preocessing.
For 1 million of row current logic takes 1 hour to process the data.
please help me..
Thank You,
Ronak
I assume you're doing something like:
update myTable set foo = NumericOnly(foo);
I don't know how much better you can do than that.
One thing that might help a bit, though. In that NumericOnly function, they're doing extra work. I'd remove the SET idx = LENGTH(val)+1; line, since all that will do is start checking the end of the string (the parts we've already checked) again. A string with 5 leading non-numerics would be checked, in full, 5 times.
Removing the line would leave:
DROP FUNCTION IF EXISTS NumericOnly;
CREATE FUNCTION NumericOnly (val VARCHAR(255))
RETURNS VARCHAR(255)
BEGIN
DECLARE idx INT DEFAULT 0;
IF ISNULL(val) THEN RETURN NULL; END IF;
IF LENGTH(val) = 0 THEN RETURN ""; END IF;
SET idx = LENGTH(val);
WHILE idx > 0 DO
IF IsNumeric(SUBSTRING(val,idx,1)) = 0 THEN
SET val = REPLACE(val,SUBSTRING(val,idx,1),"");
END IF;
SET idx = idx - 1;
END WHILE;
RETURN val;
END;
Here's another spin on things...
DEMO: http://sqlfiddle.com/#!2/0c96e/21
First, create yourself a numbers table
CREATE TABLE numbers (
number int NOT NULL PRIMARY KEY
);
INSERT INTO numbers (number)
SELECT n0 + n1 + n2 + n3 + n4 + n5
FROM (SELECT 0 AS n0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3) AS z0
CROSS
JOIN (SELECT 0 AS n1 UNION SELECT 4 UNION SELECT 8 UNION SELECT 12) AS z1
CROSS
JOIN (SELECT 0 AS n2 UNION SELECT 16 UNION SELECT 32 UNION SELECT 48) AS z2
CROSS
JOIN (SELECT 0 AS n3 UNION SELECT 64 UNION SELECT 128 UNION SELECT 192) AS z3
CROSS
JOIN (SELECT 0 AS n4 UNION SELECT 256 UNION SELECT 512 UNION SELECT 768) AS z4
CROSS
JOIN (SELECT 0 AS n5 UNION SELECT 1024 UNION SELECT 2048 UNION SELECT 3072) AS z5
ORDER
BY 1;
Here's some sample data to play with
CREATE TABLE your_table (
foo varchar(50)
);
INSERT INTO your_table (foo)
VALUES ('124nhasfonasf13')
, ('NONE')
, ('r937')
, ('o9o9')
, ('n444n4n455n')
, ('blah');
Then here's a query to give you just the numbers. Should be more efficient as it is SET based instead of iterative like your function example...
SELECT foo
, Group_Concat(c ORDER BY position SEPARATOR '')
FROM (
SELECT vals.foo
, numbers.number As position
, SubString(vals.foo, numbers.number, 1) As c
FROM (
SELECT foo
, Length(foo) As lngth
FROM your_table
WHERE foo REGEXP '[0-9]'
) As vals
INNER
JOIN numbers
ON numbers.number BETWEEN 1 AND vals.lngth
) As x
WHERE c REGEXP '[0-9]'
GROUP
BY foo