Postgres IF on variable - postgresql

I'm in the process of learning postgres, I've already found a work around to this problem but I wanted to ask the community if something like this is even possible, maybe my syntax is just off.
DO $$ BEGIN
IF :MODIFYBY IS NOT NULL THEN
UPDATE User SET ModifyBy = :MODIFYBY WHERE UserId = :USERID;
UPDATE Profile SET ModifyBy = :MODIFYBY WHERE UserId = :USERID;
END IF;
END $$;
Receiving
syntax error at or near ":"
as :MODIFYBY is a parameter to this sql.
How can I test if a parameter is null?
Note: Running on PostgreSQL 9.6
Update:
It is possible my terminology is not correct. The full sql statement is this
BEGIN;
UPDATE User
SET Email = :EMAIL
,ModifyDate = now() at time zone 'utc'
WHERE
UserId = :USERID;
UPDATE Profile
SET FirstName = :FIRSTNAME
,LastName = :LASTNAME
,ModifyDate = now() at time zone 'utc'
WHERE
UserId = :USERID;
DO $$ BEGIN
IF :MODIFYBY IS NOT NULL THEN
UPDATE User SET ModifyBy = :MODIFYBY WHERE UserId = :USERID;
UPDATE Profile SET ModifyBy = :MODIFYBY WHERE UserId = :USERID;
END IF;
END $$;
COMMIT;
I added the DO $$ BEGIN and END $$; to get the IF statement to work...

I think your problem is with the use of : parameters in a function doesn't use it.
This is an example of a function using IF
CREATE OR REPLACE FUNCTION traffic.check_distance(
int_route_source_id bigint,
num_distance_geo numeric)
RETURNS boolean AS
$BODY$
DECLARE
bol_route_error boolean = false;
num_distance_rto numeric;
BEGIN
-- CALCULATE ROUTE DISTANCE
SELECT INTO num_distance_rto
....
--RAISE DISTANCE ALARM
IF num_distance_rto > 3.5 * num_distance_geo THEN
UPDATE traffic.Route_Sources
SET
IsValid = FALSE,
result = '3.5x MUY LARGO'
WHERE
route_source_id = int_route_source_id;
bol_route_error = true;
END IF;
RETURN bol_route_error;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION traffic.check_distance(bigint, numeric)
OWNER TO postgres;

Related

syntax error at or near ";" LINE 22 - Postgresql

I am creating a user defined function in PostgreSQL and facing error.
Also, please suggest a better way (if any) to execute this function.
CREATE FUNCTION public.mark_enrollment_completed(IN enrollment_id text)
RETURNS boolean
LANGUAGE plpgsql
AS $BODY$
DECLARE
total_contents INTEGER;
completed_content_count INTEGER;
user_id TEXT;
course_id TEXT;
was_marked BOOLEAN;
BEGIN
SELECT user_id, course_id INTO user_id, course_id FROM enrollments WHERE enrollment_id = enrollment_id;
SELECT count(*) INTO total_contents FROM course_contents WHERE course_id = course_id;
SELECT count(*) INTO completed_content_count FROM completed_contents WHERE user_id = user_id;
IF total_contents = completed_content_count THEN
UPDATE enrollments SET is_completed = true WHERE enrollment_id = enrollment_id;
SET was_marked = true;
ELSE
SET was_marked = false;
RETURN was_marked;
END;
$BODY$;
Error:
ERROR: syntax error at or near ";"
LINE 22: END;
^
SQL state: 42601
Character: 750
As documented in the manual assignment is done using := operator - there is no SET in PL/pgSQL.
You are also missing the END IF
IF total_contents = completed_content_count THEN
UPDATE enrollments SET is_completed = true WHERE enrollment_id = enrollment_id;
was_marked := true;
ELSE
was_marked := false;
END IF;
RETURN was_marked;

Lock row in function POSTGRESQL

