I am attempting to write a function in PostgreSQL 9.0. This will eventually be used in a new aggregate function, but one step at a time.
Here is what I have so far:
create or replace function encstate(text,text) returns text as $$
DECLARE
oldstate alias for $1;
arg alias for $2;
BEGIN
IF length(oldstate)>0 then
select 'Encrypted';
else if
select '';
end if;
END;
$$ language sql strict immutable;
(I know I'm not yet using the $2 argument.)
The result is:
ERROR: syntax error at or near "alias"
LINE 3: oldstate alias for $1;
When I remove the DECLARE block and just refer to the arguments as $1 etc in the body, the result is:
ERROR: syntax error at or near "if"
LINE 3: if length($1)>0 then
As far as I can tell, what I have matches examples found on the web, except I could find no examples of functions with an if-statement, so I have no idea what I'm doing wrong. Any help would be appreciated.
I would suggest doing this as an SQL function:
create or replace function encstate(text,text) returns text as $$
SELECT CASE WHEN length($1)>0 then 'Encrypted' ELSE '' END;
$$ language sql strict immutable;
You could also do what you did with the other, but change sql to plpgsql. My suggestion though is that what you can do in an SQL function you should do in one usually. You will get better performance and the planner can do more with it.
If you want a SQL function:
create or replace function encstate(text, text) returns text as $$
select case
when length($1) > 0 then 'Encrypted'
else ''
end
;
$$ language sql strict immutable;
SQL has no variables or control structures as it is not procedural, it is declarative. If you want procedural features then use a plpgsql function:
create or replace function encstate(text, text) returns text as $$
DECLARE
oldstate alias for $1;
arg alias for $2;
BEGIN
IF length(oldstate) > 0 then
return 'Encrypted';
else
return '';
end if;
END;
$$ language plpgsql strict immutable;
SQL
CREATE OR REPLACE FUNCTION encstate(oldstate text, arg text)
RETURNS text LANGUAGE SQL IMMUTABLE AS
$func$
SELECT CASE WHEN $1 <> '' THEN 'Encrypted' ELSE '' END
$func$
PL/pgSQL
CREATE OR REPLACE FUNCTION encstate(oldstate text, arg text)
RETURNS text LANGUAGE plpgsql IMMUTABLE AS
$func$
BEGIN
IF oldstat <> '' THEN
RETURN 'Encrypted';
ELSE
RETURN '';
END IF;
END
$func$;
Major points
The expression length(x) > 0 (x being text) only excludes '' and NULL.
Use the 100 % equivalent expression x <> ''. Does the same simpler and faster, regardless of whether the function is declared STRICT or not.
Don't use plpgsql ALIAS if you don't have to. It's only there for compatibility and to rename pre-determined parameter names. The manual actively discourages its use for other purposes. I never use it. Named parameters are available since version 8.1. Simpler, better.
In SQL functions you can refer to parameter names (instead of positional parameters ($1, $2, ..) since PostgreSQL 9.2. It's still a good idea to name parameters even before that, for documentation.
I suspect you do not want to declare this function STRICT (synonym: RETURNS NULL ON NULL INPUT). Like the synonym implies, that returns NULL on (any) NULL input. Seems like you want an empty string ('') instead.
There is also a performance implication:
Function executes faster without STRICT modifier?
Related
Hi I've got a problem managing CASE WHEN or IF Statements in a CREATE FUNCTION Call in DB2
I tried this Statement:
CREATE OR REPLACE FUNCTION NAECHSTES_DATUM(PARAM1 CHAR(6), PARAM2 DATE)
RETURNS DATE
LANGUAGE SQL
BEGIN
DECLARE BASEDATE DATE;
DECLARE THATDATE DATE;
SET BASEDATE = TO_DATE(CONCAT(PARAM1,CAST(YEAR(PARAM2) AS CHAR(4))),'DD.MM.YYYY');
IF (BASEDATE >= PARAM2)
THEN SET THATDATE = BASEDATE;
ELSE SET THATDATE = BASEDATE + 1 YEAR;
END IF;
RETURN THATDATE;
END
I get this error
[-104] Auf "+ 1 YEAR" folgte das unerwartete Token "END-OF-STATEMENT". Mögliche Tokens: "
END IF".. SQLCODE=-104, SQLSTATE=42601, DRIVER=4.14.113
Similar result when I use CASE WHEN.
Do you know where the problem could be?
Use an alternative statement delimiter after the END of the function.
Inside the function the statement delimiter is the semicolon (;)
But Db2 needs to know an additional delimiter to indicate the end of the block.
For the Db2 command line (in shell scripts, batch files) you can use the "-td#" command line option and terminate the block with the # character. Inside a file that contains the function you can use:
--#SET TERMINATOR #
anywhere before the function block and then terminate the block with #
If you are use a GUI tool to submit the DDL or SQL, each GUI tool has its own way to specify alternative statement delimiters. Look at the settings and properties. It's always wise to fully describe your toolset (which programs, which versions, which operating system etc) in your question.
I'm trying to figure out how to store variables inside a function in mySQL. I am trying to create a function that capitalizes a field name. Creating a function works if I don't create variables. The problem is this is difficult to read, and is easy to make mistakes with.
CREATE FUNCTION capitalize(string TEXT)
RETURNS TEXT
RETURN CONCAT(UPPER(LEFT(string,1)), LOWER(RIGHT(string, LENGTH(string) - 1)));
When I try to add variables using the DECLARE and SET keywords, it no longer works.
CREATE FUNCTION capitalize(string TEXT)
RETURNS TEXT
DECLARE first_letter TEXT;
DECLARE last_letters TEXT;
SET first_letter = UPPER(LEFT(string,1));
SET last_letters = LOWER(RIGHT(string, LENGTH(string) - 1));
RETURN CONCAT(first_letter, last_letters);
I get this error message
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 4
I've fiddled around with it, removing semicolons, and double/triple checking parentheses. I've fiddled with BEGIN and END statements but nothing seems to work at all.
I have searched extensively on this topic but cannot figure out where the problem lies.
The body of a CREATE FUNCTION can consist of only a single statement, which is why the first version works and the second doesn't. Fortunately, that single statement can be a compound statement enclosed in a BEGIN ... END block.
You need to enclose the function body in a BEGIN ... END block to allow MySQL to see it as a single statement; you'll also perhaps need to precede and follow it with DELIMITER statements (depending on your client, as Mr. Berkowski points out):
DELIMITER //
CREATE FUNCTION capitalize(string TEXT)
RETURNS TEXT
BEGIN
DECLARE first_letter TEXT;
DECLARE last_letters TEXT;
SET first_letter = UPPER(LEFT(string,1));
SET last_letters = LOWER(RIGHT(string, LENGTH(string) - 1));
RETURN CONCAT(first_letter, last_letters);
END; //
DELIMITER ;
(Note especially the space between the last DELIMITER and the semicolon.)
I am interested in learning how to use dynamic functions in Postgres. I basically need a dynamic function that will spit out a table or a subtable (a few chosen columns from an existing table).
I've created the following eval() function, but it is not working as I'd like it to.
I would like my function to return the result of a query that is introduced as a string within the function. The function currently returns only the first value from the first column (enrich_d_dkj_p_k27ac). It seems like I should change my returns from text to something else?
create or replace function eval() returns text
as
$body$
declare
result text;
begin
execute 'select enrich_d_dkj_p_k27ac,enrich_lr_dkj_p_k27ac,enrich_r_dkj_p_k27ac
from dkj_p_k27ac' into result;
return result;
end;
$body$
language plpgsql;
SELECT eval();
Result is:
eval text
2.4
But this is basically only the very first value of the first column - I will need the entire table to appear - or in other words - the result of the indicated SELECT.
It would work like this (but it's useless):
create or replace function eval()
RETURNS TABLE (enrich_d_dkj_p_k27ac text -- replace with actual column types
, enrich_lr_dkj_p_k27ac text
, enrich_r_dkj_p_k27ac text) AS
$func$
begin
RETURN QUERY EXECUTE
'select enrich_d_dkj_p_k27ac,enrich_lr_dkj_p_k27ac,enrich_r_dkj_p_k27ac
from dkj_p_k27ac';
end
$func$ language plpgsql;
Call:
SELECT * FROM eval();
You do not need the eval() function, just execute the statement directly:
select enrich_d_dkj_p_k27ac,enrich_lr_dkj_p_k27ac,enrich_r_dkj_p_k27ac
from dkj_p_k27ac;
The dynamic call cannot solve your underlying problem:
How to execute a string result of a stored procedure in postgres
Why does the use of the assignment operator := fail to parse in this stored procedure (fragment)? In the update statement, in the set median = [select expression], in the expression, the MySQL 5.6 parser reports the error, "[Check]...for the right syntax to use near ':= row + 1 as row, $vol as $vol from univ_members' ".
delimiter //
CREATE PROCEDURE m()
BEGIN
DECLARE row int;
SELECT row := row + 1 AS row;
END //
delimiter ;
Running the select statement the mysql shell also fails, but says, 'row' is not a system variable or 'row' is not a column, depending on whether I try to define it with 'set'.
Do you know of a limitation in a stored procedure that prohibits this, or of such a bug in MySQL 5.6? If so, is there a workaround? Can you suggest an alternative approach?
So, after struggling like a man blinded by darkness, I defined the variable #row in the shell using 'set' (the shell's parser does not allow 'row') and it worked. The parser however does not allow a variable defined in a stored procedure with 'declare' to begin with a '#', but, if defined with 'set', it works, it does allow it to be used as the left-hand value in the :=.
So, it's an issue with variable definition. Evidently, only 'user variables', which must begin with a '#' and must be defined with 'set', can be assigned values with ':='. (See User-Defined Variables)
I find such a nuance that all variables don't share a common behavior when it comes to assignment non-intuitive and incredibly irritating. Am I still missing something?
Here is what I have, I am trying to create an insert fn that loads row data essentially into a per-existing table. I also want to run a check on specific column data to make sure the source data is not invalid.
The problem I seem to be having is getting it to run successfully. For some reason I can't seem to get this to work and I have tried various ways and have researched diligently within the site(some that are close but, don't quite give me what I need). Here is basically what I have and want to achieve. I know it may be basic, so thanks in advance.
CREATE OR REPLACE FUNCTION Schema.insert_fn (arg_1 character varying , arg_2 integer)
RETURNS SETOF CHARACTER VARYING AS
$BODY$
BEGIN
--should this insert use some kind of temp table?
insert into <schema>.table1 (character varying, integer)
values (arg_1 character varying, arg_2 integer);
--If I wanted to run some sort of check on say arg_2
If(select distinct (arg_2) from <schema>.table2 where invalid_date is not null)
THEN
raise notice 'Data has been invalidated';
END IF;
Return 'complete';
END;
$BODY$
Update
First, it told me that my return need to have 'NEXT' or 'QUERY'
RETURN cannot have a parameter in function returning set;
use RETURN NEXT or RETURN QUERY at or near "'complete'"
Once I do this, of course the function will complete. However, when I call it I get an error saying for example:
invalid input syntax for type boolean: "arg_1"
I apologize if I come off a little vague. Obviously I can't give you the complete context of the arg names as they relate to what I am doing. I appreciate any help.
Update
I also receive this error:
more than one row returned by a subquery used as an expression
I did research on this issue as well and simply cannot relate any kind of solution to at least get this to work, meaning; when I call it say, with no arguments I receive this error.
Update #ErwinBrandstetter. I communicated that wrong. I meant if
'col2 = arg_2 and invalid_date is NOT null' to raise an exception
What is happening is that the 'EXIST' statement will take any instance to where a row is found. I tried 'WHERE EXIST' and I got an error. The problem I figure is that they(validated and invalidated data) share the same unique id whether and it makes the EXIST statement true(I didn't provide this info mind you).
Update
#ErwinBrandstetter It now operates successfully. Looks like all I needed to do was seperate the two conditions. Thanks.
IF EXISTS (condition)
THEN
INSERT
ELSEIF EXISTS (invalidated data condition)
THEN
RAISE EXCEPTION'DATA IS INVALIDATED';
END IF;
END;
Might work like this:
CREATE OR REPLACE FUNCTION schema.insert_fn (_arg1 text, _arg2 integer)
RETURNS text AS -- not SETOF!
$func$
BEGIN
INSERT INTO <schema>.table1 (col1, col2) -- names here! not types
SELECT _arg1, _arg2 -- again: names! cast only if needed
WHERE NOT EXISTS ( -- only if _arg2 not invalidated
SELECT 1
FROM <schema>.table2
WHERE col2 = _arg2
AND invalid_date IS NOT NULL
);
IF NOT FOUND THEN -- No exception yet, no INSERT either --> invalidated
RAISE EXCEPTION 'Data >>%<< has been invalidated.', _arg2;
END IF;
RETURN 'complete'::text; -- return value probably useless
END
$func$ LANGUAGE plpgsql
The most prominent error was that you declared the function to return a SETOF values, while you actually only return a single value. I might just use RETURNs void, since the return value does not carry information as is.
Read the manual here and here.
Use a SELECT with your INSERT to apply additional conditions directly.
There is more. See comments in code above.