How to use `SELECT COUNT(*)` as a conditional statement in PL/SQL? - sql

I have a stored procedure that updates records if they exist, or adds a new record if they don't exist.
SQL:
CREATE OR REPLACE PROCEDURE ADDRECORD(ihost VARCHAR, iip VARCHAR)
AS
rc VARCHAR(4000);
ROWCOUNT NUMBER;
BEGIN
rc := 'select count(0) from myTable where physical_host = ihost and primary_ip = iip';
ROWCOUNT := to_number(rc, '99');
IF ROWCOUNT = 1 THEN
UPSERTRECORD(ihost, iip);
ELSE
INSERT INTO myTable(PHYSICAL_HOST, PRIMARY_IP)
VALUES (ihost, iip);
INSERT INTO IP (IP, IP_IND) VALUES (iip, 'V');
END IF;
END ADDRECORD;
The UPSERTRECORD is another stored procedure that is being called. It works fine. In fact, the error is occurring on the line that contains the to_number. The error is :
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
Is there another way to do this? Sorry, I'm not super experienced with SQL, but I need to get this figured out.

Do
DECLARE rowcount As Number(38)
select count(*) INTO rowcount from myTable
where physical_host = ihost and primary_ip = iip

Related

Postgres SQL-TG_ARGV within query