I'm trying the simulating the "nextval" function, i need the next value generated based on specific id. In some cases, the function is returned the same value.
CREATE OR REPLACE FUNCTION public.nextvalue(character,integer)
RETURNS integer AS
$BODY$
DECLARE
p_id ALIAS FOR $1;
p_numero integer;
BEGIN
p_numero = (SELECT numero FROM "TnextValue" WHERE id = p_id FOR UPDATE);
IF p_numero is null THEN
INSERT INTO "TnextValue" (numero,id) VALUES (1,p_id);
p_numero = 1;
END IF;
UPDATE "TnextValue" SET numero = p_numero + 1 where id = p_id;
RETURN p_numero;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
I tried add the statement FOR UPDATE, but the problem persist. I thinking the add one line above the statament (SELECT) the line
LOCK TABLE "TnextValue" IN ROW EXCLUSIVE MODE;
But i think this line block the table for others id obtain the next value in same time.
Thanks!
IF p_numero is null THEN
INSERT INTO "TnextValue" (numero,id) VALUES (1,p_id);
p_numero = 1;
END IF;
not going to work there's no locking on that row if two callers want the same new sequence one of them will get an error.
IF p_numero is null THEN
BEGIN
INSERT INTO "TnextValue" (numero,id) VALUES (1,p_id);
p_numero = 1;
EXCEPTION WHEN UNIQUE_VIOLATION THEN
RETURN public.nextvalue($1,$2);
END;
END IF;

can't update a procedure in postgresql

I try to update a procedure in psql, the code gives no error:
CREATE OR REPLACE FUNCTION public.decrement_user_followers()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
UPDATE users SET unit_followers = unit_followers - 1 WHERE id = OLD.user_id_followed;
UPDATE users SET unit_following = unit_following - 1 WHERE id = OLD.user_id_follower;
RETURN OLD;
END $function$
But when I try to see the change \df+ decrement_user_followers gives me the old code.
Any suggestions?
The ";" was missing at the end ... ahhh
CREATE OR REPLACE FUNCTION public.decrement_user_followers()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
UPDATE users SET unit_followers = unit_followers - 1 WHERE id = OLD.user_id_followed;
UPDATE users SET unit_following = unit_following - 1 WHERE id = OLD.user_id_follower;
RETURN OLD;
END $function$;

SELECT column WHERE (type = 'S' OR type = 'B') but perform different actions depending on whether type = 'S' or 'B'

))Hi all, currently Im stuck in an issue, hope some good PostgreSQL fellow programmer could give me a hand with it. This is my table...
I only want to select one "time" row, either WHERE "time_type" = 'Start' OR "time_type" = 'Break', but only one, the one that is at the bottom row (descending) (ORDER BY "fn_serial" DESC LIMIT 1).
Im successfully doing it by using this Trigger Function...
CREATE OR REPLACE FUNCTION timediff()
RETURNS trigger AS
$BODY$
DECLARE
prevtime character varying;
BEGIN
SELECT t.time FROM table_ebscb_spa_log04 t WHERE t.fn_name = NEW.fn_name AND (t.time_type = 'Start' OR time_type = 'Break') ORDER BY t.fn_serial DESC LIMIT 1 INTO prevtime;
IF NOT FOUND THEN
RAISE EXCEPTION USING MESSAGE = 'NOT FOUNDED';
ELSE
NEW.time_elapse := prevtime
END IF;
return NEW;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION timediff()
OWNER TO postgres;
But in my script I would like to perform different actions depending on whether "fn_type" = 'Start' OR "fn_type = 'Break', I mean where "prevtime" variable came from, eg:
IF "prevtime" came from "fn_type" = 'Start' THEN
RAISE EXCEPTION USING MESSAGE = 'PREVTIME CAME FROM START';
ELSIF "prevtime" came from "fn_type" = 'BREAK' THEN
RAISE EXCEPTION USING MESSAGE = 'PREVTIME CAME FROM BREAK';
I can hardly imagine a way to do that, so I would like to ask for suggestions.
I guess one way to achieve this could be, create a sub IF, to check which one ('Start' OR 'Break') is at the bottom row (descending). How could I do that? or what could be a better approach?
Thanks Advanced.
Use more than one variable.
CREATE OR REPLACE FUNCTION timediff()
RETURNS trigger AS
$BODY$
DECLARE
prevtime character varying;
time_type character varying;
BEGIN
SELECT t.time, t.time_type FROM table_ebscb_spa_log04 t WHERE t.fn_name = NEW.fn_name AND (t.time_type = 'Start' OR time_type = 'Break') ORDER BY t.fn_serial DESC LIMIT 1 INTO prevtime,time_type;
IF NOT FOUND THEN
RAISE EXCEPTION USING MESSAGE = 'NOT FOUND';
ELSE
NEW.time_elapse := prevtime;
RAISE NOTICE "THE TIME CAME FROM %", time_type;
END IF;
return NEW;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION timediff()
OWNER TO postgres;

Select and use one column

