How to update every each rows in different values in MySQL? - mysql

id INT | food TEXT | memo TEXT
1 | Cucumbers | NULL
2 | Dandelions | NULL
3 | Salmons | NULL
3 | Cucumbers | NULL
4 | Tomatoes | NULL
Evening.
I have a table called results that splits each food values in several rows like the above.
A column named id refers to an individual animal, and a column food is an object what the animals would eat.
I would like to fill up a column memo by using REPLACE or UPDATE keywords like this:
3 | Salmons | Mostly liked it, only 15% didn't eat.
3 | Cucumbers | Only 10% have eaten, rest of all didn't even try.
A problem is MySQL always updates the same line like this:
3 | Salmons | Mostly liked it, only 15% didn't eat.
3 | Cucumbers | Mostly liked it, only 15% didn't eat.
This is my progress:
DROP FUNCTION IF EXISTS fx_length;
DROP FUNCTION IF EXISTS fx_splitter;
DROP FUNCTION IF EXISTS fx_split_row;
DROP PROCEDURE IF EXISTS App_forEach_memo;
DELIMITER //
CREATE FUNCTION fx_length(str TEXT, del VARCHAR(2), pos INT)
RETURNS INT
RETURN LENGTH(SUBSTRING_INDEX(str, del, pos - 1)) + 1 //
CREATE FUNCTION fx_splitter(str TEXT, del VARCHAR(2), pos INT)
RETURNS TEXT
RETURN SUBSTRING(SUBSTRING_INDEX(str, del, pos), fx_length(str, del, pos)) //
CREATE FUNCTION fx_split_row(str TEXT, del VARCHAR(2), pos INT)
RETURNS TEXT
BEGIN
DECLARE output TEXT;
SET output = REPLACE(fx_splitter(str, del, pos), del, '');
IF output = '' THEN SET output = NULL; END IF;
RETURN output;
END //
CREATE PROCEDURE App_forEach(IN target INT, strings TEXT)
BEGIN
DECLARE i INT DEFAULT 1;
REPEAT
UPDATE results
SET id = target, memo = fx_split_row(strings, '| ', i)
WHERE id = target;
SET i = i + 1;
UNTIL i = target
END REPEAT;
END //
DELIMITER ;
CALL App_forEach(3, "Mostly liked it, only 15% didn't eat.| Only 10% have eaten, rest of all didn't even try.");
I think I need to change this part SET id = target . . . for counting per same id numbers, but I don't know how to do this.
Are there any ways to update the memo values w/o creating an extra temp table?
Any tips, suggestions and answers for resolving this problem would be huge appreciated.
Thanks.

