MySQL : Cascade update on same table - mysql

I have a MySQL table in which each records references its parent_id :
| id | Summary | parent_id | hidden |
-------------------------------------
| 1 | First | NULL | 0 |
| 2 | Hello | 1 | 0 |
| 3 | john | 1 | 0 |
| 4 | Second | NULL | 0 |
| 5 | World | 2 | 0 |
| 6 | Doe | 4 | 0 |
I would like to cascade update so that if line 1 becomes hidden, its child (line 2) and the childs of its childs (line 5) becomes also hidden.
Is it possible with MySQL.
I already have a DELETE ON CASCADE constraint which works fine.
CONSTRAINT FK_ID_With_CascadeDelete FOREIGN KEY (parent_id) REFERENCES MyTable (id) ON DELETE CASCADE

You can use Stored Procedure as follows.
DELIMITER $$
DROP PROCEDURE IF EXISTS `update_node`$$
CREATE PROCEDURE `update_node`(IN p_id INT)
proc: BEGIN
DECLARE e_no_id CONDITION FOR SQLSTATE '45000';
IF ( p_id IS NULL ) THEN
SIGNAL e_no_id SET MESSAGE_TEXT = 'The id cannot be empty.';
LEAVE proc;
END IF;
DROP TEMPORARY TABLE IF EXISTS del_temp_table;
CREATE TEMPORARY TABLE IF NOT EXISTS del_temp_table(
id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
delids INTEGER UNSIGNED NOT NULL,
node INTEGER DEFAULT 0
);
SET #rept := #loopcount := 1;
INSERT INTO del_temp_table (delids, node) SELECT p_id, #rept;
myloop: WHILE (1 = 1)
DO
SELECT COUNT(id) AS cnt FROM test WHERE `parent_id` IN ( SELECT DISTINCT(delids) FROM del_temp_table WHERE node = #rept ) INTO #loopcount;
IF (#loopcount = 0) THEN
LEAVE myloop;
ELSE
SET #rept := #rept + 1;
SELECT GROUP_CONCAT(d1.delids) INTO #wherein FROM ( SELECT DISTINCT(delids) FROM del_temp_table WHERE node = (#rept - 1) ) AS d1;
INSERT INTO del_temp_table (delids, node) SELECT id, #rept FROM test WHERE FIND_IN_SET( parent_id, #wherein ) > 0;
END IF ;
END WHILE myloop;
UPDATE test SET hidden = 1 WHERE id IN ( SELECT delids FROM del_temp_table );
END$$
DELIMITER ;
Here i used Temporary table to store recursive nodes and iterated each time by using that table to fetch next child and inserted it into same table.
I believe this could help you. Replace table name test with your table name

I think you simply can't do that. Even with an UPDATE TRIGGER you cannot update other rows on the same table.
Maybe this is not the answer you were expecting but probably your best choice is to use a PROCEDURE like this:
DELIMITER //
CREATE PROCEDURE UpdateHiden(IN pid INT, IN phidden INT)
BEGIN
UPDATE mytable
SET HIDDEN = phidden
WHERE ID = pid OR parent_id = pid;
END; //
DELIMITER ;
See sample on DB Fiddle here

Related

Mysql trigger when on update reserved cloumn sub capacity if statements

I want to set check class capacity if will be full class should be
confirm. When I update reserved it give error. Please help. Thank you.
CREATE TRIGGER `control_class_capacity`
AFTER UPDATE ON `learningcenter_class`
FOR EACH ROW BEGIN
IF NEW.capacity - NEW.reserved = 0 THEN BEGIN
UPDATE learningcenter_class SET isconfirm = 1 WHERE class_id = NEW.class_id;
END; END IF;
END
Assuming your class_id is unique you could change the trigger to a before trigger and set NEW.ISCONFIRM. BTW if statements in MYSQL do not require begin and end statements.
drop table if exists t;
create table t(class_id int ,capacity int, reserved int, isconfirm int);
insert into t values (1,1,0,0) , (2,1,0,0);
drop trigger if exists `control_class_capacity`;
delimiter $$
CREATE TRIGGER `control_class_capacity`
before UPDATE ON t
FOR EACH ROW BEGIN
IF NEW.capacity - NEW.reserved = 0 THEN
SET new.isconfirm = 1;
END IF;
END $$
delimiter ;
update t set reserved = 1 where class_id = 1;
select * from t;
+----------+----------+----------+-----------+
| class_id | capacity | reserved | isconfirm |
+----------+----------+----------+-----------+
| 1 | 1 | 1 | 1 |
| 2 | 1 | 0 | 0 |
+----------+----------+----------+-----------+
2 rows in set (0.00 sec)

Trigger to invalidate cache after update

I'm trying to write a trigger to invalidate the word index for my story database. However I can't seem to figure out how to stop the trigger from firing again during the indexing operation. I know I need to place an if statement to stop the update, but I can't seem to figure out what it should look like.
CREATE TRIGGER trMarkStoryForReindex BEFORE UPDATE ON Chapters
FOR EACH ROW BEGIN
-- any update to any chapter invalidates the index for the whole story
-- we could just periodically flush the story index, but this way is
-- better.
SET New.isIndexed = FALSE;
-- delete the index for that story
-- It will get rebuilt in at most 15 minutes
DELETE FROM freq WHERE storyid = NEW.StoryId;
END;
I basically want the trigger to fire only when isIndexed is has not been set in the update statement causing the trigger.
My data model looks like so:
Chapters
id
isIndexed
StoryId
Freq
word
storyid
Here's my proposal for solution. I've tested this on SQL fiddle, and it seems to work:
-- Database setup
create table chapters (
id int unsigned not null auto_increment primary key,
isIndexed boolean default false,
storyId int not null,
index idx_storyId(storyId)
);
create table freq (
word varchar(50),
storyId int not null,
index idx_storyId(storyId)
);
delimiter //
create trigger bi_chapters before update on chapters
for each row
begin
if new.isIndexed = false then
delete from freq where storyId = new.storyId;
end if;
end //
delimiter ;
insert into freq(word, storyId)
values ('one', 1), ('two', 1), ('three', 2);
insert into chapters(isIndexed, storyId)
values (true, 1), (true, 2);
When you select the values from freq (before updating chapters) you get this:
select * from chapters;
| id | isIndexed | storyId |
|----|-----------|---------|
| 1 | false | 1 |
| 2 | true | 2 |
select * from freq;
| word | storyId |
|-------|---------|
| one | 1 |
| two | 1 |
| three | 2 |
Now, do an update to chapters and select from freq again:
update chapters
set isIndexed = false
where storyId = 1;
select * from freq;
| word | storyId |
|-------|---------|
| three | 2 |
The only modification I did is that if block that checks if the new row is updated to false. If I've understood your question correctly, this would do what you need.
SQL fiddle example

SQL cursor & stored procedure order arrangement

I was working on a stored procedure to update the order field in a product table.
It works only the problem now is the the last item in the loop(cur), is increased twice instead of once (so dubbeled). Like so:
+-----------------+
|product + order |
|_id | |
| | |
| 1 | 0 |
| 2 | 1 |
| etc.. | etc..|
| 36 | 35 |
| 37 | 36 |
| 38 | 38 |
| |
+-----------------+
I cant figure out why. The link table(CategoryProduct) in this case goes to 38 with a category_id of 2 CALL curorder(2);
Stored procedure:
DELIMITER //
CREATE PROCEDURE curorder(
IN catid INT
)
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE i INT DEFAULT 0;
DECLARE p INT;
DECLARE cur CURSOR FOR SELECT product_id FROM test.CategoryProduct WHERE category_id = catid;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
read_loop: LOOP
FETCH cur INTO p;
UPDATE `test`.`Product` SET `order` = i WHERE `Product`.`product_id` =p;
SET i = i + 1;
IF done THEN
LEAVE read_loop;
END IF;
END LOOP;
CLOSE cur;
END //
DELIMITER ;
The Database is a Mysql Database. Any suggestions for improving the procedure are always welcome.
Thanks in advance.
EDIT:
I already tried to place the SET i STATEMENT beneath the IF STATEMENT but with no result.
You should put:
IF done THEN
LEAVE read_loop;
END IF;
Above your update statement, the last time mysql walks trough the loop is uses the old variables because there is no new 'p'. but i is incremented.
I good way to debug stored procedures is with a log table:
CREATE TABLE procedureLog
(
id INTEGER AUTO_INCREMENT,
description TEXT,
PRIMARY KEY (id)
);
For this case you can log the update parameters with the follow query:
INSERT INTO  `test`.`procedureLog` (`id` ,`description`) VALUES (null,  CONCAT('id: ', CAST(p as CHAR), ' order: ', CAST(i as CHAR)));
Good luck!

SQL IF - ELSE Statement

I have a table like this :
+-------+------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------+------+-----+---------+-------+
| ID | bigint(20) | NO | PRI | NULL | |
| view | bigint(20) | NO | | NULL | |
+-------+------------+------+-----+---------+-------+
Is it possible to do this with SQL's IF-ELSE statement ?
Check if there is ID=1 row in table
If there is , increase view column by 1 .
If there isn't, insert new row to table with ID=1
It should be something like this :
IF((SELECT COUNT(ID) FROM wp_viewcount WHERE ID=1) == 0)
BEGIN
INSERT INTO wp_viewcount VALUES (1,1)
END
ELSE
BEGIN
UPDATE wp_viewcount SET view=view+1 WHERE ID=1
END
The following SQL statement will result in the IF - ELSE logic you want, by using the on duplicate key syntax.
insert into wp_viewcount values(1,1) on duplicate key update view=view+1;
You can only use MySQL's if in a stored procedure. For example:
DELIMITER //
CREATE PROCEDURE `test_procedure` (IN wp_id INT)
BEGIN
IF( (SELECT COUNT(*) FROM wp_viewcount WHERE id = wp_id)<1) THEN
INSERT INTO wp_viewcount(id,view) VALUES (wp_id,1);
ELSE
UPDATE wp_viewcount SET view=view+1 WHERE ID=wp_id;
END IF;
END //
Given your use case, you might be better served by MySQL's INSERT ... ON DUPLICATE KEY UPDATE.
Why not use
if not exists (select * from wp_viewcount where id = 1)
begin
--insert logic
end
else
begin
--update logic
end

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.