Text equality (including null values) in Postgres plpgsql stored function - sql

I have a stored function which is supposed to compare three text values for equality. Some of these text values may be null, and if they are, then the comparison should return a false value.
CREATE OR REPLACE FUNCTION "subject_check_if_subjectName_exists"(name1IN text, name2IN text, name3IN text, name4IN text)
returns boolean as
$$
declare
results boolean;
subjectList record;
begin
results = false;
for subjectList in select name1, name2, name3, name4 from subject loop
if (name1In = subjectList.name1) and (name2In = subjectList.name2) and (name3In = subjectList.name3) and (name4In = subjectList.name4)
then
results = true;
EXIT; -- exit out of loop
end if;
end loop;
return results;
end;
$$ language 'plpgsql';
Of both name4IN and subjectList.name4 are null, and all the other values are equal, the function doesn't return a true value - which it should. How can I compare these text values even if they are null (null = null should return true)?

I think you want to use is not distinct from:
For non-null inputs, IS DISTINCT FROM is the same as the <> operator. However, if both inputs are null it returns false, and if only one input is null it returns true. Similarly, IS NOT DISTINCT FROM is identical to = for non-null inputs, but it returns true when both inputs are null, and false when only one input is null.
Essentially, A is not distinct from B is like A = B but it treats NULLs as "equal" (i.e. it behaves like most SQL newcomers think = should). For example, consider a simple function like this:
create function f(text,text) returns text as $$
begin
if $1 is distinct from $2 then
return '!=';
end if;
return '==';
end $$
language plpgsql;
That will give you results like this:
=> select f(null, null) as "1"
f(null, '') as "2",
f('', '') as "3",
f('pancakes','pancakes') as "4",
f('pancakes', null) as "5",
f('pancakes', 'house') as "6";
1 | 2 | 3 | 4 | 5 | 6
----+----+----+----+----+----
== | != | == | == | != | !=
So something like this is what you're looking for:
if (name1In is not distinct from subjectList.name1) and ...

Essentially, this is what I want to do. I figured there must be a quicker way to compare values including null ones. This does what I want it to do:
-- Function to check subject names (name1,2,3 and 4) already exists
CREATE OR REPLACE FUNCTION "subject_check_if_subjectName_exists"(name1IN text, name2IN text, name3IN text, name4IN text)
returns boolean as
$$
declare
results boolean;
subjectList record;
begin
results = false;
for subjectList in select name1, name2, name3, name4 from subject loop
if ((subjectList.name4 is null) and (name4IN is null)) then
if ((subjectList.name3 is null) and (name3IN is null)) then
if ((subjectList.name2 is null) and (name2IN is null)) then
if (name1IN = subjectList.name1) then
results = true;
EXIT;
end if;
else
if ((name1IN = subjectList.name1) and (name2IN = subjectList.name2)) then
results = true;
EXIT;
end if;
end if;
else
if ((name1IN = subjectList.name1) and (name2IN = subjectList.name2) and (name3IN = subjectList.name3)) then
results = true;
EXIT;
end if;
end if;
else
if ((name1IN = subjectList.name1) and (name2IN = subjectList.name2) and (name3IN = subjectList.name3) and (name4IN = subjectList.name4)) then
results = true;
EXIT;
end if;
end if;
end loop;
return results;
end;
$$ language 'plpgsql';
Will try IS NOT DISTINCT FROM now.
Thank you for your help. Much appreciated!