WITH cte AS ( SELECT food, ROW_NUMBER() OVER () rn
FROM results
WHERE #id = id )
UPDATE results, cte
SET results.memo = TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(#memo, '|', cte.rn), '|', -1))
WHERE results.food = cte.food
AND results.id = #id
AND cte.rn <= 1 + LENGTH(#memo) - LENGTH(REPLACE(#memo, '|', ''));
fiddle
You may put this query into a procedure if needed.
If you have ancient MySQL version which does not support CTE and window functions then emulate them in subquery using user-defined variables.

Related

Tokenize a delimited string in MySQL

I wrote a function in MySQL to split a delimited string into tokens, but right now, it only returns the first token it encounters after the start position (pos). Is there a way to get it to return tokens as a result set with one column containing all of the parsed tokens?
Here is my current function code:
CREATE DEFINER=`root`#`localhost` FUNCTION `split_string_multi_byte`(
source_string VARCHAR(255),
delim VARCHAR(12),
pos INT) RETURNS varchar(255) CHARSET latin1
BEGIN
RETURN REPLACE(SUBSTRING(SUBSTRING_INDEX(source_string, delim, pos),
CHAR_LENGTH(SUBSTRING_INDEX(source_string, delim, pos -1)) + 1),
delim, "");
END
Here are some parameters that I entered in my Navicat database development and admin client:
That returns the following results:
See how only "1" is returned. I would like to see all of the tokens if possible. However, I'm not sure how to do that.
Any help would be appreciated.
Thanks!
Rob
CREATE DEFINER=`root`#`%` FUNCTION `SPLIT`(s varchar(200), c char, i integer) RETURNS varchar(200) CHARSET utf8mb4
DETERMINISTIC
BEGIN
DECLARE retval varchar(200);
WITH RECURSIVE split as (
select 1 as x,substring_index(substring_index(s,c,1),c,-1) as y, s
union all
select x+1,substring_index(substring_index(s,c,x+1),c,-1),s from split where x<= (LENGTH(s) - LENGTH(REPLACE(s,c,'')))
)
SELECT y INTO retval FROM split WHERE x=i ;
return retval;
END
SELECT SPLIT('a,b,c,d,e,f',',',3); results in 'c'
Because you cannot return a table from a function (in MySQL), you could do something like this if you want results in a table:
SET #tekst = 'aap,noot,mies,wim,zus,jet';
WITH RECURSIVE abc as (
SELECT 1 as c
UNION ALL
SELECT c+1
FROM abc WHERE c<=15)
select c,split(#tekst,',',c) as t
from abc
where not split(#tekst,',',c) is null;
output:
+------+------+
| c | t |
+------+------+
| 1 | aap |
| 2 | noot |
| 3 | mies |
| 4 | wim |
| 5 | zus |
| 6 | jet |
+------+------+

Customized cyclic number design in database

I need a database design (mysql 8.0+) to support a cyclic number series from 1 to a specific max number, such as 1 to 3, then would be get 1,2,3,1,2,3,... as query result respectively and cyclically. My version has been worked successfully but hope seeking for maybe better, native version. Many thanks.
My scripts are here,
CREATE TABLE IF NOT EXISTS `cyclic_series_number` (
`category` VARCHAR(100) NOT NULL,
`sn` int NOT NULL,
`max` int NOT NULL,
PRIMARY KEY (`category`)
);
Afterwards, insert 2 records. The 1st record will be the one to test.
REPLACE INTO `cyclic_series_number` (`category`, `sn`, `max`)
VALUES ('testing', 1, 3), ('ticket', 1, 999);
SELECT * FROM `cyclic_series_number`;
+--------------------------+
| cyclic_series_number |
+---+-----------+----+-----+
| # | category | sn | max |
+---+-----------+----+-----+
| 1 | 'testing' | 1 | 3 |
+---+-----------+----+-----+
| 2 | 'ticket' | 1 | 999 |
+---+-----------+----+-----+
The last, offering a stored procedure.
The idea is to update (sn=sn+1) and get that number as well as a necessary check sn+1 to see if exceeds the max number.
All above logics run at the same time.
DROP PROCEDURE IF EXISTS `get_new_sn`;
DELIMITER //
CREATE PROCEDURE get_new_sn(IN input_category varchar(100))
BEGIN
SET #latest_sn = -1;
UPDATE `cyclic_series_number`
SET `sn` = (#latest_sn := case `sn` when `max` then 1 else `sn` + 1 end)
WHERE `category` = #input_category;
SELECT #latest_sn;
END //
DELIMITER ;
The testing result shows the stored procedure works.
CALL get_new_sn('testing'); -- 2
CALL get_new_sn('testing'); -- 3
CALL get_new_sn('testing'); -- 1
CALL get_new_sn('testing'); -- 2
CALL get_new_sn('testing'); -- 3
CALL get_new_sn('testing'); -- 1
-- ...
References
StackOverflow mysql-how-to-set-a-local-variable-in-an-update-statement-syntax
UPDATE sourcetable
SET sourcetable.num = subquery.num
FROM ( SELECT id, 1 + (ROW_NUMBER() OVER (ORDER BY id) - 1) % 99 num
FROM sourcetable ) subquery
WHERE sourcetable.id = subquery.id;
where 99 is upper limit.
keeping your stored procedure...
change line that starts:
SET sn = (#latest_sn := case sn when ... .. ...
to something like:
SET sn = (sn + 1) % max;
The modulo operator returns remainder after division... so if sn+1 is less than max then the remainder is sn+1. Once sn+1 = max, remainder = 0 and it starts over... This means too, that max needs to be 1 higher than highest allowed value... so if sn can be 99 but not 100, then max = 100.

Matching records from two tables

I have two Tables: ads_info and ads.
I want to match records from two tables.
SQL Schema for ads:
| id | title |
|----|-----------------------|
| 1 | This Dog is very nice |
SQL Schema for ads_info:
| id | infotext | tag |
|----|------------------------------|-----------|
| 1 | Dogs can eat a lot of things | dog, pets |
I want to check if the title of the Ads with id 1 has tags in ads_info. I have tried this:
SELECT * FROM `ads` where id = '1' UNION
SELECT * FROM `ads_info` where tag like '%ads.title%'
HERE IS SQL FIDDLE: LINK
Do you want a simple join?
select a.*, ai.tag,
(tag like concat('%', ads.title, '%')) as flag
from ads a join
ads_info ai
on ai.id = a.id;
The flag is, of course, false. It is rather hard to see situations where it would evaluate to true as you have expressed the logic.
Well you can do it this way : DEMO I am sure there are better ways and even this example can be better executed :) But it will maybe help...
First you create function for split and procedure for inserting those values in table(I have used here a answer from here LINK and corrected some small errors):
FUNCTION
CREATE FUNCTION SPLIT_STR(
x VARCHAR(255),
delim VARCHAR(12),
pos INT
)
RETURNS VARCHAR(255)
RETURN REPLACE(SUBSTRING(SUBSTRING_INDEX(x, delim, pos),
LENGTH(SUBSTRING_INDEX(x, delim, pos -1)) + 1),
delim, '');
PROCEDURE
CREATE PROCEDURE ABC(in fullstr VARCHAR(255))
BEGIN
DECLARE a int default 0;
DECLARE str VARCHAR(255);
simple_loop: LOOP
SET a=a+1;
SET str=SPLIT_STR(fullstr,",",a);
IF str='' THEN
LEAVE simple_loop;
END IF;
insert into my_temp_table values (str);
END LOOP simple_loop;
END;
I have created a table for this values:
create table my_temp_table (temp_columns varchar(100));
Called the procedure:
call ABC((select tag from ads_info));
And then you can use this:
Select * from ads B where exists
(select * from my_temp_table where
find_in_set(UPPER(trim(temp_columns)), replace(UPPER(B.TITLE), ' ', ',')) > 0 );
Or this:
SELECT * FROM ads, my_temp_table
WHERE find_in_set(UPPER(trim(temp_columns)), replace(UPPER(ads.TITLE), ' ', ',')) > 0 ;

Mysql:Query for the table as below

I have the following table:
|-------------------|
| id | Homephone |
|-------------------|
| 1 | 454454125 |
| 2 | 47872154587 |
| 3 | 128795423 |
| 4 | 148784474 |
|-------------------|
I have around 40.000 rows in the table.
I want to format Homephone values as following:
454-454-125
478-721-545-87
128-795-423
148-784-474
i.e. after every 3 numbers I want - (hyphen).
How to achieve this using MySQL?
You need to wite a udf for this
Basically, you need to create your own function (so called UDF - User Defined Function) and run it on the table.
There is a nice function by Andrew Hanna posted in String Functions chapter of the MySQL Reference Manual. I fixed a small mistake there (replaced WHILE (i < str_len) DO by WHILE (i <= str_len) DO.
There are two steps (two SQL queries):
Create the function. It has three parameters: str - the string to be modified, pos - position of the character being inserted into the string, delimit - character(s) to be inserted:
DELIMITER //
CREATE FUNCTION insert_characters(str text, pos int, delimit varchar(124))
RETURNS text
DETERMINISTIC
BEGIN
DECLARE i INT DEFAULT 1;
DECLARE str_len INT;
DECLARE out_str text default '';
SET str_len = length(str);
WHILE (i <= str_len) DO
SET out_str = CONCAT(out_str, SUBSTR(str, i, pos), delimit);
SET i = i + pos;
END WHILE;
-- trim delimiter from end of string
SET out_str = TRIM(trailing delimit from out_str);
RETURN(out_str);
END//
DELIMITER ;
Run the function...
...for testing purpose (select, no update):
SELECT insert_characters(Homephone, 3, "-") AS new_phone FROM my_table;
...to update the records:
UPDATE my_table SET Homephone = insert_characters(Homephone, 3, "-");
Please try to analyze the function line by line. This example may help you to understand the subject.

MySql: Count amount of times the words occur in a column

For instance, if I have data in a column like this
data
I love book
I love apple
I love book
I hate apple
I hate apple
How can I get result like this
I = 5
love = 3
hate = 2
book = 2
apple = 3
Can we achieve this with MySQL?
Here is a solution only using a query:
SELECT SUM(total_count) as total, value
FROM (
SELECT count(*) AS total_count, REPLACE(REPLACE(REPLACE(x.value,'?',''),'.',''),'!','') as value
FROM (
SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(t.sentence, ' ', n.n), ' ', -1) value
FROM table_name t CROSS JOIN
(
SELECT a.N + b.N * 10 + 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
ORDER BY n
) n
WHERE n.n <= 1 + (LENGTH(t.sentence) - LENGTH(REPLACE(t.sentence, ' ', '')))
ORDER BY value
) AS x
GROUP BY x.value
) AS y
GROUP BY value
Here is the full working fiddle: http://sqlfiddle.com/#!2/17481a/1
First we do a query to extract all words as explained here by #peterm(follow his instructions if you want to customize the total number of words processed). Then we convert that into a sub-query and then we COUNT and GROUP BY the value of each word, and then make another query on top of that to GROUP BY not grouped words cases where accompanied signs might be present. ie: hello = hello! with a REPLACE
If you want to perform such kind of text analysis, I would recommend using something like lucene, to get the termcount for each term in the document.
This query is going to take a long time to run if your table is of any decent size. It may be better to keep track of the counts in a separate table and update that table as values are inserted or, if real time results are not necessary, to only run this query every so often to update the counts table and pull your data from it. That way, you're not spending minutes to get data from this complex query.
Here's what I've for you so far. It's a good start. The only thing you need to do is modify it to iterate through the words in each row. You could use a cursor or a subquery.
Create test table:
create table tbl(str varchar(100) );
insert into tbl values('data');
insert into tbl values('I love book');
insert into tbl values('I love apple');
insert into tbl values('I love book');
insert into tbl values('I hate apple');
insert into tbl values('I hate apple');
Pull data from test table:
SELECT DISTINCT str AS Word, COUNT(str) AS Frequency FROM tbl GROUP BY str;
create a user defined function like this and use it in your query
DELIMITER $$
CREATE FUNCTION `getCount`(myStr VARCHAR(1000), myword VARCHAR(100))
RETURNS INT
BEGIN
DECLARE cnt INT DEFAULT 0;
DECLARE result INT DEFAULT 1;
WHILE (result > 0) DO
SET result = INSTR(myStr, myword);
IF(result > 0) THEN
SET cnt = cnt + 1;
SET myStr = SUBSTRING(myStr, result + LENGTH(myword));
END IF;
END WHILE;
RETURN cnt;
END$$
DELIMITER ;
Hope it helps
Refer This
Split-string procedure is not my job. You can find it here
http://forge.mysql.com/tools/tool.php?id=4
I wrote you the rest of code.
drop table if exists mytable;
create table mytable (
id int not null auto_increment primary key,
mytext varchar(1000)
) engine = myisam;
insert into mytable (mytext)
values ('I love book,but book sucks!What do you,think about it? me too'),('I love apple! it rulez.,No, it sucks a lot!!!'),('I love book'),('I hate apple!!! Me too.,!'),('I hate apple');
drop table if exists mywords;
create table mywords (
id int not null auto_increment primary key,
word varchar(50)
) engine = myisam;
delimiter //
drop procedure if exists split_string //
create procedure split_string (
in input text
, in `delimiter` varchar(10)
)
sql security invoker
begin
declare cur_position int default 1 ;
declare remainder text;
declare cur_string varchar(1000);
declare delimiter_length tinyint unsigned;
drop temporary table if exists SplitValues;
create temporary table SplitValues (
value varchar(1000) not null
) engine=myisam;
set remainder = input;
set delimiter_length = char_length(delimiter);
while char_length(remainder) > 0 and cur_position > 0 do
set cur_position = instr(remainder, `delimiter`);
if cur_position = 0 then
set cur_string = remainder;
else
set cur_string = left(remainder, cur_position - 1);
end if;
if trim(cur_string) != '' then
insert into SplitValues values (cur_string);
end if;
set remainder = substring(remainder, cur_position + delimiter_length);
end while;
end //
delimiter ;
delimiter //
drop procedure if exists single_words//
create procedure single_words()
begin
declare finish int default 0;
declare str varchar(200);
declare cur_table cursor for select replace(replace(replace(replace(mytext,'!',' '),',',' '),'.',' '),'?',' ') from mytable;
declare continue handler for not found set finish = 1;
truncate table mywords;
open cur_table;
my_loop:loop
fetch cur_table into str;
if finish = 1 then
leave my_loop;
end if;
call split_string(str,' ');
insert into mywords (word) select * from splitvalues;
end loop;
close cur_table;
end;//
delimiter ;
call single_words();
select word,count(*) as word_count
from mywords
group by word;
+-------+------------+
| word | word_count |
+-------+------------+
| a | 1 |
| about | 1 |
| apple | 3 |
| book | 3 |
| but | 1 |
| do | 1 |
| hate | 2 |
| I | 5 |
| it | 3 |
| lot | 1 |
| love | 3 |
| me | 2 |
| No | 1 |
| rulez | 1 |
| sucks | 2 |
| think | 1 |
| too | 2 |
| What | 1 |
| you | 1 |
+-------+------------+
19 rows in set (0.00 sec)
The code must be improved in order to consider any punctuation but this is the general idea.