Please consider the following (working) stored procedure. This function receives an integer as its first parameter, indicating which privilegeid has to be checked against the current user. Privileges are stored in the table privileges and consist of an id and a name (varchar).
Each privilege belongs to one or more roles stored in users_roles. Each user is assigned to one or more roles. This function retrieves all roles assigned to current_user and checks them, like said, against the given priviligeid.
CREATE OR REPLACE FUNCTION "public"."has_privilege" (in int4) RETURNS bool AS
$BODY$
DECLARE
asked_privilegeid ALIAS FOR $1;
userid int;
role_row users_roles%rowtype;
privilege_row privileges%rowtype;
BEGIN
EXECUTE 'SELECT userid FROM users WHERE username=$1' INTO userid USING current_user;
FOR role_row IN SELECT * FROM users_roles
WHERE userid=userid
LOOP
IF role_row.roleid = 1 THEN
return TRUE;
END IF;
FOR privilege_row IN SELECT * FROM privileges WHERE roleid=role_row.roleid LOOP
IF privilege_row.privilegeid = asked_privilegeid THEN
return TRUE;
END IF;
END LOOP;
END LOOP;
return FALSE;
END
$BODY$
LANGUAGE 'plpgsql'
However, this code isn't a efficient as it could be considering it retrieves all rowvalues for users_roles and privileges. I tried to write the procedure as following, but it doesn't seem to work:
CREATE OR REPLACE FUNCTION "public"."has_privilege" (in int4) RETURNS bool AS
$BODY$
DECLARE
asked_privilegeid ALIAS FOR $1;
privilegeid int;
userid int;
roleid int;
//role_row users_roles%rowtype;
//privilege_row privileges%rowtype;
BEGIN
EXECUTE 'SELECT userid FROM users WHERE username=$1' INTO userid USING current_user;
FOR roleid IN SELECT roleid FROM users_roles
WHERE userid=userid
LOOP
IF roleid = 1 THEN
return TRUE;
END IF;
FOR privilegeid IN SELECT privilegeid FROM privileges WHERE roleid=roleid LOOP
IF privilegeid = asked_privilegeid THEN
return TRUE;
END IF;
END LOOP;
END LOOP;
return FALSE;
END
$BODY$
LANGUAGE 'plpgsql'
What am I doing wrong? Thanks in advance!
Edit: The indentions didn't came through as expected. Here are pastebin links:
http://pastebin.com/w18WaCW0
http://pastebin.com/W8ewXxEe
The problem is with the row
WHERE userid=userid
It is ambiguous which is the column from the table and which is your variable. Same issue here
FROM privileges WHERE roleid=roleid
Don't use variable names that are also column names that you will reference
You can also rewrite your PROC body as a direct SQL statement that will probably work faster
CREATE OR REPLACE FUNCTION "public"."has_privilege" (in int4) RETURNS bool AS
$BODY$
DECLARE
asked_privilegeid ALIAS FOR $1;
BEGIN
RETURN EXISTS (
SELECT *
FROM users u
INNER JOIN users_roles r on r.userid=u.userid
LEFT JOIN privileges p on p.roleid=r.roleid
AND p.privilegeid = asked_privilegeid
AND r.roleid <> 1 // don't need to process this join if we already have our answer
WHERE u.username = $1
AND (r.roleid=1 OR p.privilegeid is not null))
END
$BODY$
LANGUAGE 'plpgsql'
DECLARE
asked_privilegeid ALIAS FOR $1;
_userid int; -- chage to avoid variable name same as column name
role_row users_roles%rowtype;
privilege_row privileges%rowtype;
BEGIN
_userid := (select userid from users where username = session_user); -- if u use current_user u will have problem when function is defined as security definer
FOR role_row IN SELECT * FROM users_roles WHERE userid = _userid -- your code is error becus userid is same as your variable name
LOOP
IF role_row.roleid = 1 THEN
return TRUE;
END IF;
FOR privilege_row IN SELECT * FROM privileges WHERE roleid=role_row.roleid LOOP
IF privilege_row.privilegeid = asked_privilegeid THEN
return TRUE;
END IF;
END LOOP;
END LOOP;
return FALSE;
END
when u declare variable in postgre, it is good practice that u use underscore b4 variable name '_userid'. to make it distinct from column name
I think this can be solved with just a single SELECT statement:
SELECT count(*)
FROM privileges p
JOIN roles r ON r.privilegeid = p.privilegeid
JOIN user_roles ur ON ur.roleid = r.roleid
JOIN users u ON u.userid = ur.userid AND u.username = session_user
WHERE p.privilegeid = asked_privilegeid
(not tested)
If the count is zero, the privilege is not assigned, otherwise it is.