I'm trying to do a trigger that will verify if a record exists in the table passed as argument. I'm doing it as a laravel migration but nervertheless the SQL should be perceptible
DB::unprepared('
CREATE OR REPLACE FUNCTION insert_or_update_lojas() RETURNS trigger AS $insert_or_update_lojas$
DECLARE
store TEXT := TG_ARGV[0]::TEXT;
BEGIN
-- Check if product exists
IF
EXISTS (SELECT 1 FROM TG_ARGV[0]::TEXT WHERE ean = NEW.ean)
THEN
UPDATE store SET price_form_factor=NEW.price_form_factor, price_unit=NEW.price_unit WHERE ean=NEW.ean;
RETURN NULL;
END IF;
RETURN NEW;
END;
$insert_or_update_lojas$ LANGUAGE plpgsql;');
DB::unprepared('
CREATE TRIGGER insert_or_update_continente BEFORE INSERT ON continente FOR EACH ROW EXECUTE PROCEDURE insert_or_update_lojas(\'continente\');');
Right now I'm getting
relation "store" does not exist LINE 1: SELECT EXISTS (SELECT 1 FROM store WHERE ean = NEW.ean) ^ QUERY: SELECT EXISTS (SELECT 1 FROM store WHERE ean = NEW.ean) CONTEXT: PL/pgSQL function insert_or_update_lojas() line 6 at IF
EDIT:
After googling for the past day i slightly changed the if statement. IF EXECUTE format(\'EXISTS SELECT 1 FROM %s WHERE ean = NEW.ean\', store) THEN EXECUTE (\'UPDATE store SET price_form_factor=NEW.price_form_factor, price_unit=NEW.price_unit WHERE ean=NEW.ean\'); RETURN NULL; END IF;
I know the format() works. However when I do EXECUTE format() I get the following error:
SQLSTATE[42601]: Syntax error: 7 ERROR: syntax error at or near "("
LINE 8:
EXECUTE format('EXISTS SELECT 1 FROM %s WHER...
^
(SQL:
CREATE OR REPLACE FUNCTION insert_or_update_lojas() RETURNS trigger AS $insert_or_update_lojas$
DECLARE
store TEXT := quote_ident(TG_ARGV[0]);
BEGIN
-- Check if product exists
IF
EXECUTE format('EXISTS SELECT 1 FROM %s WHERE ean = NEW.ean', store)
THEN
EXECUTE ('UPDATE store SET price_form_factor=NEW.price_form_factor, price_unit=NEW.price_unit WHERE ean=NEW.ean');
RETURN NULL;
END IF;

Unexpected OUT value from Postgres Stored Procedure

Using Postgres10.
I'm having an issue where I am calling a stored procedure and expecting a specific value for my OUT param and I am not getting it. I am calling the Items stored procedure with the call code below.
PROBLEM
I expect the first time I call the Item stored procedure to get an insert with a rtn value of 1 but I get a 4... This means the IF EXISTS is finding a row in the table with the same name but my table is empty.
I am expecting there is something weird going on where the IF EXISTS statement is being re-evaluated after the INSERT statement and entering the block where rtn gets set to 4. Is this something to do with plpgsql? It's acting as if the order of the stored procedure is not going top to bottom always when I put in Raise commands to test values at certain points.
SCHEMA/TABLE
CREATE TABLE aips.Item (
ItemPk SERIAL PRIMARY KEY,
Name VARCHAR(100) NOT NULL,
CONSTRAINT UNI_Item_Name UNIQUE(Name)
);
STORED PROCEDURE
CREATE OR REPLACE FUNCTION aips.Item(
INOUT p_ItemPk INT,
INOUT p_Name VARCHAR(100),
OUT rtn INT
) AS
$$
DECLARE rowcnt INT;
BEGIN
-- Insert or Find Path
IF p_ItemPk IS NULL THEN
-- Check for Find
IF EXISTS (SELECT * FROM aips.Item where Name = p_Name) THEN
SELECT ItemPk, Name
INTO p_ItemPk, p_Name
FROM aips.Item
WHERE Name = p_Name;
rtn := 4;
RETURN;
END IF;
-- Perform insert
INSERT INTO aips.Item (Name)
VALUES (p_Name)
RETURNING ItemPk INTO p_ItemPk;
GET DIAGNOSTICS rowcnt = ROW_COUNT;
IF rowcnt = 1 THEN
rtn := 1;
ELSE
rtn := 0;
RAISE EXCEPTION 'Expecting to insert a single row and rows returned --> %', rowcnt;
END IF;
ELSE -- Update or No Operation Path
-- Check for no changes
IF EXISTS (SELECT ItemPk
FROM aips.Item
WHERE ItemPk = p_ItemPk
AND Name = p_Name) THEN
rtn := 5;
RETURN;
END IF;
-- Perform Update
UPDATE aips.Item
SET Name = p_Name
WHERE ItemPk = p_ItemPk;
GET DIAGNOSTICS rowcnt = ROW_COUNT;
IF rowcnt = 1 THEN
rtn := 2;
ELSE
rtn := 0;
RAISE EXCEPTION 'Expecting to update a single row and rows returned --> %', rowcnt;
END IF;
END IF;
RETURN;
END;
$$ LANGUAGE plpgsql;
CALL
select (aips.Item(NULL, 'Test 1')).*;
The problem is the way you call the function:
select (aips.Item(NULL, 'Test 1')).*; -- WRONG!
because it is executed three times, once for each output column. The function should be called in the FROM clause:
select * from aips.Item(NULL, 'Test 1');

PLS-00103: Encountered the symbol ";" when expecting one of the following: <an identifier> <a d

I am trying to change the value of "TOTAL_TM_R_PT" where all the times a certain player shows up.
create or replace PROCEDURE CalculatePlayerTMPoints(Player IN NUMBER,PointNum IN NUMBER)
DECLARE PtValue INT;
UserNM TABLE;
BEGIN
SELECT POINT_VALUE INTO PtValue FROM POINTS WHERE POINTS.POINT_ID = PointNum;
SELECT ACCT_USERNAME INTO UserNM FROM ROSTERS WHERE ROSTERS.PLAYER_ID = Player;
for i in 1..UserNM.count
loop
UPDATE TEAM_MANAGER_POINTS
SET TOTAL_TM_R_PT = TOTAL_TM_R_PT + PtValue
WHERE TEAM_MANAGER_POINTS.ACCT_USERNAME = UserNM;
End loop;
END;
This is my error:
Error(3,1): PLS-00103: Encountered the symbol "DECLARE" when expecting one of
the following: ; is with authid as cluster order using external
deterministic parallel_enable pipelined result_cache accessible
Any ideas?
You need to define a collection type and use BULK COLLECT INTO for storing query results in a collection. AS keyword was also missing in your code and using Declare is not valid.
create or replace PROCEDURE CalculatePlayerTMPoints(Player IN NUMBER,PointNum IN NUMBER) AS
PtValue INT;
TYPE usertype IS TABLE OF ROSTERS.ACCT_USERNAME%TYPE;
UserNM usertype ;
BEGIN
SELECT POINT_VALUE INTO PtValue FROM POINTS WHERE POINTS.POINT_ID = PointNum;
SELECT ACCT_USERNAME BULK COLLECT INTO UserNM FROM ROSTERS WHERE ROSTERS.PLAYER_ID = Player;
for i in 1..UserNM.count
loop
UPDATE TEAM_MANAGER_POINTS
SET TOTAL_TM_R_PT = TOTAL_TM_R_PT + PtValue
WHERE ACCT_USERNAME = UserNM(i);
End loop;
END;

How to check If column exists and table row count togather before running the query in PL/SQL?

I need to execute one query, Before executing the query I need to check the following two conditions,
1) Table row count is 0
2) One of table column exists.
So basing on my requirement I wrote the procedure as follows,
DECLARE
rowCount INT;
column_not_exists exception;
pragma exception_init (column_not_exists , -00904);
BEGIN
SELECT count(*) INTO rowCount FROM Settings;
IF (rowCount = 0) THEN
Insert into Settings (ID,PROCESSID,AREA) select ID,PROCESSID,AREA from POINT WHERE CLASSNAME = 'ENDPOINT' and PROCESSID IS NOT NULL;
END IF;
exception WHEN column_not_exists THEN NULL;
END;
/
But somehow the exception is not handled and still I can see the error message in the logs
PL/SQL: ORA-00904: "PROCESSID": invalid identifier
ORA-06550: line 8, column 1:
PL/SQL: SQL Statement ignored
Can somebody help me here to validate both the conditions togather.
You had better use system view ALL_TAB_COLUMNS (of course, you need to have priveleges to access it). Also you need to use Oracle dynamic SQL statement "EXECUTE IMMEDIATE", otherwise your code will not compile.
I've altered you code here, please replace accordingly these places:
lower(t.OWNER)='owner',
lower(t.TABLE_NAME)='point'
code:
declare
rowcount int;
l_cnt number;
begin
select count(*) into rowcount from settings;
if (rowcount = 0) then
select count(*) into l_cnt from ALL_TAB_COLUMNS t
where lower(t.OWNER)='owner' and lower(t.TABLE_NAME)='point'
and lower(t.COLUMN_NAME) in ('processid','area');
if l_cnt = 2 then
execute immediate 'insert into settings
(id, processid, area)
select id, processid, area
from point
where classname = ''ENDPOINT''
and processid is not null';
end if;
end if;
end;

HANA - Passing string variable into WHERE IN() clause in SQL script

Lets suppose I have some SQL script in a scripted calculation view that takes a single value input parameter and generates a string of multiple inputs for an input parameter in another calculation view.
BEGIN
declare paramStr clob;
params = select foo
from bar
where bar.id = :IP_ID;
select '''' || string_agg(foo, ''', ''') || ''''
into paramStr
from :params;
var_out = select *
from "_SYS_BIC"."somepackage/MULTIPLE_IP_VIEW"(PLACEHOLDER."$$IP_IDS$$" => :paramStr);
END
This works as expected. However, if I change the var_out query and try to use the variable in a where clause
BEGIN
...
var_out = select *
from "_SYS_BIC"."somepackage/MULTIPLE_IP_VIEW"
where "IP_IDS" in(:paramStr);
END
the view will activate, but I get no results from the query. No runtime errors, just an empty result set. When I manually pass in the values to the WHERE IN() clause, everything works fine. It seems like an elementary problem to have, but I can't seem to get it to work. I have even tried using char(39) rather than '''' in my concatenation expression, but no banana :(
Ok, so what you are doing here is trying to make the statement dynamic.
For the IN condition, you seem to hope that once you have filled paramStr it would be handled as a set of parameters.
That's not the case at all.
Let's go with your example from the comment: paramStr = ' 'ip1','ip2' '
What happens, when the paramStr gets filled into your code is this:
var_out = select *
from "_SYS_BIC"."somepackage/MULTIPLE_IP_VIEW"
where "IP_IDS" in(' ''ip1'',''ip2'' ');
So, instead of looking for records that match IP_DS = 'ip1' or IP_DS = 'ip2' you are literally looking for records that match IP_DS = ' 'ip1','ip2' '.
One way to work around this is to use the APPLY_FILTER() function.
var_out = select *
from "_SYS_BIC"."somepackage/MULTIPLE_IP_VIEW";
filterStr = ' "IP_IDS" in (''ip1'',''ip2'') ';
var_out_filt = APPLY_FILTER(:var_out, :filterStr) ;
I've written about that some time ago: "On multiple mistakes with IN conditions".
Also, have a look at the documentation for APPLY_FILTER.
The SAP note “2315085 – Query with Multi-Value Parameter on Scripted Calculation View Fails with Incorrect Syntax Error“ actually showcases the APPLY_FILTER() approach that failed when I first tried it.
It also presents an UDF_IN_LIST function to convert a long varchar string with array of items from input parameter to a usable in-list predicate.
Unfortunately, couldn't make it work in sps11 rev 111.03 despite some parameter available (SAP Note 2457876 :to convert error into warning for string length overflow) - (range 3) string is too long exception
then ALTER SYSTEM ALTER CONFIGURATION ('indexserver.ini', 'System') set ('sqlscript', 'typecheck_procedure_input_param') = 'false' WITH RECONFIGURE;
-recompile the Calc View
then
select * from :var_tempout where OBJECT_ID in (select I_LIST from "BWOBJDES"."CROSS_AREA::UDF_INLIST_P"(:in_objectids,','));
invalid number exception - invalid number
But taking the scripting out of the function makes it work...
For this SPS 11 level APPLY_FILTER seems to be the only workaround. And it is really hard to say what would be the rev on SPS 12 to go in order to have it.
FUNCTION "BWOBJDES"."CROSS_AREA::UDF_INLIST_P"(str_input nvarchar(5000),
delimiter nvarchar(10))
RETURNS table ( I_LIST INTEGER ) LANGUAGE SQLSCRIPT SQL SECURITY INVOKER AS
/********* Begin Function Script ************/
BEGIN
DECLARE cnt int;
DECLARE temp_input nvarchar(128);
DECLARE slice NVARCHAR(10) ARRAY;
temp_input := :str_input;
cnt := 1;
WHILE length(temp_input) > 0 DO
if instr(temp_input, delimiter) > 0 then
slice[:cnt] := substr_before(temp_input,delimiter);
temp_input := substr_after(temp_input,delimiter);
cnt := :cnt + 1;
else
slice[:cnt] := temp_input;
break;
end if;
END WHILE;
tab2 = UNNEST(:slice) AS (I_LIST);
return select I_LIST from :tab2;
END;
CREATE PROCEDURE "MY_SCRIPTED_CV/proc"( IN numbers NVARCHAR(5000), OUT
var_out
MY_TABLE_TYPE ) language sqlscript sql security definer reads sql data with
result view
"MY_SCRIPTED_CV" as
/********* Begin Procedure Script ************/
BEGIN
-- not working
--var_out = select * from MY_TABLE where NUMBER in (select I_LIST from
--UDF_INLIST_P(:numbers,','));
-- working
DECLARE cnt int;
DECLARE temp_input nvarchar(128);
DECLARE slice NVARCHAR(13) ARRAY;
DECLARE delimiter VARCHAR := ',';
temp_input := replace(:numbers, char(39), '');
cnt := 1;
WHILE length(temp_input) > 0 DO
if instr(temp_input, delimiter) > 0 then
slice[:cnt] := substr_before(temp_input,delimiter);
temp_input := substr_after(temp_input,delimiter);
cnt := :cnt + 1;
else
slice[:cnt] := temp_input;
break;
end if;
END WHILE;
l_numbers = UNNEST(:slice) AS (NUMBER);
var_out=
SELECT *
FROM MAIN AS MA
INNER JOIN l_numbers as LN
ON MAIN.NUMBER = LN.NUMBER
END;
I know, this is a quite old thread but nevertheless my findings based what I read in here from Jenova might be interesting for others. Thatswhy I am writing them down.
I was faced with the same problem. Users can sent multiple entrie in an input parameter in a calc view I have to optimize. Unluckily I run into the same problem like Jenova and others before. And, no, imho this is not about dynamic view execution or dynamic sql, but about processing in lists in a clean way in a scripted calc view or table function if they are send in an input parameter.
Lars' proposal to use APPLY_FILTER is not applicable in my case, since I cannot use a complex statement in the APPLY_FILTER function itself. I have to materialize the whole amount of data in the select, which result I can assign to APPLY_FILTER and then execute the filtering. I want to see the filtering applied in the first step not after materializing data which is not appearing in the result of the filter application.
So I used a hdbtablefunction to create a function performing the parameter string cleanup and the transformation into an array and afterwards it is returning the result of the UNNEST function.
Since the result of the function is a table it can be used as a table inside the scripted view or tablefunction - in joins, subselects and so on.
This way I am able to process user input lists as expected, fast and with much less resource consumption.
FUNCTION "_SYS_BIC"."package1::transparam" (ip_string NVARCHAR(500) )
RETURNS table ( "PARAMETER" nvarchar(100))
LANGUAGE SQLSCRIPT
SQL SECURITY INVOKER AS
v_test varchar(1000);
IP_DELIMITER VARCHAR(1) := ',';
v_out VARCHAR(100):='';
v_count INTEGER:=1;
v_substr VARCHAR(1000):='';
v_substr2 VARCHAR(1000):='';
id INTEGER array;
val VARCHAR(100) array;
BEGIN
--
v_substr:=:ip_string;
v_substr := REPLACE(:v_substr, '''', '');
v_substr := REPLACE(:v_substr, ' ', '');
while(LOCATE (:v_substr, :ip_delimiter) > 0 ) do
-- find value
v_out := SUBSTR(v_substr, 0, LOCATE (:v_substr, :ip_delimiter) - 1 );
-- out to output
val[v_count]:=v_out;
-- increment counter
v_count:=:v_count+1;
-- new substring for search
v_substr2 := SUBSTR(:v_substr, LOCATE (:v_substr, :ip_delimiter) + 1, LENGTH(:v_substr));
v_substr := v_substr2;
END while;
IF(LOCATE (:v_substr, :ip_delimiter) = 0 AND LENGTH(:v_substr) > 0) THEN
-- no delimiter in string
val[v_count]:=v_substr;
END IF;
-- format output as tables
rst = unnest(:VAL) AS ("PARAMETER");
RETURN SELECT * FROM :rst;
END;
can be called like
select * from "package1.transparam"('''BLU'',''BLA''')
returning table with two lines
PARAMETER
---------
BLU
BLA
The most comprehensive explanation is here:
https://blogs.sap.com/2019/01/17/passing-multi-value-input-parameter-from-calculation-view-to-table-function-in-sap-hana-step-by-step-guide/
Creating custom function splitting string into multiple values
Then inner/left outer join can be used to filter.