I am trying to convert the following trigger from PL/SQL to MySQL
Especially i wanna know how to do this:
1. FOR quantity in 1..:new.product_quantity
2. FOR row IN ()
.
create or replace trigger "TRG_INSERT_BILL_PRODUCTS"
after insert on Bill_Products
for each row
begin
FOR quantity in 1..:new.product_quantity
LOOP
FOR row IN (
SELECT pa.article_id,pa.consist_quantity
FROM product_articles pa
WHERE pa.product_id=:new.product_id)
LOOP
update store
set store_quantity=store_quantity-row.consist_quantity
where article_id=row.article_id;
END LOOP;
END LOOP;
END;
Explanation of the trigger:
Store table has store.article_id and store.store_quantity for that article
Product_articles table has pa.product_id, pa.article_id (article that is consistent in the product), pa.consist_quantity (of the article)
So after inserting an product in bill, i want to find all his consisting articles and lower the store.store_quantity for that store.article_id, that would be product_quantity (how many products of that were added in the bill) * consist_quantity (of that article in the product)
FOR quantity in 1..:new.product_quantity
MySql hasn't FOR loop.
You can emulate it using WHILE loop:
Set quantity = 1
WHILE quantity <= :new.product_quantity DO
.....
statement_list
.....
Set quantity = quantity + 1
END WHILE
FOR row IN ( query ) LOOP ...
MySql doesn't support this kind of a loop, you must declare a cursor and process it:
DECLARE cursor_name CURSOR FOR
SELECT pa.article_id,pa.consist_quantity
FROM product_articles pa
WHERE pa.product_id=:new.product_id;
also declare a continue handler for this cursor, then explicitely open the cursor, fetch rows from it in a loop and close it.
Please refer to documentation: http://dev.mysql.com/doc/refman/5.6/en/cursors.html
to find how to use MySql cursors with examples.
Related
I'm trying to write a trigger for a database but feel a bit lost. This is what I want the trigger to do: When there have been changes made to shipments (so when a new item has been shipped) I want to alter the value stock in another relation also called stock by subtracting 1, so that the two relationships correlate.
Any ideas? Thank you in advance!
The old and new items refer to to the shipment table. Since the stock fields are in the stock table, you need to explicitly update it. Similarly, you will want to fetch the count and assess its validity.
CREATE FUNCTION decstock() RETURNS trigger as $pname$
qty integer;
BEGIN
--Get the current count
SELECT stk.stock
INTO qty
FROM stock stk
WHERE stk.isbn=old.isbn;
--Validate count
IF (qty = 0) then
raise exception 'There is no stock to ship';
ELSE
--Update valid stock
UPDATE stock stk
SET stk.stock = stk.stock - 1
WHERE stk.isbn = old.isbn;
END IF;
RETURN NEW;
END;
$pname$ LANGUAGE plpgsql;
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"
So I have 2 tables, communication,and movement.
communication has columns fromID,timestamp that has ID of caller, and time the call was made. Then I have another table movement that has ID,timestamp,x,y, that has the ID of a person, their location (x,y), and the time that they are at that location.
I want to write a query that looks something like this:
For every single row of communication(R)
SELECT * FROM movement m
WHERE m.ID = R.fromID && m.timestamp <= R.timestamp
ORDER BY timestamp
Basically, what this is doing is finding the closest movement timestamp for a given communication timestamp. After that, eventually, I want to find the location (x,y) of a call, based on the movement data.
How would I do this? I know there's a set based approach, but I don't want to do it that way. I looked into cursors, but I get the feeling that the performance is terrible on that.
So is there anyway to do this with a loop? I essentially want to loop through every single row of the communication, and get the result.
I tried something like this:
DELMITER $$
CREATE PROCEDURE findClosestTimestamp()
BEGIN
DECLARE commRowCount DEFAULT 0;
DECLARE i DEFAULT 0;
DECLARE ctimestamp DEFAULT 0;
SELECT COUNT(*) FROM communication INTO commRowCount;
SET i = 0;
WHILE i < commRowCount DO
SELECT timestamp INTO ctimestamp FROM communication c
SELECT * FROM movement m
WHERE m.vID = c.fromID && m.timestamp <= R.timestamp
END$$
DELIMITER ;
But I know that's completely wrong.
Is the only way to do this cursors? I just can't find an example of this anywhere on the internet, and I'm completely new to procedures in SQL.
Any guidance would be greatly appreciated, thank you!!
Let's see if I can point you in the right direction using cursors:
delimiter $$
create procedure findClosestTimeStamp()
begin
-- Variables to hold values from the communications table
declare cFromId int;
declare cTimeStamp datetime;
-- Variables related to cursor:
-- 1. 'done' will be used to check if all the rows in the cursor
-- have been read
-- 2. 'curComm' will be the cursor: it will fetch each row
-- 3. The 'continue' handler will update the 'done' variable
declare done int default false;
declare curComm cursor for
select fromId, timestamp from communication; -- This is the query used by the cursor.
declare continue handler for not found -- This handler will be executed if no row is found in the cursor (for example, if all rows have been read).
set done = true;
-- Open the cursor: This will put the cursor on the first row of its
-- rowset.
open curComm;
-- Begin the loop (that 'loop_comm' is a label for the loop)
loop_comm: loop
-- When you fetch a row from the cursor, the data from the current
-- row is read into the variables, and the cursor advances to the
-- next row. If there's no next row, the 'continue handler for not found'
-- will set the 'done' variable to 'TRUE'
fetch curComm into cFromId, cTimeStamp;
-- Exit the loop if you're done
if done then
leave loop_comm;
end if;
-- Execute your desired query.
-- As an example, I'm putting a SELECT statement, but it may be
-- anything.
select *
from movement as m
where m.vID = cFromId and m.timeStamp <= cTimeStamp
order by timestampdiff(SECOND, cTimeStamp, m.timeStamp)
limit 1;
end loop;
-- Don't forget to close the cursor when you finish
close curComm;
end $$
delimiter ;
References:
MySQL Reference: Cursors
MySQL Reference: Date and time functions - timestampdiff()
How can I make trigger to read from two separated attribute in two different tables and then
calculate something .
this is the code I want to make but the mysql says it has problem
CREATE TRIGGER `fee_calculate` BEFORE INSERT ON `BookingRoom`
FOR EACH ROW BEGIN
SET NEW.fee = `Booking.Night_Qty` * `Room.RoomPrice` ;
END
I Think It Should Help You
I made some assumptions:
I assumed you wanted to update the reorders table with a productID that matches the Products table. (you can do an insert but may run into PK issues depending on how you have your table set up).
I assumed that the reorder amount will be placed there.
I did NOT include transaction statements, I would recommend adding these so it's logged.
CREATE trigger updateprod
on Products
for update as
begin
declare #qtyonhand int,
#reorder int
if update(QtyonHand)
begin
select #qtyonhand = QtyonHand,#reorder = ReorderAmount from inserted
if #qtyonhand < #reorder
begin
update Reorders set ReorderAmount = #reorder, ReorderDate = getdate()
end
else if #qtyonhand >= #reorder
begin
delete from reorders where ProductID = ProductID
end
end
end
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