Trigger to invalidate cache after update - mysql

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

Related

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.

MySQL : Cascade update on same table

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

MySQL: splits row into multiple rows with join table?

I have an existing table with the following structure
+-------------+-------+-------+-------+
| employee_id | val_1 | val_2 | val_3 | ...
+-------------+-------+-------+-------+
| 123 | A | B | C |
I want to change this single table into 2 tables - one which contains the values in seperate rows, and another with becomes a join table for this. For example, the above would be turned into this:
+-------------+--------+ +----+-------+
| employee_id | val_id | | id | value |
+-------------+--------+ +----+-------+
| 123 | 1 | | 1 | A |
+-------------+--------+ +----+-------+
| 123 | 2 | | 2 | B |
+-------------+--------+ +----+-------+
| 123 | 3 | | 3 | C |
+-------------+--------+ +----+-------+
What's the best SQL to use to convert the existing table into these 2 new tables? I can create the values table easy enough, but I'm not sure how to create the join table at the same times.
Something like this (psuedo-code only, sorry):
For each row in (SELECT employee_id, val_1, val_2, val_3 FROM existing_table)
{
for each val in (row.Values)
{
INSERT INTO new_values (val)
val_id = SELECT LAST_INSERT_ID();
INSERT INTO new_employees (employee_id, val_id);
}
}
There's probably a set-based way of doing this to avoid the loops... but sorry, I don't know what it is as like you, I'm not sure how to get the identity of the values table back into the parent employee table.
And also, while cursors are generally frowned on, this sort of one-off operation is exactly what they're designed for (ie I wouldn't recommend cursors for regular transaction or report processing, but for a re-structure of data.... why not?).
for the first result
`INSERT INTO new_val
SELECT emp_id, REPLACE(UPPER(column_name), 'VAL_', '') FROM
information_schema.COLUMNS ,
employee
WHERE TABLE_NAME = 'employee' AND TABLE_SCHEMA = 'myschema' AND column_name LIKE 'VAL_%'`;
using first result, populate the query and use it to insert into new table;
May be minor fine tuning required. Not tested
SELECT CONCAT('select ', new_val.number, ', VAL_', new_val.number, '
FROM employee, new_val
WHERE new_val.emp_id = employee.emp_id and new_val.number = ', val.number,
' union all' ) FROM
val ;
So here's what I ended up writing to do this. As the procedure name suggests, I was expecting there to be a more straightforward way of doing this!
CREATE PROCEDURE iWasHopingItWouldBeSimpler()
BEGIN
DECLARE loop_done BOOLEAN DEFAULT 0;
DECLARE emp_id BIGINT(20);
DECLARE val1 DECIMAL(19,2);
DECLARE val2 DECIMAL(19,2);
DECLARE val3 DECIMAL(19,2);
DECLARE emp CURSOR
FOR
SELECT employee_id, val1, val2, val3 FROM existing;
-- Declare continue handler
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET loop_done=1;
OPEN emp;
-- Loop through all rows
REPEAT
FETCH emp INTO emp_id, val1, val2, val3;
INSERT INTO new_values (value) VALUES(val1);
INSERT INTO new_join (employee_id, values_id) VALUES(emp_id, LAST_INSERT_ID());
INSERT INTO new_values (value) VALUES(val2);
INSERT INTO new_join (employee_id, values_id) VALUES(emp_id, LAST_INSERT_ID());
INSERT INTO new_values (value) VALUES(val3);
INSERT INTO new_join (employee_id, values_id) VALUES(emp_id, LAST_INSERT_ID());
-- End of loop
UNTIL loop_done END REPEAT;
CLOSE emp;
SET loop_done=0;
END;

How to get frequency of a word in a row using mysql fulltext

I have a MyISAM table comprising over 2 million records, on which there is a FULLTEXT index over multiple columns.
Given a search term, I would like to know how many times it occurs within the indexed fields of each record.
For example, when searching for 'test' within the following table (in which there is a FULLTEXT index over both the FREETEXT and Third_Col columns):
+----+--------------------------------------------+---------------------------+
| ID | FREETEXT | Third_Col |
+----+--------------------------------------------+---------------------------+
| 1 | This is first test string in test example. | This is first test Values |
| 2 | This is second test. | This is sec col |
+----+--------------------------------------------+---------------------------+
I expect results like:
+----+-------+
| ID | count |
+----+-------+
| 1 | 3 |
| 2 | 1 |
+----+-------+
I know that in the FULLTEXT index MySQL uses dtf (the number of times the term appears in the document); how can one obtain this?
Create a user defined function like this
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 ;
Then you can use this in your query as follows
select id, getCount(concat(FREETEXT, Third_col), 'test') from yourtable
Hope it helps

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