MYSQL trigger error (lots of function) - mysql

i can't seem to found any fault on my code to make a trigger. ( i usually code using oracle, but i convert to my sql in this project, checked all the function and convert those that aren't available in mysql already)
here's the code :
CREATE TRIGGER `transaction_before_insert` BEFORE INSERT ON `transaction` FOR EACH ROW BEGIN
DECLARE TEMPKODE VARCHAR(12);
DECLARE TEMP VARCHAR(5);
TEMP:= CONCAT('T',DATE_FORMAT(NOW(),'%Y'));
SELECT CONCAT(TEMP, LPAD(NVL(MAX(CAST(SUBSTR(TRANSACTION_ID,5,5) AS UNSIGNED))+1,1),5,0))
FROM TRANSACTION INTO TEMPKODE
WHERE SUBSTR(TRANSACTION_ID,1,4) = TEMP;
NEW.TRANSACTION_ID := TEMPKODE;
END
EDIT 1:
i'm coding it from heidisql if there's any code difference, since i heard if i do it on mysql work bench i should use
SET variables
instead of directly
variables :=
the desired result is forex: T201600001
//T for transaction, 2016 i got it from dateformat, and the rest is choosing the biggest data from the database
it's a software for production planning so i'm making the transaction code

NVL, is a function built for you?, Oracle NVL function does not exist in MySQL (find its equivalent in MySQL), see IFNULL.
DELIMITER $$
BEGIN
DECLARE TEMPKODE VARCHAR(12);
DECLARE TEMP VARCHAR(5) DEFAULT CONCAT('T',DATE_FORMAT(NOW(),'%Y'));
-- OR: SET TEMP := CONCAT('T',DATE_FORMAT(NOW(),'%Y'));
-- TEMP := CONCAT('T',DATE_FORMAT(NOW(),'%Y'));
/*
SELECT CONCAT(TEMP,LPAD(NVL(MAX(CAST(SUBSTR(TRANSACTION_ID,5,5) AS UNSIGNED))+1,1),5,0))
FROM TRANSACTION INTO TEMPKODE
WHERE SUBSTR(TRANSACTION_ID,1,4) = TEMP;
*/
SELECT CONCAT(TEMP,LPAD(COALESCE(MAX(CAST(SUBSTR(TRANSACTION_ID,5,5) AS UNSIGNED))+1,1),5,0))
FROM TRANSACTION
WHERE SUBSTR(TRANSACTION_ID,1,4) = TEMP INTO TEMPKODE;
-- NEW.TRANSACTION_ID := TEMPKODE;
SET NEW.TRANSACTION_ID := TEMPKODE;
END$$
DELIMITER ;
UPDATE
You can simplify with the answer of #GordonLinoff:
SET NEW.TRANSACTION_ID := CONCAT(...);

