MySQL: group rows by sum of a binary variable - mysql

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 ;

Related

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

Mysql group_concat limit rows in grouping

The next example is my database.
tb_port
id port
1 80
2 22
3 53
4 3128
5 443
tb_dest
id dest
1 network
2 local
tb_rule
id id_port id_dest
1 1 1
2 2 1
3 3 1
4 4 1
5 5 1
Select:
select dest,group_concat(port) from tb_port a, tb_dest b, tb_rule c where a.id=c.id_port and b.id=c.id_dest group by dest
Result:
network 80,22,53,3128,443
but is not the result I'm looking for, the result would be this.
Select ex:
select dest,group_concat(port limit 2) from tb_port a, tb_dest b, tb_rule c where a.id=c.id_port and b.id=c.id_dest group by dest
result I would like
network 80,22
network 53,3128
network 443
how to achieve this result only with SQL?
Sqlfiddle: http://sqlfiddle.com/#!2/d11807
MySQL doesn't make this kind of query easy, but one (admittedly not very pretty) solution is to use a variable to give each row a sequence number per dest and just group by the row number integer divided by 2 to get two numbers in each group;
SELECT dest, GROUP_CONCAT(port ORDER BY rank) ports
FROM (
SELECT dest, port, (
CASE dest WHEN #curDest
THEN #curRow := #curRow + 1
ELSE #curRow := 1 AND #curDest := dest END) rank
FROM tb_port a
JOIN tb_rule c ON a.id = c.id_port
JOIN tb_dest b ON b.id = c.id_dest,
(SELECT #curRow := 0, #curDest := '') r
ORDER BY dest
) z
GROUP BY FLOOR(rank/2),dest
ORDER BY dest, MIN(rank)
An SQLfiddle to test with.
Here is a stored proc,you just put in the delimiter when you call it
DELIMITER $$
DROP PROCEDURE IF EXISTS explode_table $$
CREATE PROCEDURE explode_table(bound VARCHAR(255))
BEGIN
DECLARE id TEXT;
DECLARE value TEXT;
DECLARE occurance INT DEFAULT 0;
DECLARE i INT DEFAULT 0;
DECLARE splitted_value TEXT;
DECLARE done INT DEFAULT 0;
DECLARE cur1 CURSOR FOR
select dest,group_concat(port) from tb_port a, tb_dest b, tb_rule c
where a.id=c.id_port and b.id=c.id_dest and dest != '' group by dest;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
DROP TEMPORARY TABLE IF EXISTS table2;
CREATE TEMPORARY TABLE table2(
`id` VARCHAR(255),
`value` VARCHAR(255) 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(CONCAT(value,bound))
- LENGTH(REPLACE(CONCAT(value,bound), bound, ''))
+1);
SET i=2;
WHILE i <= occurance DO
SET splitted_value =
SUBSTRING_INDEX(SUBSTRING_INDEX(CONCAT(value,bound),bound,i),bound,-2) ;
INSERT INTO table2 VALUES (id, splitted_value);
SET i = i + 2;
END WHILE;
END LOOP;
SELECT * FROM table2;
CLOSE cur1;
END; $$
CALL explode_table(',')

Loops within MySQL Stored procedures

I need to write a script that creates and calls a stored procedure named test. This procedure should calculate the common factors between 10 and 20. To find a common factor, you can use the modulo operator (%) to check whether a number can be evenly divided into both numbers. Then, this procedure should display a string that displays the common factors like this:
Common factors of 10 and 20: 1 2 5 Thanks in advance!
Here is what I have so far:
`USE my_guitar_shop;
DROP PROCEDURE IF EXISTS test;
-- Change statement delimiter from semicolon to double front slash
DELIMITER //
CREATE PROCEDURE test()
BEGIN
DECLARE counts INT Default 1;
DECLARE factor10;
DECLARE factor20;
DECLARE FACTORS varchar(100);
simple_loop: LOOP
SELECT 10
MOD counts
into factor10;
SELECT 20
MOD counts
into factor20;
WHEN (factor10 = 0 && factor20 = 0) THEN
SELECT concat("Common factors of 10 and 20:";
WHEN
END//
-- Change statement delimiter from semicolon to double front slash
DELIMITER ;
CALL test(); `
How about a version without loops?
CREATE PROCEDURE test(IN _first INT, _second INT)
SELECT CONCAT('Common factors of ', LEAST(_first, _second), ' and ', GREATEST(_first, _second), ': ', GROUP_CONCAT(n)) result
FROM
(
SELECT n
FROM
(
SELECT a.N + b.N * 10 + c.N * 100 + 1 n
FROM
(SELECT 0 AS N 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) a
,(SELECT 0 AS N 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) b
,(SELECT 0 AS N 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) c
ORDER BY n
) n
WHERE n <= LEAST(_first, _second)
HAVING _first MOD n = 0
AND _second MOD n = 0
) q;
Usage:
mysql> CALL test(10, 20);
+---------------------------------------+
| result |
+---------------------------------------+
| Common factors of 10 and 20: 1,2,5,10 |
+---------------------------------------+
1 row in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
mysql> CALL test(800, 1000);
+------------------------------------------------------------------+
| result |
+------------------------------------------------------------------+
| Common factors of 800 and 1000: 1,2,4,5,8,10,20,25,40,50,100,200 |
+------------------------------------------------------------------+
1 row in set (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Here is SQLFiddle demo
USE my_guitar_shop;
DROP PROCEDURE IF EXISTS test;
DELIMITER //
CREATE PROCEDURE test()
BEGIN
DECLARE factor10 INT;
DECLARE factor20 INT;
DECLARE counter INT;
DECLARE result VARCHAR(50);
SET factor10 = 10;
SET factor20 = 20;
SET counter = 1;
SET result = 'Common factors of 10 and 20: ';
WHILE (counter <= factor10/2) DO
IF (factor10 % counter = 0 AND factor20 % counter = 0) THEN
SET result = CONCAT(result, counter, ' ');
END IF;
SET counter = counter+1;
END WHILE;
/* IF (factor10 % factor10 = 0 AND factor20 % factor10 = 0) THEN
SET result = CONCAT(result, factor10, ' ');
END IF;
*/
SELECT result AS message;
END //
DELIMITER ;
CALL test();

How to efficiently count sequence breaks / holes in 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

save result of UNION query in local variable in MYSQL stored procedure

In my MySQL Stoared Procedure I want to store result of bellow query into local variables.
MySQL SP
BEGIN
Declare temp_ID bigint;
Declare temp_teamName Text;
(select ID,team1 from tbl_tournament_matches where leveID = 1 and tournamentID = 91 and matchType = 'L')
UNION
(select ID,team1 from tbl_tournament_matches where leveID = 2 and tournamentID = 91 and looserTeam is not null)
ORDER BY RAND() LIMIT 0,1;
select temp_ID, temp_teamName;
END;
How can I pass result of query into local variable?
note : above SP will return only 1 row.
You can achieve this without having to store the value into a variable.
SELECT * FROM(
select ID,team1 from tbl_tournament_matches where leveID = 1 and tournamentID = 91 and matchType = 'L'
UNION
select ID,team1 from tbl_tournament_matches where leveID = 2 and tournamentID = 91 and looserTeam is not null
) ORDER BY RAND() LIMIT 0,1
But, if you want to store a value for later use, you can use the INTO keyword:
SELECT id, data INTO #x, #y FROM test.t1 LIMIT 1;
http://dev.mysql.com/doc/refman/5.0/en/select-into.html