Is it possible to return more than one rows and column in a single query inside a stored procedure or a trigger? and how can i fetch the data being return? do i need to use loop or some thing?
here is what i want:
DROP TRIGGER IF EXISTS `trgg`;
DELIMITER ;;
CREATE TRIGGER `trgg` AFTER INSERT ON tbl
FOR EACH ROW BEGIN
SET #result = (SELECT * FROM tbl2 WHERE field = 1 );
// i want to fetch the values return #result, is that possible?
// Or at least only the column only, not necessarily all the rows,
#### rest of the codes goes here #####
END ;;
i been researching this for about a day, but still i cant find the answer,
is anybody here can help me on this
You can use cursor inside trigger. the good example of cursor is http://dev.mysql.com/doc/refman/5.0/en/cursors.html
Declare cursor for your select statement. And in the loop fetch values of that cursor in some declared variable
Related
DROP PROCEDURE IF EXISTS kund2orderNew;
DELIMITER ;;
CREATE PROCEDURE kund2orderNew(kundId2 INT)
BEGIN
IF kundId2 <> (SELECT kundId FROM kund2order) THEN
INSERT INTO kundOrder VALUES ();
INSERT INTO kund2order VALUES (kundId2, (SELECT id FROM kundOrder));
END IF;
END
;;
DELIMITER ;
Alright am I doing something wrong here? What im trying to do is to check if kundId is in the kund2order, if its not then what I want to do is create a new row in the kundOrder table that just uses the default values and then take the recently created id from that row in the kundOrder and put it inside the new row in kund2order (together with kundId).
For some reason it just gives me (node:18328) UnhandledPromiseRejectionWarning: Error: ER_BAD_NULL_ERROR: Column 'kundId' cannot be null
I am a bit confused as to what the problem is, both tables are empty after I have called this procedure. Is the problem my if statement or is it something else?
That's not the correct way to check if an ID is already in the table. When you use a SELECT query as an expression, it has to return just one row. You can use:
IF NOT EXISTS (SELECT * FROM kund2Order WHERE kundId = kundId2) THEN
And if you want to insert the auto-increment of the row that was just inserted into kundOrder, you should use LAST_INSERT_ID():
INSERT INTO kund2order VALUES (kundId2, LAST_INSERT_ID());
I'm trying to use a call a stored procedure with a set of values that i'm receiving from another query and i would like to know on how i can i call another procedure using the value from a query . here is my code
CREATE DEFINER=`root`#`localhost` PROCEDURE `temp`(IN u_id int)
BEGIN
#below query will give me all the u_id values that i need to use(ex : 2,8,9)
Declare cur cursor for select r_id from temp.usr_rl where u_id in (u_id);
#below i would like to use the u_id values and run the below procedure in a loop for each value in u_id
open cur;
repeat
fetch cur into a;
if not done then
call get_r(a);
end if;
until done end repeat;
close cur;
END
That would cause you huge performance hit since you are using cursor to process every record and calling another procedure. Thus you are actually doubling the effect. Instead, fetch and store all the values in a Temporary Table using CTAS like below and access that temporary table from within your procedure call get_r for whatever further processing you are doing.
create temporary table myTemp
as select r_id from temp.usr_rl where u_id = u_id;
I am trying to write a stored procedure in MySQL which will perform a somewhat simple select query, and then loop over the results in order to decide whether to perform additional queries, data transformations, or discard the data altogether. Effectively, I want to implement this:
$result = mysql_query("SELECT something FROM somewhere WHERE some stuff");
while ($row = mysql_fetch_assoc($result)) {
// check values of certain fields, decide to perform more queries, or not
// tack it all into the returning result set
}
Only, I want it only in MySQL, so it can be called as a procedure. I know that for triggers, there is the FOR EACH ROW ... syntax, but I can't find mention of anything like this for use outside of the CREATE TRIGGER ... syntax. I have read through some of the looping mechanisms in MySQL, but so far all I can imagine is that I would be implementing something like this:
SET #S = 1;
LOOP
SELECT * FROM somewhere WHERE some_conditions LIMIT #S, 1
-- IF NO RESULTS THEN
LEAVE
-- DO SOMETHING
SET #S = #S + 1;
END LOOP
Although even this is somewhat hazy in my mind.
For reference, though I don't think it's necessarily relevant, the initial query will be joining four tables together to form a model of hierarchal permissions, and then based on how high up the chain a specific permission is, it will retrieve additional information about the children to which that permission should be inherited.
Something like this should do the trick (However, read after the snippet for more info)
CREATE PROCEDURE GetFilteredData()
BEGIN
DECLARE bDone INT;
DECLARE var1 CHAR(16); -- or approriate type
DECLARE var2 INT;
DECLARE var3 VARCHAR(50);
DECLARE curs CURSOR FOR SELECT something FROM somewhere WHERE some stuff;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET bDone = 1;
DROP TEMPORARY TABLE IF EXISTS tblResults;
CREATE TEMPORARY TABLE IF NOT EXISTS tblResults (
--Fld1 type,
--Fld2 type,
--...
);
OPEN curs;
SET bDone = 0;
REPEAT
FETCH curs INTO var1, var2, var3;
IF whatever_filtering_desired
-- here for whatever_transformation_may_be_desired
INSERT INTO tblResults VALUES (var1, var2, var3);
END IF;
UNTIL bDone END REPEAT;
CLOSE curs;
SELECT * FROM tblResults;
END
A few things to consider...
Concerning the snippet above:
may want to pass part of the query to the Stored Procedure, maybe particularly the search criteria, to make it more generic.
If this method is to be called by multiple sessions etc. may want to pass a Session ID of sort to create a unique temporary table name (actually unnecessary concern since different sessions do not share the same temporary file namespace; see comment by Gruber, below)
A few parts such as the variable declarations, the SELECT query etc. need to be properly specified
More generally: trying to avoid needing a cursor.
I purposely named the cursor variable curs[e], because cursors are a mixed blessing. They can help us implement complicated business rules that may be difficult to express in the declarative form of SQL, but it then brings us to use the procedural (imperative) form of SQL, which is a general feature of SQL which is neither very friendly/expressive, programming-wise, and often less efficient performance-wise.
Maybe you can look into expressing the transformation and filtering desired in the context of a "plain" (declarative) SQL query.
Use cursors.
A cursor can be thought of like a buffered reader, when reading through a document. If you think of each row as a line in a document, then you would read the next line, perform your operations, and then advance the cursor.
Using a cursor within a stored procedure.
Prepare the SQL Query
SELECT id FROM employee where department_id = 1;
Create the cursor which will hold the result set returned by the SQL Query.
DECLARE BonusDistributionCursor CURSOR FOR SELECT id FROM employee where department_id = 1;
To have a safe exit when fetching a row from cursor does not return any result then declare a handler called NOT FOUND and set value to a declared variable
DECLARE CONTINUE HANDLER FOR NOT FOUND SET finished = 1;
Open the Cursor before you can fetch the next row from the cursor.
OPEN BonusDistributionCursor;
Fetch the next row pointed by the cursor and move the cursor to next row after that.
FETCH BonusDistributionCursor INTO employeeId;
Run the desired business logic according to the usecase required.
DELIMITER $$
CREATE PROCEDURE distributeYearlyBonus (IN departmentId VARCHAR(2))
BEGIN
DECLARE finished INTEGER DEFAULT 0;
DECLARE empId VARCHAR(TEXT) DEFAULT "";
DECLARE BonusDistributionCursor CURSOR FOR SELECT id FROM employee where department_id = departmentId;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET finished = 1;
OPEN BonusDistributionCursor;
addBonus: LOOP
FETCH BonusDistributionCursor INTO empId;
IF finished = 1 THEN
LEAVE addBonus;
END IF;
INSERT INTO `bonus_paid_details` (`employee_id`, `year`, `datetime`) VALUES (empId, YEAR(CURDATE());, now());
END LOOP addBonus;
CLOSE BonusDistributionCursor;
END$$
DELIMITER ;
Execute the above script and you will find a new Stored Procedure created.
Call or Invoke the Stored Procedure by inputing the departmentId which will receive the bonus amount.
CALL BonusDistributionCursor(1);
Hope this explains "How to iterate using Cursors used within Stored Procedure"
I create a Mysql procedure using cursor, but it's run too slow... It's get between 40 and 60 lines by second.. See:
DELIMITER $$
CREATE PROCEDURE sp_create(IN v_idsorteio INT,OUT afetados INT)
BEGIN
DECLARE done INT default 0;
DECLARE vc_idsocio INT;
DECLARE z INT;
DECLARE cur1 CURSOR FOR select IdSocio from socios where Sorteio=1 and Finalizado='S' and CodClientes IS NOT NULL;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done=1;
SET z=1;
OPEN cur1;
FETCH cur1 INTO vc_idsocio;
WHILE done=0 DO
-- SELECT register as t;
insert INTO socios_numeros_sorteio (IdSocio,IdSorteio,NumerodeSorteio) VALUES (vc_idsocio,v_idsorteio,z);
FETCH cur1 INTO vc_idsocio;
SET z = z+1;
END WHILE;
CLOSE cur1;
Select z-1 as total INTO afetados;
END$$
DELIMITER ;
how can I to improve that?
This is slow because you are looping through a resultset, row by row, and performing individual insert statements for each row returned. That's why it's gonna be slow.
Let's briefly summarize what you are doing. First, you are running a query:
select IdSocio
from socios
where Sorteio=1
and Finalizado='S'
and CodClientes IS NOT NULL;
(Apparently the order these rows are returned in is not important.)
Then for each row returned from that query, you want to insert a row into another table.
insert INTO socios_numeros_sorteio
(IdSocio
,IdSorteio
,NumerodeSorteio
) VALUES
(vc_idsocio
,v_idsorteio
,z);
The value for the first column is coming from a value returned by the query.
The value for the second column is being assigned a value passed as an argument to the procedure.
And the value for the third column is from a counter that starts at 1 and is being incremented by 1 for each row.
MySQL is optimized to perform an operation like this. But it's NOT optimized to do this using a stored procedure that loops through a cursor row by row.
If you are looking to get some reasonable performance, you need to SIGNIFICANTLY REDUCE the number of individual INSERT statements you run, and instead think in terms of processing data in "sets" rather than individual rows. One approach is batch the rows up into "extended insert" statements, which can insert multiple rows at a time. (The number rows you can insert in one statement is effectively limited by max_allowed_packet.)
That approach will significantly improve performance, but it doesn't avoid the overhead of the cursor, fetching each row into procedure variables.
Something like this (in the body of your procedure) is likely to perform much, much better, because it takes the result set from your select and inserts all of the rows into the destination table in one fell swoop, without bothering to mess with updating the values of variables in the procedure.
BEGIN
SET #idsorteio = v_idsorteio;
INSERT INTO socios_numeros_sorteio
( IdSocio
, IdSorteio
, NumerodeSorteio
)
SELECT s.IdSocio AS IdSocio
, #idsorteio AS IdSorteio
, #z := #z+1 AS NumerodeSorteio
FROM socios s
JOIN (SELECT #z := 0) z
WHERE s.Sorteio=1
AND s.Finalizado='S'
AND s.CodClientes IS NOT NULL;
SELECT ROW_NUMBER() INTO afetados;
END$$
Another simple solution is only to change the engine of the table to MyISAM by running the below query,
ALTER TABLE `socios_numeros_sorteio`
ENGINE=MyISAM;
Then CALL the procedure again.
note: MyISAM make the insertion process very fast
So this should be a fairly straight forward trigger, but my MySQL isn't great, so it's undoubtably a failure on my part.
It's not updating the stats table at all, even though it should be;
DROP TRIGGER countryUpdate;
DELIMITER //
CREATE TRIGGER countryUpdate AFTER INSERT ON stats
FOR EACH ROW BEGIN
DECLARE NewIP varchar(16);
DECLARE NewCountry varchar(80);
SET NewIP = inet_aton(new.vis_ip);
SET NewCountry = (SELECT country FROM iptocountry WHERE lower_bound <= NewIP AND upper_bound >= NewIP)
UPDATE stats
SET Country = NewCountry
END //
DELIMITER;
Well, first off, your UPDATE—if it works at all—is changing all rows in the stats table, and its doing that for each row inserted. That really doesn't make much sense. At minimum, you want to add a where clause to only hit the one row you've just inserted.
Apparently, though, that can't work at all in MySQL, because "a stored function or trigger cannot modify a table that is already being used (for reading or writing) by the statement that invoked the function or trigger." (Look under “Restrictions for Stored Functions”)
So, instead, you need to use a a before insert trigger, and do a SET new.country = NewCountry to fix the row up before its ever inserted.