I have been spinning my wheels trying to figure out what I have wrong. I'm new to MySQL so it could be something simple. I am creating a stored procedure to increase a user's in-game currency. I try an DECLARE a variable for the procedure and workbench gives me an error saying "not valid in this position, expecting END". Everything that I've looked up online says to do it this way. If I move where the DECLARE is to above the SET TRANSACTION I can get it so there are no errors but the procedure doesn't change the value in currency. I believe this is because the variable isn't declared and so it doesn't have anywhere to store the starting balance ergo can't add the amount to the balance. I did see some articles that mentioned not putting in the semi-colon but I tried changing that but that generates different errors. Any help would be much appreciated.
CREATE DEFINER=`root`#`localhost` PROCEDURE `addCurrencyBalance`(userID INT, amount INT)
BEGIN
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
DECLARE balance INT;
SET balance = (SELECT currency
FROM users
WHERE user_id=#userID);
SET #amount = balance + #amount;
UPDATE users
SET
currency = #amount
WHERE
user_id = #userID;
SELECT currency FROM users WHERE user_id=userID;
COMMIT;
END
You are mixing user variables and local variables, which makes the code messy. For example userID is an argument to the function, but then you refer to it using #userID. Also, the variable names clash with actual column names of the table.
Bottom line, I don't think you need that complicated logic. It seems like a simple update statement does what you want. So your code becomes a single-statement procedure, like so:
create procedure addcurrencybalance (p_user_id int, p_amount int)
update users
set currency = currency + p_amount
where user_id = p_user_id
;
Do not use the same names for parameters, declared variables and column, and do not confuse users defined (at) variables with local (declared variables). Your code corrected is
delimiter $$
CREATE PROCEDURE p1(p_userID INT, p_amount INT)
BEGIN
DECLARE balance INT;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET balance = (SELECT currency FROM t WHERE user_id=P_userID);
SET p_amount = balance + p_amount;
UPDATE t
SET currency = p_amount
WHERE user_id = p_userID;
#SELECT currency FROM t WHERE user_id=p_userID;
END $$
#GMB answer is better..
Related
I need to do calculations based on Formula stored in Table as VARCHAR, for example, "100*BasicVal" and pass some value like 100 to string part of variable "BasicVal" as 1000.
I have tried to work on, and clearly, it's not the way. Please suggest a way for implementation. Trim won't work in my case as sometimes the formula may go as long as 4-5 Variable parts and operations. The code that I tried on my test table is following. It will help you to understand what I am trying to get.
CREATE DEFINER=`root`#`localhost` PROCEDURE `tester`()
begin
DECLARE VALUE0 varchar(50);
DECLARE VALUE1 INT;
DECLARE BasicVal INT;
select #valueC1:=C1 from tester where id = '1';
SET BasicVal = 1000;
SET VALUE0 = #valueC1;
SET VALUE1 = CAST(VALUE0 AS SIGNED );
INSERT IGNORE INTO TESTER (C1) VALUES(VALUE1);
end
Please note that Row returned from "select #valueC1:=C1 from tester where id = '1';" is 100*BasicVal
I clearly understand the code above makes no practical sense as I am trying to convert String to INT, see this code just to understand my requirement.
CREATE PROCEDURE Sp_IU_Group(
GID int,
GroupName nvarchar(200),
UserID int,
Status int
)
BEGIN
IF GID=0 THEN
Insert into tblGroup (GroupName,UserID,Status)
values (GroupName,UserID,Status);
else
update tblGroup set GroupName=GroupName,UserID=UserID,Status=Status WHERE GID=GID;
END IF;
END
This query:
update tblGroup set GroupName=GroupName,UserID=UserID,Status=Status WHERE GID=GID
Will update every record in the table... to itself. This matches every record, because this is always true:
WHERE GID=GID
And this updates a value to itself:
GroupName=GroupName
The problem is that you're using the same names for multiple things. Give things different names. Something as simple as this:
CREATE PROCEDURE Sp_IU_Group(
GIDNew int,
GroupNameNew nvarchar(200),
UserIDNew int,
StatusNew int
)
(Or use any other standard you want to distinguish the variables from the database objects, such as prepending them with a special character like an #.)
Then the query can tell the difference:
update tblGroup set GroupName=GroupNameNew,UserID=UserIDNew,Status=StatusNew WHERE GID=GIDNew
(Modify the rest of the stored procedure for the new variable names accordingly, of course.)
Basically, as a general rule of thumb, never rely on the code to "know what you meant". Always be explicit and unambiguous.
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've got a stored procedure in MySQL that gets the next unique ID from a table, to use as an ID for 2 other tables (not the best way to do it, I'm sure, but I'm modifying someone else's code here). The procedure is as follows:
DELIMITER $$
CREATE DEFINER=`root`#`%` PROCEDURE `GetNextID`( OUT id bigint )
BEGIN
DECLARE uid VARCHAR(255);
SET uid = uuid();
INSERT INTO `ident_column_generator` (u) VALUES (uid);
SELECT ID INTO id FROM `ident_column_generator` WHERE u = uid;
DELETE FROM `ident_column_generator` WHERE u = uid;
END$$
When I call the procedure from MySQL Workbench:
CALL GetNextID( #id );
SELECT #id;
#id is NULL. I can't work out what's going wrong? Even if I run SET #id = 0; before calling the procedure, it ends up as NULL afterwards. If I call the functions within the procedure manually from MySQL Workbench, #id outputs fine, e.g.:
SET #uid = uuid();
INSERT INTO `ident_column_generator` (u) VALUES (#uid);
SELECT ID INTO #id FROM `ident_column_generator` WHERE u = #uid;
DELETE FROM `ident_column_generator` WHERE u = #uid;
SELECT #id;
This outputs #id as being a valid number.
Any ideas why id isn't being set properly?
Typically, spent 3 hours on this, then JUST after I posted the question I find the problem. So, for future reference: It appears MySQL is case insensitive where variables are concerned. The ID column name and id variable apparently completely confused it.
I changed the procedure's input parameter name to retId and then it worked perfectly.
Thanks Nick, I also had same issue. The column name and variable name were same due to which we were getting the issue.
In MySql
UPDATE `inventoryentry` SET `Status` = 1 WHERE `InventoryID`=92 AND `ItemID`=28;
It successfully update only one row , where inventoryID = 92 and itemID=28 , the following message displayed.
1 row(s) affected
when I put this on stored procedure, as follow
CREATE DEFINER=`root`#`localhost` PROCEDURE `Sample`(IN itemId INT, IN itemQnty
DOUBLE, IN invID INT)
BEGIN
DECLARE crntQnty DOUBLE;
DECLARE nwQnty DOUBLE;
SET crntQnty=(SELECT `QuantityOnHand` FROM `item` WHERE id=itemId);
SET nwQnty=itemQnty+crntQnty;
UPDATE `item` SET `QuantityOnHand`=nwQnty WHERE `Id`=itemId;
UPDATE `inventoryentry` SET `Status` = 1 WHERE `InventoryID`=invID AND
`ItemID`=itemId;
END$$
calling stored procedures
CALL Sample(28,10,92)
It update all the status = 1 in inventoryentry against InventoryID (i.e. 92) ignoring ItemID, instead of updating only one row. The following message displayed!
5 row(s) affected
Why Stored procedure ignoring itemID in update statement ? or Why Stored procedure updating more than one time? But without Stored procedure it working fine.
You need to use different variable names apart from your field name, also use the table name with the columns for better understanding like i used in following:
CREATE DEFINER=`root`#`localhost` PROCEDURE `Sample`(IN itemID INT, IN itemQnty
DOUBLE, IN invID INT)
BEGIN
DECLARE crntQnty DOUBLE;
DECLARE nwQnty DOUBLE;
SET crntQnty=(SELECT `QuantityOnHand` FROM `item` WHERE id=itemID);
SET nwQnty=itemQnty+crntQnty;
UPDATE `item` SET `QuantityOnHand`=nwQnty WHERE `QuantityOnHand`.`Id`=itemID;
UPDATE `inventoryentry` SET `Status` = 1 WHERE `InventoryID`=invID AND
`inventoryentry`.`ItemID`=itemID;
END$$
because of
update inventoryentry ... WHERE ... AND `ItemID`=itemId
You are saying that column itemid should be the same as column itemid which is always true
Try renaming your parameter to a name that differs from your column name
Using same names for columns and variable names has some issues.
Semantics of Stored procedure code is not checked at CREATE time. At runtime, undeclared variables are detected, and an error message is generated for each reference to an undeclared variable. However, SP's seem to believe any reference denotes a column, even though the syntactic context excludes that. This leads to a very confusing error message in case the procedure.
Your column name ItemID matches with input variable name itemId, and hence is the issue.
Please look at my answer to a similar query here.