You really do not need temporary variables for this operation (in either Oracle or MySQL). I think the following is the same logic:
BEGIN
select new.transactionid := CONCAT('T', YEAR(now()),
LPAD(COALESCE(MAX(SUBSTR(TRANSACTION_ID, 5, 5) + 1
), 1
), 5, 0)
from transaction t
where TRANSACTION_ID LIKE CONCAT(YEAR(now()), '%')
END;

Related

Mysql - Return table from stored procedure into a variable?

Thanks to this answer https://stackoverflow.com/a/8180159/16349298 , i'm able to translate
a string into a temporary table (usable for WHERE <id> IN <tmpTable>.<colomn>)
The only modification i made is at the end (The select) :
CREATE PROCEDURE stringToTmpTable(IN inputString VARCHAR(255), IN sep VARCHAR(255))
BEGIN
declare pos int; -- Keeping track of the next item's position
declare item varchar(100); -- A single item of the input
declare breaker int; -- Safeguard for while loop
-- The string must end with the delimiter
if right(inputString, 1) <> sep then
set inputString = concat(inputString, sep);
end if;
DROP TABLE IF EXISTS MyTemporaryTable;
CREATE TEMPORARY TABLE MyTemporaryTable ( columnName varchar(100) );
set breaker = 0;
while (breaker < 2000) && (length(inputString) > 1) do
-- Iterate looking for the delimiter, add rows to temporary table.
set breaker = breaker + 1;
set pos = INSTR(inputString, sep);
set item = LEFT(inputString, pos - 1);
set inputString = substring(inputString, pos + 1);
insert into MyTemporaryTable values(item);
end while;
SELECT * FROM MyTemporaryTable;
END
I would like to use this process in a function or procedure in order to call it in any procedure that needs it.
So here is the problem :
I don't know how to store the result of this procedure into a variable : i can't use the SELECT * INTO #p FROM ...; like CALL stringToTmpTable(<string>,<separator>) INTO #table;
An other way would be to add OUT parameter to stringToTmpTable() but it can't return multiple rows. Unfortunatly the amount of parameters in the string is variable so i can't define as much variable as there is parameters in the string.
Finally the FIND_IN_SET() isn't the solution i need.
In the worst case I could copy / past the stringToTmpTable() process in any other procedure that needs it, but that doesn't seem like the best way to me.
Any suggestions ?
"i'm able to translate a string into a temporary table" too, but I am using a different method:
SET #input = 'Banana, Apple, Orange, Pears';
WITH RECURSIVE cte1 as (
select
#input as s,
substring_index(substring_index(#input,',',1),',',-1) as w,
length(#input)-length(replace(#input,',','')) x
union all
select
substring_index(s,',',-x),
trim(substring_index(substring_index(substring_index(s,',',-x),',',1),',',-1)) as w,
x-1 x
from cte1 where s<>'' and x>0
)
select * from cte1
DBFIDDLE
But it's a bit of a problem to determine the real problem you have, which is causing you to ask this question. So this is not an answer, but just a different way of selecting all words from a comma-delimted string.

MySQL running totals in procedure

I just cant get this to work within a procedure (as a simple query this works fine):
SET #c:=0;
SELECT
(#c := #c + 1) AS Cumulative,
FROM
NumberTable,
Table
...
I think it hast something to to with the "counter"-variable. For the procedure I tried:
DECLARE c int(4) DEFAULT 0;
BEGIN
SELECT
(c = c + 1) AS Cumulative,
FROM
NumberTable,
Table
...
END
But that doesn't work (gives NULL). Any suggestions?
PS: I think the question breaks down to this: How to SET a variable within SELECT while using a PROCEDURE (since := does not work there)

Temporary Table in Stored Functions?

I'm writing a function that I need to use either a TABLE variable for (I hear they don't exist in MySQL) or a temporary table.
However, it seems that temporary tables only seem to work in stored procedures, not functions. I keep getting this error:
Explicit or implicit commit is not allowed in stored function or
trigger.
What I'm trying to build is a solution to an earlier question of mine. It's a function that receives a start date, an end date, and a comma-deliminated string. It first finds all the months between the start and end date and saves them as individual records in the first temporary table. It then parses out the comma-deliminated string and saves those into a second temporary table. Then it does a select join on the two, and if records are present, it returns true, otherwise false.
My intention is to use this as part of another queries WHERE clause, so it needs to be a function and not a stored procedure.
How can I use temporary tables in stored functions? And if I can't, what can I do instead?
Here's my (currently broken) function (or as a gist):
-- need to parse out a string like '4,2,1' and insert values into temporary table
-- MySQL doesn't have a native string split function, so we make our own
-- taken from: http://blog.fedecarg.com/2009/02/22/mysql-split-string-function/
DROP FUNCTION IF EXISTS SPLIT_STR;
CREATE FUNCTION SPLIT_STR(x VARCHAR(255), delim VARCHAR(12), pos INT) RETURNS VARCHAR(255)
RETURN REPLACE(SUBSTRING(SUBSTRING_INDEX(x, delim, pos), LENGTH(SUBSTRING_INDEX(x, delim, pos -1)) + 1), delim, '');
-- need to find all months between the start and end date and insert each into a temporary table
DROP FUNCTION IF EXISTS months_within_range;
DELIMITER //
CREATE FUNCTION months_within_range(starts_at DATE, ends_at DATE, filter_range VARCHAR(255)) RETURNS TINYINT
BEGIN
DROP TABLE IF EXISTS months_between_dates;
DROP TABLE IF EXISTS filter_months;
CREATE TEMPORARY TABLE months_between_dates (month_stuff VARCHAR(7));
CREATE TEMPORARY TABLE filter_months (filter_month VARCHAR(7));
SET #month_count = (SELECT PERIOD_DIFF(DATE_FORMAT(ends_at, "%Y%m"), DATE_FORMAT(starts_at, "%Y%m")));
-- PERIOD_DIFF only gives us the one month, but we want to compare to, so add one
-- as in, the range between 2011-01-31 and 2011-12-01 should be 12, not 11
INSERT INTO months_between_dates (month_stuff) VALUES (DATE_FORMAT(starts_at, "%Y-%m"));
SET #month_count = #month_count + 1;
-- start he counter at 1, since we've already included the first month above
SET #counter = 1;
WHILE #counter < #month_count DO
INSERT INTO months_between_dates (month_stuff) VALUES (DATE_FORMAT(starts_at + INTERVAL #counter MONTH, "%Y-%m"));
SET #counter = #counter + 1;
END WHILE;
-- break up the filtered string
SET #counter = 1;
-- an infinite loop, since we don't know how many parameters are in the filtered string
filters: LOOP
SET #filter_month = SPLIT_STR(filter_range, ',', #counter);
IF #filter_month = '' THEN LEAVE filters;
ELSE
INSERT INTO filter_months (filter_month) VALUES (#filter_month);
SET #counter = #counter + 1;
END IF;
END LOOP;
SELECT COUNT(*) INTO #matches FROM months_between_dates INNER JOIN filter_months ON months_between_dates.month_stuff = filter_months.filter_month;
IF #matches >= 1 THEN RETURN 1;
ELSE RETURN 0;
END//
DELIMITER ;
drop table statements cause an implicit commit, which is not allowed in a mysql function. drop temporary table doesn't cause the commit though. if you're not worried about regular (non-temporary) tables named months_between_dates or filter_months existing you should be able to change
DROP TABLE IF EXISTS months_between_dates;
DROP TABLE IF EXISTS filter_months;
to
DROP TEMPORARY TABLE IF EXISTS months_between_dates;
DROP TEMPORARY TABLE IF EXISTS filter_months;

Iterate 50 times over an SQL command

I want to make propotion codes for my product.
I have created an appropriate SQL statement which basically uses the current timestamp and runs SHA1 on it.
I tried a while ago to create an iterative loop over my INSERT command but failed
anyone know how?
Do 50 times
INSERT INTO ......
end
Also, I cannot have two of the same promotion code so the timestamp needs to be different for each iteration (If it is at all possible that the timestamp might be the same between iterations).
write the code in python or some other scripting language.
Use a GUID for the promotion code rather than a hash of the timestamp.
http://dev.mysql.com/doc/refman/5.0/en/flow-control-constructs.html
Your approach with using the PROCEDURE is the best I guess. I would first set another delimiter, because you are using the ';' already:
delimiter //
After that define your procedure. The INSERT INTO ...... is your INSERT code, what you wrote in your question.
CREATE PROCEDURE createPromotions(p1 INT)
BEGIN
SET #x = 0;
REPEAT
SET #x = #x + 1;
INSERT INTO ......
UNTIL #x > p1
END REPEAT;
END
//
After entering the '//' the procedure is ready to CALL. And if you want to execute it 50 times, just call it 50 times:
CALL createPromotions(50)
This is pretty dirty, but if you have any table on your system which you know has more than 50 rows, you can do the following:
create table promotion_code ( pc varchar(100) );
set #c = 1;
insert into promotion_code
select sha1( now() + (#c := #c + 1 ) )
from mysql.help_relation limit 50;
CREATE PROCEDURE createPromotions(p1 INT)
BEGIN
SET #x = 0;
REPEAT SET #x = #x + 1; UNTIL #x > p1 END REPEAT;
END
Some sort of proceedure like this would be nice but I don't know how to get it into the format I need. which is to repeat an SQL statement 50 times

Using variable in a LIMIT clause in MySQL

I am writing a stored procedure where I have an input parameter called my_size that is an INTEGER. I want to be able to use it in a LIMIT clause in a SELECT statement. Apparently this is not supported, is there a way to work around this?
# I want something like:
SELECT * FROM some_table LIMIT my_size;
# Instead of hardcoding a permanent limit:
SELECT * FROM some_table LIMIT 100;
For those, who cannot use MySQL 5.5.6+ and don't want to write a stored procedure, there is another variant. We can add where clause on a subselect with ROWNUM.
SET #limit = 10;
SELECT * FROM (
SELECT instances.*,
#rownum := #rownum + 1 AS rank
FROM instances,
(SELECT #rownum := 0) r
) d WHERE rank < #limit;
STORED PROCEDURE
DELIMITER $
CREATE PROCEDURE get_users(page_from INT, page_size INT)
BEGIN
SET #_page_from = page_from;
SET #_page_size = page_size;
PREPARE stmt FROM "select u.user_id, u.firstname, u.lastname from users u limit ?, ?;";
EXECUTE stmt USING #_page_from, #_page_size;
DEALLOCATE PREPARE stmt;
END$
DELIMITER ;
USAGE
In the following example it retrieves 10 records each time by providing start as 1 and 11. 1 and 11 could be your page number received as GET/POST parameter from pagination.
call get_users(1, 10);
call get_users(11, 10);
A search turned up this article. I've pasted the relevant text below.
Here's a forum post showing an example of prepared statements letting
you assign a variable value to the limit clause:
http://forums.mysql.com/read.php?98,126379,133966#msg-133966
However, I think this bug should get some attention because I can't
imagine that prepared statements within a procedure will allow for any
procedure-compile-time optimizations. I have a feeling that prepared
statements are compiled and executed at the runtime of the procedure,
which probaby has a negative impact on efficiency. If the limit
clause could accept normal procedure variables (say, a procedure
argument), then the database could still perform compile-time
optimizations on the rest of the query, within the procedure. This
would likely yield faster execution of the procedure. I'm no expert
though.
I know this answer has come late, but try SQL_SELECT_LIMIT.
Example:
Declare rowCount int;
Set rowCount = 100;
Set SQL_SELECT_LIMIT = rowCount;
Select blah blah
Set SQL_SELECT_LIMIT = Default;
This feature has been added to MySQL 5.5.6.
Check this link out.
I've upgraded to MySQL 5.5 just for this feature and works great.
5.5 also has a lot of performance upgrades in place and I totally recommend it.
Another way, the same as wrote "Pradeep Sanjaya", but using CONCAT:
CREATE PROCEDURE `some_func`(startIndex INT, countNum INT)
READS SQL DATA
COMMENT 'example'
BEGIN
SET #asd = CONCAT('SELECT `id` FROM `table` LIMIT ',startIndex,',',countNum);
PREPARE zxc FROM #asd;
EXECUTE zxc;
END;
As of MySQL version 5.5.6, you can specify LIMIT and OFFSET with variables / parameters.
For reference, see the 5.5 Manual, the 5.6 Manual and #Quassnoi's answer
I've faced the same problem using MySql 5.0 and wrote a procedure with the help of #ENargit's answer:
CREATE PROCEDURE SOME_PROCEDURE_NAME(IN _length INT, IN _start INT)
BEGIN
SET _start = (SELECT COALESCE(_start, 0));
SET _length = (SELECT COALESCE(_length, 999999)); -- USING ~0 GIVES OUT OF RANGE ERROR
SET #row_num_personalized_variable = 0;
SELECT
*,
#row_num_personalized_variable AS records_total
FROM(
SELECT
*,
(#row_num_personalized_variable := #row_num_personalized_variable + 1) AS row_num
FROM some_table
) tb
WHERE row_num > _start AND row_num <= (_start + _length);
END;
Also included the total rows obtained by the query with records_total.
you must DECLARE a variable and after that set it. then the LIMIt will work and put it in a StoredProcedure not sure if it works in normal query
like this:
DECLARE rowsNr INT DEFAULT 0;
SET rowsNr = 15;
SELECT * FROM Table WHERE ... LIMIT rowsNr;