Use coalesce:
if (coalesce(name1In, '') = coalesce(subjectList.name1, '') ....

Related

Stored Procedure in Snowflake: Use parameter in the where clause

I have following stored procedure in Snowflake:
create or replace procedure test_procedure(parameter varchar)
returns number
language sql
as
$$
begin
if ((SELECT MONTHLY_DELIVERED_AMOUNT FROM test.process.msv_month_amount where TARGET_KEY = parameter) = 0)
then
return null;
else
return (SELECT monthly_target_amount from test.process.msv_month_amount where TARGET_KEY = parameter);
end if;
end;
$$
;
call test_procedure('Key10');
When I try to call the test_procedure it give me following error:
SQL compilation error: error line 1 at position 98 invalid identifier 'parameter'
How do i fix this?
You should prefix the variable name with a colon.
create or replace procedure test_procedure(parameter varchar)
returns number
language sql
as
$$
begin
if ((SELECT MONTHLY_DELIVERED_AMOUNT FROM msv_month_amount where TARGET_KEY = :parameter) = 0)
then
return null;
else
return (SELECT monthly_target_amount from msv_month_amount where TARGET_KEY = :parameter);
end if;
end;
$$
;
call test_procedure('Key10');
Documentation links on the usage are provided below
https://docs.snowflake.com/en/developer-guide/snowflake-scripting/variables.html#using-a-variable-in-a-sql-statement-binding
https://docs.snowflake.com/en/sql-reference/stored-procedures-snowflake-scripting.html#calling-a-stored-procedure-without-using-the-returned-value
User Parameter with single colon (:)
create or replace procedure test_procedure(parameter varchar)
returns number
language sql
as
$$
begin
if ((SELECT MONTHLY_DELIVERED_AMOUNT FROM test.process.msv_month_amount where TARGET_KEY = :parameter) = 0)
then
return null;
else
return (SELECT monthly_target_amount from test.process.msv_month_amount where TARGET_KEY = :parameter);
end if;
end;
$$
;
call test_procedure('Key10');
The code could be simplified to single query and CASE expression:
SELECT CASE WHEN MONTHLY_DELIVERED_AMOUNT = 0 THEN NULL
ELSE monthly_target_amount
END
FROM msv_month_amount
WHERE TARGET_KEY = :parameter

Failed to insert record and return id

I have a query that returns its id after adding
DO
$do$
BEGIN
IF (SELECT COUNT(*) = 0 FROM COMMENTS WHERE PARENT_ID = ''${this.parent_id}') THEN
UPDATE COMMENTS SET HAS_CHILD = TRUE WHERE COMMENT_ID = '${this.parent_id}';
END IF;
INSERT INTO COMMENTS(USER_ID, ... , PARENT_ID, ... , HAS_CHILD) VALUES('${this.user_id}', ... , ${this.parent_id}, ... ', FALSE)
RETURNING COMMENT_ID;
END
$do$
When executing the request, an error occurs: ERROR: query has no destination for result data
CONTEXT: PL/pgSQL function inline_code_block line 3 at SQL statement
SQL state: 42601. The request is successful without RETURNING COMMENT_ID;.
Why the request is not executed with RETURNING COMMENT_ID;?
In PL/pgSQL returning values have to be assigned to a variable with the keyword INTO, see:
https://www.postgresql.org/docs/9.4/plpgsql-statements.html#PLPGSQL-STATEMENTS-SQL-ONEROW
DO
$do$
DECLARE
result INTEGER := 0;
BEGIN
IF (SELECT COUNT(*) = 0 FROM COMMENTS WHERE PARENT_ID = '${this.parent_id}') THEN
UPDATE COMMENTS SET HAS_CHILD = TRUE WHERE COMMENT_ID = '${this.parent_id}';
-- Here you could specify returning value for the UPDATE branch...
END IF;
INSERT INTO COMMENTS(USER_ID, ... , PARENT_ID, ... , HAS_CHILD) VALUES('${this.user_id}', ... , ${this.parent_id}, ... ', FALSE)
RETURNING COMMENT_ID INTO result;
RETURN result;
END
$do$
Edit
Or you could use the RETURN QUERY syntax:
DO
$do$
BEGIN
IF (SELECT COUNT(*) = 0 FROM COMMENTS WHERE PARENT_ID = '${this.parent_id}') THEN
UPDATE COMMENTS SET HAS_CHILD = TRUE WHERE COMMENT_ID = '${this.parent_id}';
-- Here you could specify returning value for the UPDATE branch...
END IF;
RETURN QUERY
INSERT INTO COMMENTS(USER_ID, ... , PARENT_ID, ... , HAS_CHILD) VALUES('${this.user_id}', ... , ${this.parent_id}, ... ', FALSE)
RETURNING COMMENT_ID;
END
$do$

oracle sql filter for nullable values

Is that expected behavior for oracle 11g. Can someone explain why last query does not include null value?
table
statuscode
13
null
---------------------------------------------------------
select count(*) from table -- returns 2
select count(*) from table where statuscode = 13 --returns 1
select count(*) from table where statuscode <> 13 --returns 0
Think of NULL as an unknown value and testing if something equals (or does not equal) an unknown will result in an unknown (NULL) as the answer. The SQL query will display results when the boolean filter is TRUE and this will not be the case if one value is NULL.
You can test the logic in PL/SQL (since it has an accessible BOOLEAN type):
SET SERVEROUTPUT ON;
DECLARE
FUNCTION bool_to_string( bool BOOLEAN ) RETURN VARCHAR2
AS
BEGIN
RETURN CASE WHEN bool IS NULL THEN 'NULL'
WHEN bool = TRUE THEN 'TRUE'
WHEN bool = FALSE THEN 'FALSE'
ELSE 'ERROR' END;
END;
BEGIN
DBMS_OUTPUT.PUT_LINE( 'A = A => ' || bool_to_string( 'A' = 'A' ) );
DBMS_OUTPUT.PUT_LINE( 'A <> A => ' || bool_to_string( 'A' <> 'A' ) );
DBMS_OUTPUT.PUT_LINE( 'A = NULL => ' || bool_to_string( 'A' = NULL ) );
DBMS_OUTPUT.PUT_LINE( 'A <> NULL => ' || bool_to_string( 'A' <> NULL ) );
END;
/
Which outputs:
A = A => TRUE
A <> A => FALSE
A = NULL => NULL
A <> NULL => NULL
Note that the last two tests do not return FALSE but return NULL.
If you want to count including NULLs then you can do:
select count(*) from table where statuscode <> 13 OR statuscode IS NULL

Return value from case statement in plpgsql

How can I set a value (or just return a value directly) from a user defined function that uses a case statement?
create function is_bar(email varchar) returns boolean as
$$
declare returnValue boolean;
begin
select case when exists
(select * from (users join user_roles on users.userID = user_roles.userID)
where user_email=email and user_role='bar')
then (returnValue := TRUE);
else (returnValue := FALSE);
end;
return returnValue;
end;
$$ language plpgsql;
gives me:
ERROR: syntax error at or near ":="
LINE 8: then (returnValue := TRUE);
The reason of described issue is change of SQL (functional) CASE statement and PLpgSQL (procedural) CASE statement.
The SQL CASE (functional):
BEGIN
RETURN CASE WHEN EXISTS(..)
THEN true /* value */
ELSE false END; /* ended by END */
END;
The PLpgSQL (procedural) CASE:
BEGIN
CASE WHEN EXISTS(..)
THEN
RETURN true; /* statement */
ELSE
RETURN false;
END CASE; /* ended by END CASE */
END;
There are some other examples (same result):
a := CASE WHEN b < 10 THEN true ELSE false END;
a := b < 10;
CASE WHEN b < 10 THEN
a := true;
ELSE
a := false;
END CASE;
It would be much easier to return the result of the exists operator itself:
CREATE FUNCTION is_bar(email VARCHAR) RETURNS BOOLEAN AS
$$
BEGIN
RETURN EXISTS (SELECT *
FROM users
JOIN user_roles ON users.userID = user_roles.userID
WHERE user_email = email AND user_role='bar')
END;
$$ LANGUAGE plpgsql;

Oracle SQL - string not equal to

I have a trigger in Oracle SQL.
CREATE OR REPLACE TRIGGER test
BEFORE INSERT ON SomeTable
FOR EACH ROW
DECLARE str1 VARCHAR(30);
str2 VARCHAR(30);
BEGIN
-- some code
IF ( str1 <> str 2 ) THEN
DBMS_OUTPUT.PUT_LINE( ' if ' );
ELSE
DBMS_OUTPUT.PUT_LINE( ' else ' );
END IF;
END;
Now, this always goes to the else statement, even when the strings are definitely not equal. I tried to use != instead of <> with the same result. However, it works, in reverse, if I just use
IF ( str1 = str2 ) THEN ... ELSE ... END If;
So what is the right way to test for two strings not being equal to each other (in Oracle)?
Can you show us the actual values being used?
It is possible that the reason for the above behavior is becuase one of the values is null?
If it is possible for str1 and str2 to have null values, your if's should be like..
IF (str1 is null and str2 is null) then
<statments depending on whether you want to treat nulls as equal>
else if (
(str1 is null and str2 is not null) or
(str2 is null and str1 is not null) or
(str1 <> str2)) then
<statements when str1 and str2 are not equal>
else
<statements when str1 and str2 are equal?
end if;
This should determine if one character string exists inside another character string:
IF instr(str1,str2)<>0 THEN
Sometimes it is easier to negate the equality condition. E.g. if not equal(val1, val2);
function equals(
val1 varchar2,
val2 varchar2
) return boolean is
begin
if val1 is null then
return val2 is null;
elsif val2 is null then
return false;
end if;
return val1 = val2;
end;
And the in your code you would have:
BEGIN
-- some code
IF NOT equals(str1, str2) THEN
DBMS_OUTPUT.PUT_LINE( ' if ' );
ELSE
DBMS_OUTPUT.PUT_LINE( ' else ' );
END IF;
END;