Accessing parameters in a SQL stored Procedure in Snowflake - sql

I am trying to create a stored procedure that uses an integer parameter and dateadd() to create a timestamp that's used to filter results in a where clause. I am getting "Uncaught exception of type 'STATEMENT_ERROR' on line 3 at position 2 : SQL compilation error: error line 9 at position 53 invalid identifier 'X_DAYS_BACK'" when I try to run the procedure below. If I move the return line above the create table line the procedure successfully runs and the return string includes the actual value of x_days_back (e.g. base.ITEM updated for last 3 days.).
CREATE OR REPLACE PROCEDURE BASE.ITEM_LOAD_test(X_DAYS_BACK INTEGER)
returns string not null
language SQL
as
$$
BEGIN
CREATE OR REPLACE TEMPORARY TABLE BASE.TEMP_DELTA_ITEM (
ID STRING
)
AS
SELECT
SOURCE||KEY AS ID
FROM BASE.SUPPLIER_CATALOG_ITEM
WHERE
CDP__ETL_UPDATE_TIMESTAMP >= DATEADD(Day ,-1*X_DAYS_BACK, CURRENT_DATE);
drop TABLE BASE.TEMP_DELTA_ITEM;
RETURN 'base.ITEM updated for last ' || X_DAYS_BACK || ' days.' ;
END;
$$
How do I access a parameter of a SQL stored procedure within a function such as dateadd? I know it's a preview feature, so is this not currently implemented or?

Toss a colon in front of the variable/parameter:
DATEADD(day, -1*:X_DAYS_BACK, CURRENT_DATE);
Their documentation isn't amazing for SQL stored procedures but there is mention of this here where it states:
You can use a variable in a SQL statement. (This is sometimes referred to as binding a variable.) Prefix the variable name with a colon.

Related

PLS00215: String length constraints must be in range (1..32767)

I am new to pl/sql. I want to create a procedure that has three parameters called 'startMonth', 'endMonth', 'thirdMonth'. In the procedure, I am executing a sql query which is in 'run_sql' column in table_query. Values for 'startMonth', 'endMonth', 'thirdMonth' are needed to this query. This is how I wrote the procedure. My plan is to put all the sql queries in a separate table and execute in the for loop in the procedure. There I am creating a table called table1 and in the next month I want to drop it and create the table again. This is how I have written the procedure.
CREATE OR REPLACE procedure schema.sixMonthAverage (startMonth varchar,endMonth varchar ,thirdMonth varchar )
IS
start_date varchar := startMonth;
end_date varchar := endMonth;
begin
for c_rec in(select run_sql from table_query)
loop
dbms_output.put_line(startmonth);
dbms_output.put_line(endmonth);
execute immediate c_rec.run_sql using start_date, end_date;
Execute IMMEDIATE 'commit';
END LOOP;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line('Exception');
END;
This is the query in the run_sql column in table_query.
create table table1
as
select account_num,bill_seq,bill_version,
to_char(start_of_bill_dtm,''YYYYMM-DD'') st_bill_dtm,
to_char(bill_dtm - 1,''YYYYMM-DD'') en_bill_dtm,
to_char(actual_bill_dtm,''YYYYMM-DD'') act_bill_dtm,
round((invoice_net_mny + invoice_tax_mny)/1000,0) mon_bill,
bill_type_id,bill_status
from billsummary
where to_char(bill_dtm - 1,''YYYYMM'') between'||chr(32)||
startMonth ||chr(32)||'and'|| chr(32)||endMonth ||chr(32)||
'and cancellation_dtm is null;
But when I try to compile the procedure it gives me the error 'PLS00215: String length constraints must be in range (1..32767). Though I searched for the error I could not find the exact reason. It seems to be a problem in variable assigning. But I could not resolve it.
--Update
As it is given in the answer I converted the strings to dates.
CREATE OR REPLACE procedure REPO.sixMonthAverage (startMonth varchar2,endMonth varchar2 ,thirdMonth varchar2 )
IS
start_date date := TO_DATE(startMonth, 'yyyymm');
end_date date := TO_DATE(endMonth, 'yyyymm');
But when executing the query it gives the error message that ORA-00904: "END_DATE": invalid identifier. But it does not show any error message for the start_date and what would be the reason for this error message?
The error is pointing you to where the problem is. String declarations (char, varchar, varchar2 - but you should only be using varchar2, not varchar) need a length; so for example:
CREATE OR REPLACE procedure sixMonthAverage (startMonth varchar2,endMonth varchar2 ,thirdMonth varchar2 )
IS
start_date varchar2(10) := startMonth;
end_date varchar2(10) := endMonth;
...
Notice the procedure arguments do not specify a length; only the local variable declarations.
If those represent dates then they, and passed-in arguments, should probably be dates, not strings. It depends what your dynamic SQL is expecting though - if that is converting the strings to dates and specifying the format mask then I guess it's OK; otherwise you should be passed dates, or convert the strings to dates. The example you showed doesn't seem to have any bind variables to populate, though.
Dropping and recreating tables is generally not something you want to be doing though. You could delete/truncate and repopulate a table; or use partitioning if you want to keep more than one month; or use a view (or materialized view).

