mysql trigger with select from database and update a column - mysql

I have this trigger. If the incoming log agrees with input filter, than is not saved into database. But, I want to keep number of "hits" of each Primitive_filter. I have a column named hit_rate, which is int(30). Is there some way how to do that? Maybe specific error? Or sth else? Thx for help.
UPDATE Primitive_filters SET hit_rate = hit_rate + 1 where Primitive_filters.id = ???;
trigger
delimiter //
CREATE TRIGGER inputFilter
before insert
on Logs
for each row
begin
declare msg varchar(255);
IF (SELECT COUNT(*) FROM Primitive_filters, Primitive_in_filter, Filters WHERE
Filters.name = "input" AND Filters.id = Primitive_in_filter.id_filter AND Primitive_in_filter.id_primitive = Primitive_filters.id AND
(Primitive_filters.id_host LIKE CONCAT('%',(SELECT host FROM Hosts WHERE id = new.id_host),'%') OR Primitive_filters.id_host IS NULL) AND
(Primitive_filters.facility LIKE CONCAT('%',new.facility,'%') OR Primitive_filters.facility IS NULL) AND
(Primitive_filters.priority LIKE CONCAT('%',new.priority,'%') OR Primitive_filters.priority IS NULL) AND
(Primitive_filters.program LIKE CONCAT('%',new.program,'%') OR Primitive_filters.program IS NULL) AND
(new.msg REGEXP Primitive_filters.msg OR Primitive_filters.msg IS NULL)) > 0 THEN CALL raise_error; END IF;
END //
delimiter ;

This is NOT the answer to your question.
It's only a hint how to fix a potentially serious performance problem in your code.
Don't use this:
IF (SELECT COUNT(*) FROM ... giant query ...) > 0
THEN CALL raise_error;
END IF;
Use this instead:
IF EXISTS (SELECT 1 FROM ... giant query ...)
THEN CALL raise_error;
END IF;
The former condition calculates a count ... it must read all rows returned by the query
If the query returns billion rows, it must reads them all --> because you asked give me a count of rows.
Then, after the query return the count, there is a check: if the query returns at least one row, then do something.
The latter condition stops executing the query when the query returns first row, saving time and resources.

Related

Problem with comparison within a trigger statement using a variable gotten from query

