I'm doing a stored procedure to update a table and that table has a boolean named: "Finished", as a field. This field informs us if a game is finished. In my problem it makes sense to be able to set something as finished before the expiration date so, because of that, I'm checking if the row to update has the "Finished" field as true or if the expiration date has passed.
SET #isFinished=0;
SELECT Finished INTO #isFinished FROM game WHERE ID = gameID;
-- gameID comes as a parameter
-- date comes as a parameter as well
IF DATEDIFF(STR_TO_DATE(date, "%Y-%m-%d") , CURDATE()) < 0 OR #isFinished<>0 THEN
select CONCAT("Game can't be updated because it's already finished. Days missing:",DATEDIFF( STR_TO_DATE(date, "%Y-%m-%d"), CURDATE() )," and #finished=", #isFinished, ", game=",gameID)
into #msg;
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = #msg;
END IF;
The problem is that when I try to update an unfinished game it is getting into the if and throwing the error message:
"sqlMessage: 'Game can\'t be updated because it\'s already finished. Days missing:337 and #finished=1, game=2'".
quick note: The variable #isFinished is never used but in this block of code.
I've always assured that the value of "Finished" was 0 before I tested it and yet it keeps selecting it as 1 and, because of that, getting into the if.
I thought it could be from the select...into so I tried it out of the stored procedure (literally copy paste, just changed the "gameID" to the actual ID that I'm using) and it worked perfectly.
SELECT Finished INTO #isFinished FROM game WHERE ID = 2;
SELECT #isFinished
After this, I don't know what more can I check. If anyone could help I'd be thankful.
Isolated test:
create database test;
use test;
create table Tournament(
ID int(10) not null unique auto_increment,
Name varchar(250) not null,
Start_Date date not null,
End_Date date not null,
Primary key(ID)
);
create table Game(
ID int(10) not null unique auto_increment,
Tournament_ID int(10),
Date date,
Finished boolean,
Foreign Key(Tournament_ID) references Tournament(ID) ON DELETE CASCADE,
Primary Key(ID)
);
INSERT INTO Tournament VALUES(NULL, "tournament1", str_to_date("2020-06-01","%Y-%m-%d"), str_to_date("2020-07-01","%Y-%m-%d"));
INSERT INTO Game VALUES(NULL, 1, str_to_date("2020-06-02","%Y-%m-%d"), 0);
DELIMITER $$
DROP PROCEDURE IF EXISTS `UpdateGame`$$
CREATE PROCEDURE `UpdateGame`(IN gameID int, date varchar(10), finished boolean)
BEGIN
SET #isFinished=0;
SELECT Finished INTO #isFinished FROM game WHERE ID = gameID;
IF DATEDIFF(STR_TO_DATE(date, "%Y-%m-%d") , CURDATE()) < 0 OR #isFinished<>0 THEN
select CONCAT("Game can't be updated because it's already finished. Days missing:",DATEDIFF( STR_TO_DATE(date, "%Y-%m-%d"), CURDATE() )," and #finished=", #isFinished, ", game=",gameID)
into #msg;
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = #msg;
END IF;
UPDATE game SET Date=STR_TO_DATE(date, "%Y-%m-%d"), Finished=finished WHERE ID=gameID;
END$$
call UpdateGame(1,"2020-06-03",1);
SELECT * FROM game;
SELECT Finished INTO #isFinished FROM game WHERE ID = 1;
SELECT #isFinished;
You have IN param Finished so inside your stored procedure, when querying the table, in fact you querying the IN param.
So you need to delete/rename the Finished IN param or write your internal select query as:
SELECT `game`.`Finished`
INTO #isFinished
FROM game
WHERE ID = gameID;
P.S. People usually have some kind of prefixes on input params to distinguish them visually inside the stored procedure from tables, columns of local variables.
Related
I am creating a procedure that updates any invoice with unpaid status for more than 30 days to 'OVERDUE'. However with my current code whenever I am calling this procedure even the invoice which does not have UNPAID status gets its status updated to OVERDUE.
CREATE procedure sync_invoice()
begin
declare dDate date;
declare stat varchar(20);
declare d_finished int default 0;
declare d_array cursor for
select DATEISSUED, STATUS from invoice;
declare continue handler for not found set d_finished = 1;
open d_array;
repeat
fetch d_array into dDate, stat;
if (datediff(current_date(), dDate)> 30 )then
update invoice
set STATUS = 'OVERDUE'
where stat = 'UNPAID';
end if;
until d_finished
end repeat;
close d_array;
-- code
end
//
here is the invoice table
CREATE TABLE IF NOT EXISTS `invoice` (
`INVOICENO` INT(11) NOT NULL AUTO_INCREMENT,
`CAMPAIGN_NO` INT(11) NOT NULL,
`DATEISSUED` DATE NULL DEFAULT NULL,
`DATEPAID` DATE NULL DEFAULT NULL,
`BALANCEOWING` INT(11) NULL DEFAULT NULL,
`STATUS` VARCHAR(20) NULL DEFAULT NULL,
PRIMARY KEY (`INVOICENO`, `CAMPAIGN_NO`),
INDEX `FK_INVOICE_SENDS2_CAMPAIGN_idx` (`CAMPAIGN_NO` ASC),
CONSTRAINT `FK_INVOICE_SENDS2_CAMPAIGN`
FOREIGN KEY (`CAMPAIGN_NO`)
REFERENCES `campaign` (`CAMPAIGN_NO`)
ON DELETE RESTRICT
ON UPDATE RESTRICT)
AUTO_INCREMENT = 6;
Ok so there are a few wrong things here:
CREATE procedure sync_invoice()
begin
declare dDate date;
declare stat varchar(20);
declare d_finished int default 0;
declare d_array cursor for
select DATEISSUED, STATUS from invoice;
declare continue handler for not found set d_finished = 1;
open d_array;
repeat
-- The loop will be executed for each entry into invoice
fetch d_array into dDate, stat;
-- For each, if it's old enough, regardless of the status...
if (datediff(current_date(), dDate)> 30 )then
-- This SQL request, which is called over the whole invoice table is executed
update invoice
set STATUS = 'OVERDUE' -- Effectively setting the status to overdue
where stat = 'UNPAID'; -- for each entry, so far that the one inspected is UNPAID
end if;
until d_finished
end repeat;
close d_array;
-- code
end
So basically, with this code, you'll always set every invoice to OVERDUE so long that there is a single, 30+ days old UNPAID invoice in the table.
Some way around this include replacing the whole content of the procedure by the single appropriate UPDATE call:
Update invoice Set STATUS = 'OVERDUE' Where datediff(current_date(), DATEISSUED) > 30 And STATUS = 'UNPAID';
Edit: To clarify, you could 'fix' the whole thing by replacing 'stat' at line where stat = 'UNPAID'; by 'STATUS', but you would still be executing way too many instructions without any reason to, effectively iterating through your whole invoice table as many time as you have 30+ days old invoices.
But in practice, SQL is a quite powerful language in which you might not need to loop yourself for tasks working with a single entry in a table. Statements such as UPDATE ... WHERE ... do it for you. c:
Loops and cursor, then, become useful when you have to do process that need to consider multiple entry at once.
Summary: I am trying to add a Username to the db.username_table with a unique ID incremented by 1. However, I keep receiving an error 1111 related to my CASE statement. It should make the first User_ID 1, then all other new ones the max(user_id) +1. Any help would be greatly appreciated!
Background: This is for my first MySQL project - I have some experience with MS SQL that may be hindering me here. I googled many references and streamlined my code as much as possible, but the aggregate for the counter returns a 1111 error with an IF or with a CASE statement.
DELIMITER $$
CREATE PROCEDURE db.add_user
(
in new_username varchar(45)
)
begin
-- Set Counter ID
declare new_user_id int;
set new_user_id = if(max(db.username_table.User_ID) is null, 1, max(db.username_table.User_ID) + 1);
-- Add Username with Counter
insert into db.username_table (user_id, username)
values (new_user_id, new_username);
END$$
DELIMITER ;
Expected Result - Add a Username to the db.username_table with a unique ID incremented by 1.
Actual Result - Error 1111.
You are using the funtion MAX but not in SQL statements .. so the max of is used improperly . you need a select for retrive a MAX(db.username_table.User_ID.)
declare new_user_id int;
select case when
max(ifnull(db.username_table.User_ID,0)) = 0
then 1
else max(db.username_table.User_ID) + 1 end
INTO new_user_id
from your_table ;
but could you simply need an autoincrement column for User_id and avoid this SP
CREATE TABLE your_table (
User_ID int(11) NOT NULL AUTO_INCREMENT,
....
)
I'm running into an error in my stored procedure, and after numerous YT videos and forums, I still have no clue where I'm going wrong. Given what I'm trying to do, it all seems to look correct.
Here's the deal. I take in some information to buy some stock, I use an IF to make sure that I have enough money to make the purchase, I then insert the purchase information into my TRADES table and update the cash balance in ACCOUNTS to reflect the spending of $$.
I can't even test to see if it works correctly because it won't run. The only error I'm getting is at INSERT INTO, in which it says error: INTO (into) is not valid input at this position
I have done ALL of my insert statements the exact same way, and have no idea why this particular syntax is incorrect? Any help would be greatly appreciated! Below are two approaches, both with errors.
CREATE PROCEDURE `BUY` (TID INT,ID INT, CASH INT, T_NAME VARCHAR(4) ,
TCOUNT INT, TBUYDATE DATE, TBUYPRICE INT )
BEGIN
IF (ACCOUNT.CASH_BALANCE >= (TCOUNT * TBUYPRICE),
INSERT INTO TRADES (TRADE_ID, ACCOUNT_ID, TRADE_NAME, TRADE_COUNT, TRADE_BUYDATE, TRADE_BUYPRICE)
VALUES (TID, ID, T_NAME, TCOUNT, TBUYDATE, TBUYPRICE)
AND UPDATE ACCOUNT.CASH_BALANCE
WHERE ACCOUNT.ACCOUNT_ID = ID
SET ACCOUNT.CASH_BALANCE = (ACCOUNT.CASH_BALANCE - (TCOUNT * TBUYPRICE)),
NULL
)
END
I have also tried the following, however I get an error on END missing subclause or other elements before end
CREATE PROCEDURE `BUY` (TID INT,ID INT, CASH INT, T_NAME VARCHAR(4) , TCOUNT
INT, TBUYDATE DATE, TBUYPRICE INT )
BEGIN
IF (ACCOUNT.CASH_BALANCE >= (TCOUNT * TBUYPRICE))
THEN
INSERT INTO TRADES (TRADE_ID, ACCOUNT_ID, TRADE_NAME, TRADE_COUNT,
TRADE_BUYDATE, TRADE_BUYPRICE)
VALUES (TID, ID, T_NAME, TCOUNT, TBUYDATE, TBUYPRICE);
UPDATE ACCOUNT.CASH_BALANCE
SET ACCOUNT.CASH_BALANCE = (ACCOUNT.CASH_BALANCE - (TCOUNT * TBUYPRICE))
WHERE ACCOUNT.ACCOUNT_ID = ID;
ELSE #noinsert
END
There are multiple errors/corrections:
The Delimiter command was not used, so he gets confused on the end of statement and the end of the procedure definition
The account table needs to be selected in an exists statement
I've used a local variable l_cash instead of repeating TCOUNT * TBUYPRICE (Not an error).
The ELSE statement was not necessary and an END IF; was missing.
Update statement corrected.
Here is the corrected code:
DELIMITER $$
CREATE PROCEDURE `BUY` (TID INT,ID INT, CASH INT, T_NAME VARCHAR(4) , TCOUNT
INT, TBUYDATE DATE, TBUYPRICE INT)
BEGIN
DECLARE l_cash INT DEFAULT 0;
SET l_cash = TCOUNT * TBUYPRICE;
IF EXISTS(SELECT 1 FROM Account WHERE ACCOUNT_ID = ID AND CASH_BALANCE >= l_cash) THEN
INSERT INTO TRADES (TRADE_ID, ACCOUNT_ID, TRADE_NAME, TRADE_COUNT,
TRADE_BUYDATE, TRADE_BUYPRICE)
VALUES (TID, ID, T_NAME, TCOUNT, TBUYDATE, TBUYPRICE);
UPDATE ACCOUNT
SET CASH_BALANCE = (CASH_BALANCE - l_cash)
WHERE ACCOUNT_ID = ID;
END IF;
END$$
DELIMITER ;
I'm completely new to MySQL, and have been bumping with some errors, but always I do find solutions, except for this one I can't understand how to get around it.
The following MySQL Procedure returns me a value if variable "ue" is 1 or 0 (a bunch of exists validation). The validation part (SET ue = EXISTS...) works without the rest of the code, as it should, the problem is not there. But when I do execute the command INSERT INTO SELECT, it does not work, it always return 0 as response, when it should be 1. These two lines are getting in confrontation with each other.
INSERT INTO meetup_participation SELECT user_id, event_id FROM DUAL WHERE ue=1;
SELECT ue AS response;
The procedure should add 'user id' and 'event id' into meetup_participation, and then update the row at 'users' corresponding to the user with that 'user id' to increment the 'events participated'. And it also UPDATE to increment the participation in the event with this 'event id'.
I am using the SET ue to validate things like, if user exists, if event does exists, if date of event is still valid, and if user is not already in this table. So I am passing this value as a boolean to INSERT INTO meetup_participation [...] WHERE ue = 1. After that, I do SELECT ue to inform validation returned true and procedure executed without problems.
Here is the full procedure.
CREATE DEFINER=`user`#`localhost` PROCEDURE `join_event`(IN `user_id` BIGINT(64), IN `event_id` INT) NOT DETERMINISTIC MODIFIES SQL DATA SQL SECURITY DEFINER
begin
DECLARE ue INT;
SET ue = EXISTS(SELECT 1 FROM users WHERE fb_uid=user_id) AND EXISTS(SELECT 1 FROM meetup WHERE meet_id=event_id) AND EXISTS(SELECT 1 FROM meetup WHERE date > NOW() AND meet_id = event_id) AND EXISTS(SELECT 1 FROM meetup WHERE meet_id = event_id AND participants <= max_participants) AND NOT EXISTS(SELECT 1 FROM meetup_participation WHERE fb_uid = user_id AND meet_id = event_id);
INSERT INTO meetup_participation SELECT user_id, event_id FROM DUAL WHERE ue=1;
UPDATE users SET events_participated = events_participated + 1 WHERE fb_uid=user_id AND ue=1;
UPDATE meetup SET participants = participants + 1 WHERE meet_id=event_id AND ue=1;
SELECT ue AS response;
end
Thanks in advance.
The INSERT statement is executed separately from the SET ue =... statement. I'm not sure what you are trying to accomplish, but the code makes no sense.
If you want to add records to meetup_participation based on the EXISTS tests applied to each record in the users table, you would need to apply the tests to each record in your SELECT statement as part of the INSERT.
There are also numerous syntax/grammar issues in the code as shown.
If you could provide an explanation of what you are trying to accomplish with the procedure, that might allow someone to suggest the right way to code the procedure.
Selecting ue will not tell you if the procedure completed without error. You should research mysql transactions and mysql error handling. http://www.mysqltutorial.org/mysql-error-handling-in-stored-procedures/ is a good starting point.
You might end up with something like this
drop procedure if exists p;
delimiter //
CREATE DEFINER=`root`#`localhost` PROCEDURE `p`(
IN `inue` int,
IN `user_id` BIGINT(64),
IN `event_id` INT
)
LANGUAGE SQL
NOT DETERMINISTIC
MODIFIES SQL DATA
SQL SECURITY DEFINER
COMMENT ''
begin
DECLARE ue INT;
declare exit handler for sqlexception
begin
rollback;
insert into errors (msg) select concat('error ' ,inue,',',user_id,',',event_id);
end;
set autocommit = 0;
#set ue = inue;
SET ue = EXISTS(SELECT 1 FROM users WHERE fb_uid=user_id)
AND EXISTS(SELECT 1 FROM meetup WHERE meet_id=event_id)
#AND EXISTS(SELECT 1 FROM meetup WHERE dt > NOW() AND meet_id = event_id)
AND EXISTS(SELECT 1 FROM meetup WHERE meet_id = event_id AND ifnull(participants,0) <= max_participants)
AND NOT EXISTS(SELECT 1 FROM meetup_participation WHERE fb_uid = user_id AND meet_id = event_id)
;
select ue;
if ue = 1 then
start transaction;
INSERT INTO meetup_participation SELECT user_id, event_id,user_id, event_id;
UPDATE users SET events_participated = ifnull(events_participated,0) + 1 WHERE fb_uid=user_id = user_id;
UPDATE meetup SET participants = ifnull(participants,0) + 1 WHERE meet_id = event_id ;
commit;
end if;
SELECT ue AS response;
end //
The error table looks like this
CREATE TABLE `errors` (
`msg` varchar(2000) DEFAULT NULL,
`id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1
Note I am not suggesting this is a solution appropriate to your site , you need to do the research and figure out what is best for you.
I need to check first if the EndTime column in my table is null or not before I can insert another record. If the Endtime column is not null than a new record can be inserted else an error must be thrown. I'm not sure how to create the error in SQL.
This is what I tried but it doesn't work
ALTER PROCEDURE [dbo].[AddDowntimeEventStartByDepartmentID]
(#DepartmentId int,
#CategoryId int,
#StartTime datetime,
#Comment varchar(100) = NULL)
AS
BEGIN TRY
PRINT N'Starting execution'
SET #StartTime = COALESCE(#StartTime, CURRENT_TIMESTAMP);
INSERT INTO DowntimeEvent(DepartmentId, CategoryId, StartTime, EndTime, Comment)
WHERE EndTime = NULL
OUTPUT
inserted.EventId, inserted.DepartmentId,
inserted.CategoryId, inserted.StartTime,
inserted.EndTime, inserted.Comment
VALUES(#DepartmentId, #CategoryId, #StartTime, NULL, #Comment)
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER(),ERROR_MESSAGE()
END CATCH
Here is my table:
CREATE TABLE [dbo].[DowntimeEvent](
[EventId] [int] IDENTITY(0,1) NOT NULL,
[DepartmentId] [int] NOT NULL,
[CategoryId] [int] NOT NULL,
[StartTime] [datetime] NOT NULL,
[EndTime] [datetime] NULL,
[Comment] [varchar](100) NULL,
)
You could use the INSERT...SELECT syntax instead of INSERT...VALUES to be able to use a WHERE clause (with a different condition to the one you tried to use, see below), then check the number of affected rows and raise an error if it is 0:
...
BEGIN TRY
...
INSERT INTO DowntimeEvent
...
SELECT #DepartmentId, #CategoryId, #StartTime, NULL, #Comment
WHERE NOT EXISTS (
SELECT *
FROM dbo.DowntimeEvent
WHERE DepartmentId = #DepartmentId
AND CategoryId = #CategoryId
AND EndTime IS NULL
);
IF ##ROWCOUNT = 0
RAISERROR ('A NULL row already exists!', 16, 1)
;
END TRY
BEGIN CATCH
...
END CATCH;
(Of course, you will need to omit your WHERE clause as invalid Transact-SQL.)
If you want a prevention mechanism at the database level rather than just in your stored procedure, so as to be able to prevent invalid additions from any caller, you may want to consider a trigger.
A FOR INSERT trigger like this would check if new rows violate the rule "Do not add rows newer than the existing NULL row" (as well as "Do not add older rows with empty EndTime") and roll back the transaction if they do:
CREATE TRIGGER DowntimeEvent_CheckNew
ON dbo.DowntimeEvent
FOR INSERT, UPDATE
-- do nothing if EndTime is not affected
IF NOT UPDATE(EndTime)
RETURN
;
-- raise an error if there is an inserted NULL row
-- older than another existing or inserted row
IF EXISTS (
SELECT *
FROM dbo.DowntimeEvent AS t
WHERE t.EndTime IS NULL
AND EXISTS (
SELECT *
FROM inserted AS i
WHERE i.DepartmentId = t.DepartmentId
AND i.CategoryId = t.CategoryId
AND i.StartTime >= t.StartTime
)
)
BEGIN
RAISERROR ("An attempt to insert an older NULL row!", 16, 1);
ROLLBACK TRANSACTION;
END;
-- raise an error if there is an inserted row newer
-- than the existing NULL row or an inserted NULL row
IF EXISTS (
SELECT *
FROM inserted AS i
WHERE i.EndTime IS NULL
AND EXISTS (
SELECT *
FROM dbo.DowntimeEvent AS t
WHERE t.DepartmentId = i.DepartmentId
AND t.CategoryId = i.CategoryId
AND t.StartTime >= i.StartTime
)
)
BEGIN
RAISERROR ("An older NULL row exists!", 16, 1);
ROLLBACK TRANSACTION;
END;
Note that merely issuing ROLLBACK TRANSACTION in a trigger already implies raising a level 16 error like this:
Msg 3609, Level 16, State 1, Line nnn
The transaction ended in the trigger. The batch has been aborted.
so, you may not need your own. There would be a difference in the meaning of Line nnn between the message above and the one brought by your own RAISERROR, however: the line number in the former would refer to the location of the triggering statement, whereas the line number in the latter would refer to a position in your trigger.