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(',')
Related
In MySQL, how would I select each character in a given string as individual rows, if the length of the string is unknown. For example, if I have the string:
SET #str:='abc';
I would like a SELECT statement to produce:
rownum char
====== ====
1 a
2 b
3 c
If possible, I would like to avoid temp tables or stored procedures.
SELECT #var_i + 1 AS rownum, SUBSTR(#var_str, (#var_i := #var_i + 1), 1) AS `char`
FROM (SELECT (#var_str := "abc") AS _, (#var_i := 0) AS __) AS pow0
CROSS JOIN (SELECT 0 AS _ UNION ALL SELECT 0) AS pow1
CROSS JOIN (SELECT 0 AS _ UNION ALL SELECT 0) AS pow2
CROSS JOIN (SELECT 0 AS _ UNION ALL SELECT 0) AS pow3
CROSS JOIN (SELECT 0 AS _ UNION ALL SELECT 0) AS pow4 -- 8 < 10 < 16
WHERE #var_i < CHAR_LENGTH(#var_str)
;
With this procedure
CREATE DEFINER=`root`#`localhost` PROCEDURE `proc_splitstring`(IN _s TEXT)
BEGIN
DECLARE _i INTEGER;
DECLARE _n INTEGER;
DROP TABLE IF EXISTS listchar;
CREATE TEMPORARY TABLE listchar ( singlechar varchar(1));
SELECT LENGTH(_s) INTO _i;
SET _n = 0;
loop_label: LOOP
IF _n > _i THEN
LEAVE loop_label;
END IF;
SET _n = _n + 1;
INSERT INTO listchar SELECT SUBSTRING(_s,_n,1);
END LOOP;
SELECT * FROM listchar;
DROP TABLE listchar;
END
Result:
I have a routine. But it' s too slow. How can I improve the query?
My records: http://www.sqlfiddle.com/#!9/14cceb/1/0
My query:
CREATE DEFINER = 'root'#'localhost'
PROCEDURE example.ssa()
BEGIN
drop table if exists gps_table;
drop table if exists exam_datas;
CREATE TEMPORARY TABLE gps_table(ID int PRIMARY KEY AUTO_INCREMENT,timei
int,
trun_date_time datetime, tadd_meter int, tin_here int null);
insert into gps_table(timei,trun_date_time,tadd_meter,tin_here) select
imei, run_date_time, add_meter, in_here from example_table;
CREATE TEMPORARY TABLE exam_datas(ID int PRIMARY KEY AUTO_INCREMENT,vimei
int, vbas_run_date_time datetime, vbit_run_date_time datetime, vdifff int);
select tin_here from gps_table limit 1 into #onceki_durum;
select count(id) from gps_table into #kayit_sayisi;
set #i = 1;
set #min_mes = 0;
set #max_mes = 0;
set #frst_id = 0;
set #imei = 0;
set #run_date_time = '0000-00-00 00:00:00';
set #run_date_time2 = '0000-00-00 00:00:00';
myloop: WHILE (#i <= #kayit_sayisi) DO
select tin_here from gps_table where id = #i into #in_here_true;
if (#in_here_true = 1) then
select id,trun_date_time, tadd_meter from gps_table where id = #i into #frst_id,#run_date_time2, #min_mes;
select id from gps_table where id > #frst_id and tin_here =0 order by id asc limit 1 INTO #id;
SET #id = #id-1;
select id, timei, trun_date_time, tadd_meter from gps_table
where id = #id and tin_here =1 limit 1 into #i, #imei, #run_date_time, #max_mes;
if(#i-#frst_id>3) then
set #i:=#i+1;
insert into exam_datas(vimei,vbas_run_date_time,vbit_run_date_time,vdifff) Values (#imei, #run_date_time2, #run_date_time, #max_mes-#min_mes);
SELECT * FROM exam_datas;
SET #asd =1;
elseif 1=1 then
set #i:=#i+1;
End if;
ELSEIF 1=1
THEN SET #i:=#i+1;
End if;
IF (#i = #kayit_sayisi)
THEN set #tamam =1; LEAVE myloop;
END IF;
END WHILE myloop;
select DISTINCT * from exam_datas;
drop table if exists exam_datas;
drop table if exists gps_table;
END
I need: id= 6 first true and id= 11 last_true
firs_trure - last_true = 304-290= 14
id=14 first true and id=18 last_true
firs_true - last_true = 332-324= 8
This routine is too slow.
MySql version is 5.7 and There are 2 milions record in the table.
UPDATE:
Query is here. HERE
Thank you #LukStorms
It's possible to get such results in 1 query.
Thus avoiding a WHILE loop over records.
This example works without using window functions. Just using variables inside the query to calculate a rank. Which is then used to get the minimums and maximums of the groups.
select
imei,
min(run_date_time) as start_dt,
max(run_date_time) as stop_dt,
max(add_meter) - min(add_meter) as diff
from
(
select imei, id, run_date_time, add_meter, in_here,
case
when #prev_imei = imei and #prev_ih = in_here then #rnk
when #rnk := #rnk + 1 then #rnk
end as rnk,
#prev_imei := imei as prev_imei,
#prev_ih := in_here as prev_ih
from example_table t
cross join (select #rnk := 0, #prev_ih := null, #prev_imei := null) vars
order by imei, id, run_date_time
) q
where in_here = 1
group by imei, rnk
having count(*) > 4
order by imei, min(id);
In the procedure such query can be used to fill that final temporary table.
A test on db<>fiddle here
I hava stored procedure, that insert into adress table, it has 3 in parametrs and 1 out parametr. If I call this procedure out parametr is null
CREATE DEFINER=`root`#`localhost`
PROCEDURE `insert_adress`(
in d varchar(50),
in r varchar(50),
in c varchar(50),
in adress_text varchar(255),
out id int)
BEGIN
declare disc_id int;
declare reg_id int;
declare coun_id int;
if ((select count(*) from district where district.dist like d) <> 0)
then set disc_id := (select id from district where district.dist like d);
else
insert into district(dist) values(d);
set disc_id := (select max(id) from district);
end if;
if ((select count(*) from region where region.reg like r) <> 0)
then set reg_id := (select id from region where region.reg like r);
else
insert into region(reg) values(r);
set reg_id := (select max(id) from region);
end if;
if ((select count(*) from country where country.coun like c) <> 0)
then set coun_id := (select id from country where country.coun like c);
else
insert into country(coun) values(c);
set coun_id := (select max(id) from country);
end if;
insert into adress(district,region,country,adress_text) values(disc_id,reg_id,coun_id,adress_text);
set id := (select max(id) from adress);
END
I call it:
set #id = 0;
call mustaqil.insert_adress('test1', 'test1', 'test1', 'test1', #id);
select #id;
District,Region,Country tables have 'test1' row
I except that, out id, disc_id, reg_id, coun_id should be number, but it is null
P.S : stored procedure inserts row
just change set id := (select max(id) from adress);
to
select max(id) into id from adress;
it should work
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
I have the query working, just wondering if there is a better way to do this without cursors/loops/php side. I've been a DBA for 5+ years and just came across the := statement. Very cool.
Table (tblPeople) with the person ID and the number of tickets they bought.
PersonId NumTickets
1 3
2 1
3 1
I then want to assign individual tickets to each person in a new table (tblTickets), depending on how many tickets they bought. The TicketId is a key, auto increment column.
TicketId PersonId
100 1
101 1
102 1
103 2
104 3
Here is the code. It loops through the whole tblPeople over and over again incrementing a new calculated column called rowID. Then I filter out the rows based on the number of tickets they bought in the WHERE clause. The problem I see is the subquery is huge, the more people I have, the bigger the subquery gets. Just not sure if there is a better way to write this.
INSERT INTO tblTickets (PersonId)
SELECT PersonId
FROM (
SELECT s.PersonId, s.NumTickets,
#rowID := IF(#lastPersonId = s.PersonId and #lastNumTickets = s.NumTickets, #rowID + 1, 0) AS rowID,
#lastPersonId := s.PersonId,
#lastNumTickets := s.NumTickets
FROM tblPeople m,
(SELECT #rowID := 0, #lastPersonId := 0, #lastNumTickets := 0) t
INNER JOIN tblPeople s
) tbl
WHERE rowID < NumTickets
I'd add a utility table Numbers which contains all the numbers from 1 up to the maximal number of tickets a person may buy. Then you can do something like this:
INSERT INTO tblTickets (PersonId)
SELECT s.PersonId
FROM tblPeople s, Numbers n
WHERE n.number <= s.NumTickets
Following Stored procedure will serve your purpose...
DELIMITER $$
USE <your database name> $$
DROP PROCEDURE IF EXISTS `update_ticket_value2`$$
CREATE PROCEDURE `update_ticket_value2`()
BEGIN
DECLARE index_value INT;
DECLARE loop_variable INT;
SET #KeyValue = 100;
SET #LastPersonID = 0;
SET #TicketNum = 0;
SET #PersonIDToHandle = 0;
SELECT #PersonIDToHandle = PersonID, #TicketNum = NumTickets
FROM tblPeople
WHERE PersonId > #LastPersonID
ORDER BY PersonId
LIMIT 0,1;
WHILE #PersonIDToHandle IS NOT NULL
DO
SET loop_variable = 0;
WHILE(loop_variable < #TicketNum) DO
INSERT INTO tblTickets(TicketId, PersonId) VALUES(#KeyValue + loop_variable, #PersonIDToHandle);
SET loop_variable = loop_variable + 1;
END WHILE;
SET #LastPersonID = #PersonIDToHandle;
SET #PersonIDToHandle = NULL;
SET #KeyValue = #KeyValue + #TicketNum;
SELECT #PersonIDToHandle := PersonID, #TicketNum := NumTickets
FROM tblPeople
WHERE PersonId > #LastPersonID
ORDER BY PersonId
LIMIT 0,1;
END WHILE;
END$$
DELIMITER ;
Call the procedure as:
CALL update_ticket_value2();
Hope it helps...