I want to create a stored function in MySQL. I've been granted ALL PRIVILEGES, what I think contains also the required SUPER privilege. And binary logging is enabled.
While creating a function I get the 1419 error:
Error Code: 1419. You do not have the SUPER privilege and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable)
I read through the MySQL manuals and it looks like this binary logging issue should only apply to NOT DETERMINISTIC functions, which change data.
I've created a simple example function which points out my question more clearly:
DROP FUNCTION IF EXISTS getIdTest;
DELIMITER $$
CREATE FUNCTION getIdTest( pv_order_nr VARCHAR( 45 ) )
RETURNS INT UNSIGNED
COMMENT 'Gets an order number and returns an ID'
DETERMINISTIC READS SQL DATA
BEGIN
DECLARE lv_id INT UNSIGNED;
-- DOES THIS COMMAND MAKE THE FUNCTION NOT-DETERMINISTIC?
CREATE TEMPORARY TABLE tmp_log(
order_nr VARCHAR(45)
, message VARCHAR(255)
, created_at DATETIME
);
-- AND/OR DOES THIS COMMAND MAKE THE FUNCTION NON-DETERMINISTIC?
INSERT INTO tmp_log
SET order_nr = pv_order_nr
, message = CONCAT( 'Id read for order ', pv_order_nr, '.')
, created_at = NOW();
SELECT so.id_sales_order
INTO lv_id
FROM sales_order AS so
WHERE so.order_nr = pv_order_nr
LIMIT 1;
RETURN lv_id;
END
$$
DELIMITER ;
As you see my function is declared as DETERMINISTIC.
My question is, does the second statement in the function routine body (CREATE TEMPORARY TABLE) make the function NOT DETERMINISTIC?
If I omit this statement, does the third statement (INSERT INTO --a temporary table--) make the function NOT DETERMINISTIC as well?
Thanks for reading this :)
Felix
Related
If I have a TABLE named MyTable which has columns say C1(type date) and C2 (type character) I want to create a stored function that takes an input and the input should always belong to C1, and the output of the stored function should be the corresponding element in C2. I have tried to do it using the 'select' statement followed by 'where' clause inside the stored function but was not able to achieve it. Is there any other way to accomplish this task.
DELIMITER $$
CREATE FUNCTION `MyFunction`
(`Date` datetime)
RETURNS char(10)
BEGIN
DECLARE MyVariable char(10)
SELECT MyVariable = `C2`
FROM MyTable
WHERE `Date` = `C1`; RETURN MyVariable;
END$$
DELIMITER ;
But this keeps giving me ERROR CODE: 1064
At first glance, I see a syntax error:
...
BEGIN
DECLARE MyVariable char(10) <-- needs a semicolon here
SELECT MyVariable = `C2`
...
Every statement within the body of your routine must end with a semicolon. See examples of DECLARE in this manual page: https://dev.mysql.com/doc/refman/8.0/en/local-variable-scope.html
It should be like this:
...
BEGIN
DECLARE MyVariable char(10);
SELECT MyVariable = `C2`
...
Re your comment:
Error 1415 means "cannot return a result set". Your stored function is doing a SELECT without putting the result into your declared local variable using an INTO keyword.
You appear to be trying to set the value of MyVariable using = but that's just making a comparison. It doesn't assign anything to MyVariable.
Without using INTO to assign the variable, your SELECT statement is by default returning a result set. This is allowed in a stored procedure, but not in a stored function. A stored function must return a single scalar value, not a result set.
...
BEGIN
DECLARE MyVariable char(10);
SELECT `C2` INTO MyVariable
FROM MyTable
WHERE `Date` = `C1`;
RETURN MyVariable;
END
P.S.: I edited your question to replace the term "user-defined function" with "stored function". These are two different things in MySQL. You are writing a stored function.
In MySQL, they use the term user-defined function (UDF) for a function you implement in C/C++ code and compile into the MySQL server. It's less common for developers to write this type of extension.
I am trying to write a simple procedure but am encountering a syntax error at the first parameter. As best I can tell I'm following the syntax of CREATE PROCEDURE correctly.
I am limited to accessing my database with phpMyAdmin. Here is the create script I'm trying to run:
DROP PROCEDURE IF EXISTS product_index_swap/
CREATE PROCEDURE product_index_swap (#id INT, #oldIndex INT, #newIndex INT)
BEGIN
DECLARE #swapID;
SET #swapID = (SELECT `id` FROM `product` WHERE `order_index` = #newIndex LIMIT 1);
UPDATE `products` SET `order_index` = (CASE WHEN `id` = #id THEN #newIndex
WHEN `id` = #swapID THEN #oldIndex END)
WHERE `id` IN (#id, #swapID);
END
I am using the option on phpMyAdmin to change the delimiter to /.
I receive a syntax error "near '#id INT, #oldIndex INT....". I thought I may encounter more delimiter errors since I'm not entirely clear on the scope of them. I believe if that was the problem the error would be on a new line in the procedure when it failed to understand a semicolon, not at the parameters declaration.
You're using the Microsoft SQL Server convention of putting # before all the parameters and local variables. MySQL doesn't do this.
In MySQL syntax, procedure parameters have no sigil.
Also parameters are typically declared IN or OUT or INOUT.
CREATE PROCEDURE product_index_swap (IN id INT, IN oldIndex INT, IN newIndex INT)
BEGIN
DECLARE swapID;
...
MySQL variables that have the # sigil are session variables.
See also:
https://dev.mysql.com/doc/refman/5.7/en/create-procedure.html
https://dev.mysql.com/doc/refman/5.7/en/declare-local-variable.html
https://dev.mysql.com/doc/refman/5.7/en/set-variable.html
In MySQL, the #var variables are session level variables.
Use normal variables without the # and make sure you do not have conflict with column names:
CREATE PROCEDURE product_index_swap (in_id INT, in_oldIndex INT, in_newIndex INT)
BEGIN
DECLARE v_swapID int;
SELECT id into v_swapID
FROM product
WHERE order_index = in_newIndex
LIMIT 1;
UPDATE products
SET order_index = CASE WHEN id = in_id THEN in_newIndex
WHEN id = v_swapID THEN in_oldIndex
END
WHERE id IN (in_id, v_swapID);
END
Simple version of the DDL:
create function rm00dv1.no_concurrent_schedules()
returns integer
LANGUAGE SQL
READS SQL DATA
NO EXTERNAL ACTION
NOT DETERMINISTIC
BEGIN
declare num_overlaps integer;
select count(*)
into num_overlaps
from
rm00dv1.schedules a
where
a.id != 0
and
exists (
select 1
from rm00dv1.schedules b
where
b.id = 0 -- matches the key of a given record
and rm00dv1.isConcurrent(b.schdl_eff_dt, b.schdl_trm_dt, a.schdl_eff_dt, a.schdl_trm_dt) != 0
);
return num_overlaps;
end;
Table:
create table rm00dv1.schedules (
id int not null,
schdl_eff_dt date not null,
schdl_trm_dt date not null,
info_chg_ts timestamp(6) not null with default
)
in RM00DV1.TSRMDV01 ;
alter table rm00dv1.schedules add constraint no_schedule_overlap
check ((schdl_trm_dt < '01/01/2015')
or
rm00dv1.no_concurrent_schedules() <= 0);
I am getting an SQL00551N - no execution privilege and that is odd because I can execute the function in a select statement.
Any idea to solve this problem?
Thanks.
Looks like you can't. I'm looking at the DB2 10 for z/OS reference for ALTER TABLE reference and it says the following under CHECK (check-condition): "A check-condition is a search condition, with the following restrictions: ... must not contain... Built-in or user-defined functions...".
Since your function looks like it won't convert to a check condition, defining triggers on the table might be the next best option.
I learned that AFTER triggers do not get a -746 like BEFORE triggers do. I had really wanted to use a CONSTRAINT because that best captures the intent for people who come after me, with a BEFORE trigger to terminate the active schedules. But, it looks like a sequence of triggers is going to be the way to go. It is a bit clunky because the triggers all have to be created separately and you have to look at them together to get the intent, and because correct behavior is dependent on their creation order. Yes, it is documented that they will be executed in the order of their creation.
Happy path termination of rows without a specified termination date:
CREATE TRIGGER terminate_no_trm
after
INSERT ON schedules
referencing new as new
FOR EACH ROW
MODE DB2SQL
BEGIN ATOMIC
update schedules
set
schdl_trm_dt = max(schdl_eff_dt, new.schdl_eff_dt - 1 days) -- prob not necessary, but don't set the trm before the eff
, info_chg_ts = new.info_chg_ts
where
new.keyCombo = keyCombo
and
schdl_trm_dt = '9999-12-31'
and schdl_eff_dt < new.schdl_eff_dt;
end
Prevent insert of rows if that insert causes an overlap:
CREATE TRIGGER no_overlapping_schedules_i
after
insert ON schedules
referencing new as n
FOR EACH ROW
MODE DB2SQL
when (num_concurrent_schedules(n.keyCombo) > 0)
begin atomic
SIGNAL SQLSTATE '75001' (
'Concurrent schedules detected: '
concat ' ' concat cast(n.keyCombo as varchar(32))
concat ': ' concat cast(n.schdl_eff_dt as varchar(32))
concat ' to ' concat cast(n.schdl_trm_dt as varchar(32))
);
end
and prevent UPDATE if that would result in an overlap
CREATE TRIGGER no_overlapping_schedules_u
after
update ON schedules
referencing new as n
FOR EACH ROW
MODE DB2SQL
when (num_concurrent_schedules(n.keyCombo) > 0)
begin atomic
SIGNAL SQLSTATE '75001' (
'Concurrent schedules detected: '
concat ' ' concat cast(n.keyCombo as varchar(32))
concat ': ' concat cast(n.schdl_eff_dt as varchar(32))
concat ' to ' concat cast(n.schdl_trm_dt as varchar(32))
);
end
Thanks for the ideas.
Does anybody know if this is allowed?
IF CALL GET_RIGHT_NODE(edge) = 15
THEN
SELECT "IT WORKS";
I'm getting an error on this syntax, is it possible any other way?
The return values from stored procedures should be captured in OUT paramters (whereas those from user defined functions can be captured as #returnValue = function()).
So, your GET_RIGHT_NODE should take an OUT parameter and set it to the return value.
CREATE PROCEDURE GET_RIGHT_NODE
(
#edge INT,
#returnValue INT OUTPUT
)
AS
-- Definition of the proc.
then you would call the procedure as follows:
DECLARE #returnValue INT
CALL GET_RIGHT_NODE(#edge, #returnValue)
IF (#returnValue = 15)
THEN
SELECT 'IT WORKS'
I'm having a problem with executing a stored procedure from Perl (using the DBI Module). If I execute a simple SELECT * FROM table there are no problems.
The SQL code is:
DROP FUNCTION IF EXISTS update_current_stock_price;
DELIMITER |
CREATE FUNCTION update_current_stock_price (symbolIN VARCHAR(20), nameIN VARCHAR(150), currentPriceIN DECIMAL(10,2), currentPriceTimeIN DATETIME)
RETURNS INT
DETERMINISTIC
BEGIN
DECLARE outID INT;
SELECT `id` INTO outID FROM `mydb449`.`app_stocks` WHERE `symbol` = symbolIN;
IF outID > 0 THEN
UPDATE `mydb449`.`app_stocks`
SET `currentPrice` = currentPriceIN, `currentPriceTime` = currentPriceTimeIN
WHERE `id` = outID;
ELSE
INSERT INTO `mydb449`.`app_stocks`
(`symbol`, `name`, `currentPrice`, `currentPriceTime`)
VALUES (symbolIN, nameIN, currentPriceIN, currentPriceTimeIN);
SELECT LAST_INSERT_ID() INTO outID;
END IF;
RETURN outID;
END|
DELIMITER ;
The Perl code:
$sql = "select update_current_stock_price('$csv_result[0]', '$csv_result[1]', '$csv_result[2]', '$currentDateTime') as `id`;";
My::Extra::StandardLog("SQL being used: ".$sql);
my $query_handle = $dbh->prepare($sql);
$query_handle->execute();
$query_handle->bind_columns(\$returnID);
$query_handle->fetch();
If I execute select update_current_stock_price('aapl', 'Apple Corp', '264.4', '2010-03-17 00:00:00') asid; using the mysql CLI client it executes the stored function correctly and returns an existing ID, or the new ID.
However, the Perl will only return a new ID, (incrementing by 1 on each run). It also doesn't store the result in the database. It looks like it's executing a DELETE on the new id just after the update_current_stock_price function is run.
Any help? Does Perl do anything funky to procedures I should know about?
Before you ask, I don't have access to binary logging, sorry.
Perhaps you're doing it in a transaction and it's getting rolled back? The row is inserted but never becomes committed and cannot be seen.
I'd try it on your dev server and enable general query log, if in doubt.
Also you may want to know about the INSERT ... ON DUPLICATE KEY UPDATE syntax, which can probably do what you're trying to do anyway.
try
$query_handle->dump_results(15, "\n", '|');
before the bind_columns call to see if it is actually getting the results back, you could also try replace SELECT storedprocedure with SELECT * FROM storedprocedure
You should check that you are running the latest version of DBD::mysql (which is the MySQL-driver used by DBI). There used to be several issues with stored procedures, at least some are fixed in recent versions. Maybe these ressources are also helpful:
http://www.perlmonks.org/?node_id=609098
http://www.perlmonks.org/?node_id=830585