Updating a table in my trigger fails - mysql

I am trying to update a table using an after insert trigger using this code
SET #OLDTMP_SQL_MODE=##SQL_MODE, SQL_MODE='STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';
DELIMITER //
CREATE TRIGGER `le_log_trigger` AFTER INSERT ON `messagelog` FOR EACH ROW BEGIN
declare last_inserted_number VARCHAR(100) DEFAULT '0800100200';
declare last_inserted_code VARCHAR(100) DEFAULT 'Lorem Ipsum';
declare current_dataset VARCHAR(100) DEFAULT 'Lorem Ipsum';
set last_inserted_number = NEW.messagefrom;
set last_inserted_code = NEW.statuscode;
set current_dataset = (select task_name from running_tasks limit 1);
if(last_inserted_code = 201) then
update current_dataset set the_status = 'online' where device_number = last_inserted_number;
end if;
END//
DELIMITER ;
My database is called logan and i get this error when the trigger runs
/* SQL Error (1146): Table 'logan.current_dataset' doesn't exist */
Why is current_dataset not being treated as a variable?.

Because you're trying to do an UPDATE query. MySQL expects to see a table name where you've got your variable, so it won't go "oh, hey, this was declared as a variable earlier". It'll just assume right away that it's supposed to be a literal table name, and look for that table... and since it can't be found (because it doesn't exist), you get that error.
If you DID by chance have a table where a field name did co-incide with a variable name, eg.
table x (foo int, bar int);
set bar='baz';
and did
UPDATE x SET foo=bar
then you've got a problem. Which bar should be used? Are you referring the variable, or to the field? That's why there's #:
UPDATE x SET foo=bar ; // use "bar" field in the table
UPDATE x SET foo=#bar; // use variable "bar" value.
And note that you couldn't use the variable as a table name, e.g.
SET x = 'tablename';
UPDATE #x SET ...
is just a flat-out syntax error. You'd have to build a query string inside your sproc, and then prepare/execute it.

Related

MySQL PROCEDURE using IF Statement with #Parameter Not Working

Why is the data not being inserted on the table when I execute the procedure, what seems to be lacking with the code?
I'm testing the procedure on phpMyAdmin > myDatabase > Procedures "Routines Tab" and clicking "Execute", prompts with a modal and ask for the values of "#idproc and #nameproc.
I tried with just the INSERT code it works, but when I add the IF condition it doesn't work.
Using XAMPP 8.0.3,
10.4.18-MariaDB
DELIMITER $$
CREATE DEFINER=`root`#`localhost:3307` PROCEDURE `testproc`(IN `idproc` INT, IN `nameproc` VARCHAR(100))
BEGIN
IF #idproc = 0 THEN
INSERT INTO testproc(
id,
name)
VALUES(
#idproc,
#nameproc
);
ELSE
UPDATE testproc
SET
id = #idproc,
name = #nameproc
WHERE id = #idproc;
END IF;
SELECT * FROM testproc;
END$$
DELIMITER ;
You mix local variables (their names have not leading #) and user-defined variables (with single leading #). This is two different variable types, with different scopes and datatype rules. Procedure parameters are local variables too.
So when you use UDV which was not used previously you receive NULL as its value - and your code works incorrectly. Use LV everywhere:
CREATE DEFINER=`root`#`localhost:3307`
PROCEDURE `testproc` (IN `idproc` INT, IN `nameproc` VARCHAR(100))
BEGIN
IF idproc = 0 THEN
INSERT INTO testproc (name) VALUES (nameproc);
ELSE
UPDATE testproc SET name = nameproc WHERE id = idproc;
END IF;
SELECT * FROM testproc;
END
You do not check does specified idproc value exists in the table. If it is specified (not zero) but not exists then your UPDATE won't update anything. Assuming that id is autoincremented primary key of the table I recommend to use
CREATE DEFINER=`root`#`localhost:3307`
PROCEDURE `testproc` (IN `idproc` INT, IN `nameproc` VARCHAR(100))
BEGIN
INSERT INTO testproc (id, name)
VALUES (idproc, nameproc)
ON DUPLICATE KEY
UPDATE name = VALUES(name);
SELECT * FROM testproc;
END
If specified idproc value exists in id column the row will be updated, if not then the new row will be inserted.
Additionally - I recommend you to provide NULL value instead of zero when you want to insert new row with specified nameproc value. NULL always cause autoincremented primary key generation whereas zero needs in specific server option setting.

Mysql stored procedure to update and return a list of matching rows

I built this stored procedure. It's supposed to select all the 'non-started' rows from a table we're using as a queue. Update those rows to mark them as 'STARTING' so they won't be grabbed by another processor and then return those rows back to the requestor for processing. It works but it seems to be putting an unacceptably high load on our servers.
The main reason for this stored procedure is to select and update the rows so we don't get the same row selected by multiple processors at the same time.
I'm not a DBA and I had to learn some stored procedure and cursor stuff on the fly so maybe I'm missing something obvious to someone else. I'm guessing there is a way to do this without completely overloading our db servers. Any help is appreciated.
We use a MySql db 5.6.xx with a Java/Tomcat web-app.
CREATE DEFINER=`admin`#`%` PROCEDURE `select_and_start_non_started`(
IN p_companyId INT(11),
IN p_howMany INT,
IN p_instance varchar(50),
IN p_status varchar(50),
IN p_updateBy varchar(50)
)
BEGIN
DECLARE v_currentId INT;
DECLARE v_loopDone INT DEFAULT 0;
DECLARE v_loopCounter INT DEFAULT 0;
DECLARE v_idList VARCHAR(1024) DEFAULT NULL;
DECLARE queue_csr CURSOR FOR
SELECT id FROM queue
WHERE (status in (_utf8'NEW' COLLATE utf8_unicode_ci,
_utf8'RESTARTED' COLLATE utf8_unicode_ci,
_utf8'WAITING' COLLATE utf8_unicode_ci,
_utf8'QUEUED' COLLATE utf8_unicode_ci))
AND if(LENGTH(p_companyId) > 0, companyid=p_companyId, true)
LIMIT p_howMany FOR UPDATE;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_loopDone=1;
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
SET autocommit = TRUE;
RESIGNAL;
END;
SET v_idList = "";
SET autocommit = FALSE;
START TRANSACTION;
OPEN queue_csr;
iq_loop:LOOP
FETCH queue_csr INTO v_currentId;
IF v_loopDone THEN LEAVE iq_loop; END IF;
UPDATE queue SET status = p_status COLLATE utf8_unicode_ci, updatedDate=NOW(), updatedBy=p_updateBy, recordStatus=p_instance WHERE id = v_currentId;
SET v_idList = CONCAT(v_idList, ",", v_currentId);
SET v_loopCounter=v_loopCounter+1;
IF v_loopCounter > p_howMany THEN LEAVE iq_loop; END IF;
END LOOP iq_loop;
CLOSE queue_csr;
SET v_loopDone=0;
COMMIT;
SET autocommit = TRUE;
SELECT * FROM queue q WHERE FIND_IN_SET(id, v_idList);
END
It works but it seems to be putting an unacceptably high load on our servers.
As 2 first steps,
It might be helpful to have "indexes" on the columns: status and
companyid. If you don't have the indexes, you can create them using:
alter table select_and_start_non_started add index (status);
alter table select_and_start_non_started add index(companyid);
p_companyId is an integer, so I am not sure why you are taking its length. Anyway, since LENGTH(p_companyId) is a constant, instead of calling it for every row, you may want to save it in a variable:
declare v_companyid_len int;
set v_companyid_len=LENGTH(p_companyId);
BEGIN
DECLARE queue_csr CURSOR FOR .... AND if(v_companyid_len>0 ...

How to check for a character in a string and replace that character before insert

Ok, this question involves one part of a complicated stored procedure which inserts new entities into several tables.
The part that I'm currently having difficulty with needs to work like so:
insert entity with original name
check if name of new entity contains any special characters listed in table A 'Characters'
if yes, than replace that character with a 'replacement character' from table A
EDIT: I've gotten this to partially work but still not finished. I'm still having a problem showing each combination of character replacements. Also in the case of a replacement character occurring more than once, such as the '.', the substitutions needs to happen independently of one another.
ex: #www.test&aol.com -> #wwwtest&aol.com, #www.test&aolcom
Here's a rough start, I know parts of this aren't going to work, but I thought it was a decent starting point:
declare #test varchar(50)
set #test = '#www.test&aol.com'
declare #len int, #ctr int
set #len = LEN(#test)
set #ctr = 1
declare #newName varchar(50)
declare #matchedChar table(match varchar(10),replaceChar varchar(10),processed int default(0))
declare #alternateEntities table(name varchar(50))
declare #repChar varchar(10)
declare #selectedChar varchar(1)
while #ctr<=#len
begin
--Insert matching characters and replacement characters into table variable,
--this is necessary for the # character, which has multiple replacement characters
insert into #matchedChar (match,replaceChar) select Character,ReplacementCharacter from tblTransliterations where Character = SUBSTRING(#test,#ctr,1)
--loop
while (select COUNT(*) from #matchedChar where processed = 0)>0
begin
--get the top character from table variable
set #selectedChar = (select top 1 match from #matchedChar where processed = 0)
--get replacement character
set #repChar = (select top 1 replaceChar from #matchedChar where processed = 0)
--replace character in name string
--set #newName = (select Replace(#test,#selectedChar,#repChar))
set #newName = (select STUFF(#test,CHARINDEX(#selectedChar,#test),1,#repChar))
--update table variable to move onto next character
update #matchedChar set processed = 1 where #repChar = replaceChar
--add name with replaced character to alternate entities table
insert into #alternateEntities (name) values (#newName)
end
set #ctr = #ctr+1
set #len = LEN(#test)
end
select * from #alternateEntities
Instead of looping, use set-based approach.
Create a temp table and populate column 'Words' of type NVARCHAR(100), call the temp table Invalid_Words
Create a column on Invalid_Words for each token and make the col type = bit
Update the temp table bit columns if a word contains the token through a series of update statements
You now have defined which tokens were matched for each word.
The next part is to replace.

How to make MySQL produce an error if value not specified on NOT NULL Columns?

Let's assume that I have a NOT NULL column in a table,
How can I make MySQL to produce an error if such statement is used?
INSERT INTO tableName () VALUES ();
Thank you.
To set a column to not null use this syntax :
ALTER TABLE table_name
MODIFY column_name [data type] NOT NULL;
If you column is declared not null an error will be produced !!
If you want a customized error msg then you need to create trigger action !
Here is a trigger that can help you :
DELIMITER $$
CREATE TRIGGER trgBEFORE UPDATE ON `tbl`
FOR EACH ROW BEGIN
declare msg varchar(255);
IF (NEW.col1IS NULL ) THEN
set msg = concat('MyTriggerError: Trying to insert a null value );
signal sqlstate '45000' set message_text = msg;
ELSE
SET NEW.col1= NEW.col1);
END IF;
END$$
DELIMITER ;
I know this is late but it might help someone.
You can set the SQL mode, either in the configuration file or at runtime for current session.
To produce the error you need to enable strict sql mode with:
SET GLOBAL sql_mode = 'STRICT_ALL_TABLES'; or SET GLOBAL sql_mode = 'STRICT_TRANS_TABLES';
Here is a link for more information on sql modes.
How to make MySQL produce an error, when inserting a row to a table containing NOT NULL column, when not specifying a value to that column like in "INSERT INTO tableName () VALUES()".
Is there a way without a trigger?
This is possible without a trigger only when you define no default when defining a column. Also the same is applicable for alter ... column ...
Example 1:
create table ck_nn( i int not null );
insert into ck_nn values();
The above insert throws an error as no default is defined on the column.
Message can be something like Field 'i' doesn't have a default value.
Example 2:
create table ck_nn2( i2 int not null default 999 );
insert into ck_nn2 values();
The above insert won't throw any error as default value is defined on the column.
select * from ck_nn2;
+-----+
| i2 |
+-----+
| 999 |
+-----+
Example # SQL Fiddle

Mysql FETCH CURSOR result ununderstood

I've been Googleing around for a while and I am sure that the problem is that I don't understand clearly how CURSORs in MySQL work.
A short explanation of the problem: I'm writing such function (simplified):
CREATE DEFINER=`me`#`localhost` FUNCTION `product_move`(prID INT, tr_type VARCHAR(2), clID INT, am INT, dnID INT, usrID INT, price FLOAT(10,2), ti DATETIME, barc TINYTEXT, cmt TINYTEXT, lnID INT)
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE cur_id INT;
DECLARE net_pr FLOAT(10,2);
DECLARE cur_r INT;
DECLARE remaind INT DEFAULT 0;
DECLARE avg_price FLOAT(10,2) DEFAULT 0;
DECLARE curs CURSOR FOR SELECT `products_transactionsID`,
`price`,
`remains`
FROM `products_transactions`
WHERE `productID`=prID AND `remains`>0 AND `type`='V'
ORDER BY `products_transactionsID` ASC;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN curs;
SET remaind=am;
read_loop:LOOP
FETCH curs INTO cur_id,net_pr,cur_r;
IF done THEN
LEAVE read_loop;
END IF;
IF (cur_r>=remaind) THEN
SET avg_price = avg_price + (net_pr * remaind);
UPDATE `products_transactions` SET `remains`=`remains`-remaind WHERE products_transactionsID=cur_id;
LEAVE read_loop;
ELSE
SET avg_price = avg_price + (net_pr * cur_r);
SET remaind=remaind-cur_r;
UPDATE `products_transactions` SET `remains`=0 WHERE products_transactionsID=cur_id;
END IF;
END LOOP;
CLOSE curs;
SET avg_price=avg_price/am;
INSERT INTO products_transactions
(`products_transactionsID`,`clientID`,`date_created`,`delivery_notesID`,`type`,`productID`,`amountIN`,`amountOUT`,`barcodes`,`in_stock`,`out_stock`,`out_repair`,`out_loss`,`booked`,`ordered`,`userID`,`price`,`comments`,`fifo_buy_price`)
SELECT NULL, clID, ti, dnID , tr_type, prID, 0, am, barc, products_transactions.in_stock-am, products_transactions.out_stock,
products_transactions.out_repair, products_transactions.out_loss, products_transactions.booked, products_transactions.ordered,usrID,price,cmt,avg_price
FROM
products_transactions WHERE productID=prID ORDER BY products_transactionsID DESC LIMIT 1;
So, we insert a new row in this table, based upon some calculations from the previously selected rows and updating these rows meanwhile.
The problem is with the avg_price variable, which should be calculated based on the net_pr variable which is FETCH'ed from the cursor. But somehow, instead of being FETCH'ed from the SELECT, the net_pr variable takes the value of the price input parameter of my function! How is that possible?
My guesses have been so far:
a variable name conflict? Searched through the code but I can't find any.
updating the table within the LOOP could make the CURSOR loose its position? It would make sense, but that wouldn't result in this, either...
I'd apreciate any ideas.
Two things that I can see:
1) Don't update the table that you're using in the cursor. MySQL says the cursor is read only but I wouldn't trust this. Set your value, exit the cursor, and then update the table.
2) Using the same name for a variable in the proc definition and a column in a select gives a conflict: http://dev.mysql.com/doc/refman/5.0/en/local-variable-scope.html
"A local variable should not have the same name as a table column. If an SQL statement, such as a SELECT ... INTO statement, contains a reference to a column and a declared local variable with the same name, MySQL currently interprets the reference as the name of a variable. "