I am trying to use an attribute from a 2nd table in the trigger of the 1st. To do this I am trying to load that value into a variable and then use it as a comparison.
However whenever I try and test the process the comparison answers false.
DELIMITER $$
create trigger evolve_persona before update on phantom_thieves
for each row begin
set #t := (select tier from persona where pname = old.persona);
if((new.persona != old.persona) and (select cast(#t as unsigned) = '1')) then
set
new.st = old.st+10, new.ma = old.ma+10, new.en= old.en+10, new.ag= old.ag+10,
new.lu= old.lu+20;
end if;
end$$
DELIMITER ;
I can see nothing wrong with your trigger but, this is somewhat more complicated as be written in a comment.
Make please following
SET #t = -1;
SELECT #t; -- returns -1
update phantom_thieves SET .....
SELECT #t; -should display at sometime 1
This seems to be the only problem that tier don't has the response 1 and with above, you can see what you get.

Execute query according to the result of another

I have a query that gives output "OK".
I want to use that result from the query to run another query.
If result is OK then don't run.
If it is ERROR, then run.
The queries are slow and I don't want to execute the second query if the result is OK. The second query checks what is wrong.
How I can do this? I checked in the Internet but I don't know if there is such function and it's name. I don't want to use nested queries or WITH expressions
Thanks!
Use Try and Catch to error handling and do what you want to execute as per below:
If Query 1 is executed then query 2 skip otherwise execute query 2 if any error in query 1:
Begin Try
--Query 1
End Try
Begin Catch
-- Query 2
End catch
you can put result of the query into variable and then check it using IF...ELSE
declare #test varchar(10) = 'ok';
if #test != 'ok'
begin
--query if not 'ok'
end
else
begin
--query if 'ok', or you can remove `else` at all if not required
end
DEMO
Well, you can create a variable in which you store the result of a function that returns 'OK' or 'KO' (How to create a function with SQL Server).
Then you can use an IF statement where you execute different queries
IF statement SQL Server.
This could be an idea.
Capture result set into a variable. Also ensure your condition returns one or other and only one column
I have added a sample query
declare #output VARCHAR(10)
SET #OUTPUT =
--sample query
(select case when SUM(s.SellingValue) = SUM(h.SellingValue) then 'OK' ELSE 'ERROR' END OUTPUT
from Missing_Sales s inner join Missing_Sales_TH h on s.Branch_no = h.Branch_no and s.date = h.Date)
BEGIN
IF #output = 'OK'
--sample query
SELECT * FROM Missing_Sales
ELSE
--sample query
SELECT * FROM Missing_Sales_TH
END

Want to generate unique Id's using a function in mysql

I wrote a function to generate unique id's,its working but sometimes two people are getting same id,I mean duplicates are formed. My unique id looks like
2016-17NLR250001, I deal with only last four digits 0001. I am posting my function please correct it and please help me in avoiding duplicates even though users login into same account or if they do it on same time.
MY FUNCTION:
DELIMITER $$
USE `olmsap`$$
DROP FUNCTION IF EXISTS `fun_generate_uniqueid`$$
CREATE DEFINER=`root`#`%` FUNCTION `fun_generate_uniqueid`( V_DATE DATE,V_MANDALID INT ) RETURNS VARCHAR(30) CHARSET latin1
DETERMINISTIC
BEGIN
DECLARE MDLCODE VARCHAR(5);
SET MDLCODE = ' ';
SELECT COUNT(*) INTO #CNT FROM `st_com_mandal` WHERE MANDAL_VS_MC=V_MANDALID;
SELECT dist_mandal_code INTO MDLCODE FROM `st_com_mandal` WHERE MANDAL_VS_MC=V_MANDALID;
IF #CNT>0 THEN
SET #YR=`FUN_FISCAL_YR`(V_DATE);
SELECT CONCAT(IF(DIST_SAN_CODE='GUN','GNT',DIST_SAN_CODE),IFNULL(`dist_mandal_code`,'NULL'))INTO #MANDAL
FROM `st_com_dist` SCD INNER JOIN `st_com_mandal` STM ON STM.`mandal_dist_id`= SCD.`DIST_VC_DC` WHERE MANDAL_VS_MC=V_MANDALID;
IF MDLCODE >0 THEN
SELECT COUNT(Soil_Sample_ID)+1 INTO #ID FROM `tt_mao_soil_sample_dtls` WHERE MANDAL_ID=V_MANDALID AND SUBSTR(UNIQUE_ID,1,7)=#YR ;
ELSE
SELECT COUNT(Soil_Sample_ID)+1 INTO #ID FROM `tt_mao_soil_sample_dtls` WHERE SUBSTR(UNIQUE_ID,1,14)=CONCAT(#YR,#MANDAL) ;
END IF ;
IF LENGTH(#ID)=1 THEN
SET #ID=CONCAT('000',#ID);
ELSEIF LENGTH(#ID)=2 THEN
SET #ID=CONCAT('00',#ID);
ELSEIF LENGTH(#ID)=3 THEN
SET #ID=CONCAT('0',#ID);
ELSE
SET #ID=#ID;
END IF ;
RETURN CONCAT(#YR,#MANDAL,#ID);
ELSE
RETURN 'Mandal Doesnt Exists';
END IF;
END$$
DELIMITER ;
I do not think community will be able to help you with this question. This is a complex function that requires very careful analysis of table / index access and locking.
The only thing I can recommend is to not use existing table data to calculate next sequence as this is a bad practice.
Besides Race conditions that you are experiencing you will also get problems if the record with the last sequence is deleted.
I suggest you read this to get an idea on how to write a custom sequence generator:
http://en.latindevelopers.com/ivancp/2012/custom-auto-increment-values/

Updating a column name of a same table which has a parent child relationship using mysql

I searched a lot of doing a task but found no appropriate solution.
Basically the scenario is. I have a user_comment table in which there are 5 column(id,parent_id,user_comments,is_deleted,modified_datetime). There is a parent child relationship like 1->2,1->3,2->4,2->5,5->7 etc. Now i am sending the id from the front end and i want to update the column is_deleted to 1 and modified_datetime on all the records on
this id as well as the all the children and children's of children.
I am trying to doing this by using a recursive procedure. Below is the code of my procedure
CREATE DEFINER=`root`#`localhost` PROCEDURE `user_comments`(
IN mode varchar(45),
IN comment_id int,
)
BEGIN
DECLARE p_id INT DEFAULT NULL ;
if(mode = 'delete')
then
update user_comment set is_deleted = 1, modified_datetime = now()
where id = comment_id ;
select id from user_comment where parent_id = comment_id into p_id ;
if p_id is not null
then
SET ##GLOBAL.max_sp_recursion_depth = 255;
SET ##session.max_sp_recursion_depth = 255;
call user_comments('delete', p_id);
end if;
end if;
END
By using this procedure it give me an error of more than one row.
If i return the select query without giving it to variable then shows me the the appropriate results on the select query but i have to call this procedure recursively based on getting the ids of the select query.
I need help i have already passed 2 days into this.
I used cursor also. Below is the code of cursor
CREATE DEFINER=`root`#`localhost` PROCEDURE `user_comments`(
IN mode varchar(45),
IN comment_id int,
)
BEGIN
DECLARE p_emp int;
DECLARE noMoreRow INT;
DECLARE cur_emp CURSOR FOR select id from user_comment where parent_id = comment_id ;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET noMoreRow = 0;
if(mode = 'delete')
then
OPEN cur_emp;
LOOPROWS: LOOP
IF noMoreRow = 0 THEN
update user_comment set is_deleted = 1, modified_datetime = now() where id = comment_id
CLOSE cur_emp;
LEAVE LOOPROWS;
END IF;
FETCH cur_emp INTO p_emp;
update user_comment set is_deleted = 1, modified_datetime = now() where id = p_emp ;
SET ##GLOBAL.max_sp_recursion_depth = 255;
SET ##session.max_sp_recursion_depth = 255;
call user_comments('delete', p_emp);
END LOOP;
end if;
END
After using cursor i am getting a thread error.i don't know how can overcome this problem!!!
Mysql's documentation on select ... into varlist clearly says:
The selected values are assigned to the variables. The number of
variables must match the number of columns. The query should return a
single row. If the query returns no rows, a warning with error code
1329 occurs (No data), and the variable values remain unchanged. If
the query returns multiple rows, error 1172 occurs (Result consisted
of more than one row). If it is possible that the statement may
retrieve multiple rows, you can use LIMIT 1 to limit the result set to
a single row.
Since you wrote in the OP that a comment can be parent of many comments, using simple variables cannot be a solution. You should use a CURSOR instead, that can store an entire resultset.
You loop through the records within the cursos as shown in the sample code in the above link and call user_comments() in a recursive way.
UPDATE
If your receive
Error Code: 1436. Thread stack overrun
error, then you can do 2 things:
Increase the thread_stack setting in the config file and restart mysql server.
You can try to simplify your code to use less recursions and therefore less stack space. For example, when you fetch all children into the cursor, then rather calling the user_comments() recursively for each, you can set all direct children's status within the code and call the function recirsively on grand-childrens only (if any). You can also change your data structure and use nested set model to approach hierarchical structures.
Nested set model is more complex to understand, it is less resource intensive to traverse, but more resource intensive to maintain.

mysql return results from update

I want to select a bunch of rows from a mysql database and update the viewed attribute of those once selected (this is a kind of 'I have read these' flag).
Initially I did something like this:
update (
select a, b, c
from mytable
where viewed = '0'
)
set viewed = '1';
This selects the rows nicely and updates their viewed attribute as required. But it does not return the selected rows from the subquery.
Is there a clause I can add, or perhaps I need to store the subquery, etc...? I did consider a transaction but I ended up with the same problem. I have not tried a stored procedure...
Please can someone advise / point me in the right direction on how to do what I do above but in addition return the selected tables from the subquery?
Thanks in advance.
Update:
As pointed out by #Barmar, #a_horse_with_no_name, #fancyPants and #George Garchagudashvil...
In MySQL you have to use two statements to select and update, and not a nested statement as in my initial post, if you want to return the selected rows.
e.g.
begin;
select a, b, c
from mytable
where viewed = '0';
update mytable
set viewed = '1'
where viewed = '0';
commit;
thanks guys.
I would create a simple function:
DELIMITER $$
DROP FUNCTION IF EXISTS `mydb`.`updateMytable`$$
CREATE
/*[DEFINER = { user | CURRENT_USER }]*/
FUNCTION `mydb`.`updateMytable`() RETURNS TEXT
BEGIN
SET #updated := '';
UPDATE mytable
SET viewed = 1
WHERE viewed = 0
AND (
SELECT #updated := CONCAT_WS(',', #updated, id)
) != ''
;
RETURN TRIM(LEADING ',' FROM #updated);
END$$
DELIMITER ;
which updates tables and returns concatenated ids.
From php you call this:
SELECT mydb.updateMytable()
and you get ids in a stirng: 1,2,7,54,132 etc...
Update:
my function is returning string containing comma separated ids:
'1,5,7,52,...' these ids are only which would have been updated during the function call,
better php-mysql example would be (you may and would use PDO):
$query = "SELECT mydb.updateMytable()";
$res = mysql_query($query);
$arr = mysql_fetch_array($res);
$ids = explode(',', $arr[0]);
// now you can do whatever you want to do with ids
foreach ($ids as $id)
{
echo "Hoorah: updated $id\n";
}
also remember to change mydb and mytable according to your database names
Final
because you need more complex functionality, simply run two query:
First run:
SELECT a, b, c
FROM mytable
WHERE viewed = 0
Next run:
UPDATE mytable
SET viewed = 1
WHERE viewed = 0