Postgresql 11 - Create Procedure to Execute COPY function

I'm currently trying to create a procedure to automatically copy data into my database when I call the procedure. However, every time I call it I get the following error:
ERROR: column "name" does not exist
LINE 1: SELECT format('COPY test(%L) FROM %s CSV HEADER', name, '/Us...
How does the column not exist? Here's everything I've written out:
CREATE PROCEDURE
test_insert() AS
$$
BEGIN
EXECUTE format('COPY test(%L) FROM %s CSV HEADER', name, '/Users/Receiving.csv');
END;
$$ LANGUAGE plpgsql;
If you use name without single quotes, it is interpreted as a column name in the (tacit) SELECT statement
SELECT format('...', name, '...')
that PL/pgSQL runs when you execute your function.
Since this SELECT statement does not have a FROM clause, you get the observed error.
The solution is to use a string literal instead, i.e. write 'name' instead of 'name'.

Save stored procedure output to new table without repeating table type

I want to call an existing procedure and store its table-typed OUT parameters to new physical tables, without having to repeat the definitions of the output types when creating the new tables. For example, if the procedure were
CREATE PROCEDURE MYPROC
(IN X INTEGER, OUT Y TABLE(A INTEGER, B DOUBLE, C NVARCHAR(25)))
LANGUAGE SQLSCRIPT AS BEGIN
...
END;
I would want to create a physical table for the output without repeating the (A INTEGER, B DOUBLE, C NVARCHAR(25)) part.
If I already had a table with the structure I want my result to have, I could CREATE TABLE MY_OUTPUT LIKE EXISTING_TABLE, but I don't.
If I already had a named type defined for the procedure's output type, I could create my table based on that type, but I don't.
If it were a subquery instead of a procedure output parameter, I could CREATE TABLE MY_OUTPUT AS (<subquery>), but it's not a subquery, and I don't know how to express it as a subquery. Also, there could be multiple output parameters, and I don't know how you'd make this work with multiple output parameters.
In my specific case, the functions come from the SAP HANA Predictive Analysis Library, so I don't have the option of changing how the functions are defined. Additionally, I suspect that PAL's unusually flexible handling of parameter types might prevent me from using solutions that would work for ordinary SQLScript procedures, but I'm still interested in solutions that would work for regular procedures, even if they fail on PAL.
Is there a way to do this?
It's possible, with limitations, to do this by using a SQLScript anonymous block:
DO BEGIN
CALL MYPROC(5, Y);
CREATE TABLE BLAH AS (SELECT * FROM :Y);
END;
We store the output to a table variable in the anonymous block, then create a physical table with data taken from the table variable. This even works with PAL! It's a lot of typing, though.
The limitation I've found is that the body of an anonymous block can't refer to local temporary tables created outside the anonymous block, so it's awkward to pass local temporary tables to the procedure this way. It's possible to do it anyway by passing the local temporary table as a parameter to the anonymous block itself, but that requires writing out the type of the local temporary table, and we were trying to avoid writing table types manually.
As far as I understand, you want to use your database tables as output parameter types.
In my default schema, I have a database table named CITY
I can create a stored procedure as follows using the table as output parameter type
CREATE PROCEDURE MyCityList (
OUT CITYLIST CITY
)
LANGUAGE SQLSCRIPT
AS
BEGIN
CITYLIST = SELECT * FROM CITY;
END;
After procedure is created, you can execute it as follows
do
begin
declare myList CITY;
call MyCityList(:myList);
select * from :myList;
end;
Here is the result where the output data is in a database table format, namely as CITY table
I hope this answers your question,
Update after first comment
If the scenario is the opposite as mentioned in the first comment, you can query system view PROCEDURE_PARAMETER_COLUMNS and create dynamic SQL statements that will generate tables with definitions in procedure table type parameters
Here is the SQL query
select
parameter_name,
'CREATE Column Table ' ||
procedure_name || '_'
|| parameter_name || ' ( ' ||
string_agg(
column_name || ' ' ||
data_type_name ||
case when data_type_name = 'INTEGER' then '' else
'(' || length || ')'
end
, ','
) || ' );'
from PROCEDURE_PARAMETER_COLUMNS
where
schema_name = 'A00077387'
group by procedure_name, parameter_name
You need to replace the WHERE clause according to your case.
Each line will have such an output
CREATE Column Table LISTCITIESBYCOUNTRYID_CITYLIST ( CITYID INTEGER,NAME NVARCHAR(40) );
The format for table name is concatenation of procedure name and parameter name
One last note, some data types integer, decimal, etc requires special code like excluding length or adding of scale , etc. Some are not handled in this SQL.
I'll try to enhance the query soon and publish an update

how to apply an aggregation function over a measure dynamically passed to a stored procedure?

I am trying to do spit by dimension on an analytic view within a stored procedure, and I want to pass the measure on which I will apply the aggregation function dynamically. So I did the following:
create procedure procHO (in currentMeasure varchar(60))
language sqlscript as
begin
data_tab = select MONTH_NAME as ID, sum(:currentMeasure) from
_SYS_BIC."schema/analyticView" GROUP BY MONTH_NAME;
end;
then I call the procedure this way:
call procHO("MARGIN");
but I am getting an error saying :
inconsistent datatype: only numeric type is available for aggregation function: line 5 col 38 (at pos 124) Could not execute 'call procHO("MARGIN")'
I also tried to do this using CE_ functions, here is what I did:
create procedure procHO1(in currentMeasure varchar(60))
language sqlscript as
begin
out1 = CE_OLAP_VIEW("schema/analyticView", ["MONTH_NAME",
SUM(:currentMeasure)]);
end;
and I call the procedure this way:
call procHO1("MARGIN");
but still, I am getting an error saying:
feature not supported: line 5 col 70 (at pos 157)
Could not execute 'call procHO1("MARGIN")'
by the way, as a workaround, it is possible to create a dynamic SQL query that would resolve the issue, here is an example:
create procedure procHO2(in currentMeasure varchar(60))
language sqlscript as
begin
exec 'select MONTH_NAME AS ID, sum('||:currentMeasure||') as SUM_MEASURE from
_SYS_BIC."schema/analyticView" GROUP BY MONTH_NAME';
end;
I call it this way
call procHO2('MARGIN');
but I don't want to create the SQL query dynamically since it's not recommended by SAP.
So what to do to pass an aggregated measure dynamically?
this is what the sample code from the documentation:
CREATE PROCEDURE my_proc_caller (IN in_client INT, IN in_currency INT, OUT outtab mytab_t) LANGUAGE SQLSCRIPT
BEGIN
outtab = SELECT * FROM CALC_VIEW (PLACEHOLDER."$$client$$" => :in_client , PLACEHOLDER."$$currency$$" => :in_currency );
END;
of course this works only on the latest release. Which revision are you running on?

Stored Procedure / Stored Function: SELECT in PostgreSQL

I have created a stored procedure / function with a simple SELECT in PostgreSQL:
CREATE FUNCTION select_proc2()
RETURNS SETOF procedure AS
$DELIMETER$
SELECT * FROM procedure;
$DELIMETER$
LANGUAGE 'sql'
This one works but when I tried to be specific like this:
CREATE FUNCTION select_proc2(INT)
RETURNS SETOF procedure AS
$DELIMETER$
SELECT "Fname" FROM procedure where "Id" = $1;
$DELIMETER$
LANGUAGE 'sql'
it returns an error:
ERROR: return type mismatch in function declared to return procedure
DETAIL: Final statement returns character instead of integer at
column 1. CONTEXT: SQL function "select_proc2"
I tried any solution that I can think of. Anyone here know how to solve this error?
You need to adapt the RETURN type to what's actually returned:
CREATE OR REPLACE FUNCTION select_proc2(int)
RETURNS SETOF text AS
$func$
SELECT "Fname" FROM procedure WHERE "Id" = $1;
$func$ LANGUAGE sql
In your second version you only return one column. From the name I am deriving the data type text, but that's just a guess.
If "Id" is the primary key column of procedure or otherwise defined UNIQUE, only one row can be returned and you can simplify to:
RETURNS text
Also don't quote the language name. sql is an identifier here, not a string literal. It's only tolerated for historic reason, but it's probably going to be an error in future versions.
Concerning your column names: My advise is to use non-quoted lower-case identifiers exclusively in Postgres. Start by reading the chapter "Identifiers and Key Words" to learn about the significance of "id", "Id", ID or id as column name.
remove quotes around field names (without "):
CREATE FUNCTION select_proc2(INT)
RETURNS SETOF procedure AS
$DELIMETER$
SELECT Fname FROM procedure where Id = $1;
$DELIMETER$
LANGUAGE 'sql'