I have a procedure which accepts the table name and the 3 fields that need filling. This is to be called from another procedure that loops through another table deciding which sub-table to put stuff into. The important bit is a simple insert statement, e.g.
insert into table1 values
('blah','String','50');
So that 4 parameters coming in (table1, and the 3 values). How would I do this using dollar quoting? Obviously this doesnt work but gives you an idea of what I'm trying to accomplish:
create or replace procedure
insert_dc_table(p_tblname varchar,
p_name varchar,
p_datatype varchar,
p_datalen varchar)
as $$
begin
execute
'insert into '||p_tblname||'(name,datatype,datalen) values '
||'('
||p_name||', '
||p_datatype||', '
||p_datalen
||')';
end;
$$ language plpgsql;
I'd need double-dollars around some, but am unsure of exactly where $$ and quotes go in all this !*&#!
I could declare a variable to hold the execute statement and do:
declare a _output varchar(200);
a_output := $$ insert into $$||p_tblname||$$(name,datatype,datalen) values ( '$$||p_name||$$',
well, i get lost there!
Thanks in advance for help!
Redshift Stored Procedures only require dollar quoting of the procedure body. Quotes inside the the procedure body are interpreted as normal.
You may find that your SQL client does not submit the SP create correctly due to the dollar quotes. If so I recommend using psql to create the stored procedure.
Sample stored procedures are available in our "Amazon Redshift Utils" GitHub repository.
Here's a modification of your example:
-- DROP PROCEDURE insert_dc_table(VARCHAR,VARCHAR ,VARCHAR ,VARCHAR);
CREATE OR REPLACE PROCEDURE insert_dc_table(
p_tblname VARCHAR, p_name VARCHAR, p_datatype VARCHAR, p_datalen VARCHAR )
AS $$
DECLARE
rows INTEGER;
BEGIN
sql := 'INSERT INTO '||p_tblname||' (name, datatype, datalen)'
||' VALUES ('||p_name||','||p_datatype||','||p_datalen||');';
RAISE INFO 'Running SQL: %', sql;
EXECUTE sql;
GET DIAGNOSTICS rows := ROW_COUNT;
RAISE INFO 'Rows inserted = %', rows;
END
$$ LANGUAGE plpgsql;
-- CALL insert_dc_table ('test_table', 'name', 'type', 'length');
Related
I am actually trying to find a solution for my issue. The problem is this one :
A function generate a string, this string is a SQL request, and I want to use snowflake to "read" and execute this SQL request.
Do you have a solution for this kind of problem please ?
I still continue to try to find a solution if I find it I will put it here.
Here is my problem with more information about it.
create or replace function var_test(arg1 varchar)
returns varchar as
$$
'CREATE OR REPLACE TABLE ENV_EUT.EUT.TABLE_TEST_ALEXIS_' || arg1 || '(a varchar);'
$$
;
SELECT var_test('3') AS num_table;
With this request, i get back a table with 1 column and a value in this column :
CREATE OR REPLACE TABLE ENV_EUT.EUT.TABLE_TEST_ALEXIS_3(a varchar);
My problem now is I don't succeed to execute the string in this table. Do you see a way to do this please ? Best regards
Thank you all
Check out Snowflake Scripting.
https://docs.snowflake.com/en/developer-guide/snowflake-scripting/index.html
You can declare a statement as a variable and execute it.
See also: execute immediate
https://docs.snowflake.com/en/sql-reference/sql/execute-immediate.html
-- very simple sproc
create or replace procedure myprocedure(arg1 string)
returns varchar
language sql
as
$$
-- declare variables
declare
smt string;
begin
-- construct statement
smt := 'CREATE OR REPLACE TABLE TEST_ALEXIS_' || arg1 || ' (a varchar)';
-- execute statement
execute immediate smt;
-- message to return on success
return 'Successfully executed statement: ' || smt;
-- message to return on exception
exception
when statement_error then
return object_construct('Error type', 'STATEMENT_ERROR',
'SQLCODE', sqlcode,
'SQLERRM', sqlerrm,
'SQLSTATE', sqlstate);
end;
$$
;
-- call sproc to create table
call myprocedure('TEST');
I keep getting the "SELECT query has no destination for result data" error upon calling this test procedure. What am I doing wrong? I did try adding the RETURN() command prior to SELECT statement but that didn't work either.
CREATE OR REPLACE PROCEDURE SchemaName.SP_Testing_Creating_Procedure (OUT ColumnName VARCHAR(9))
AS $$
BEGIN
SELECT TOP 10 ColumnName FROM SchemaName.TableName where ColumnName is not null;
END;
$$
LANGUAGE plpgsql;
CALL SchemaName.SP_Testing_Creating_Procedure();
As John mentioned you need to put the result into OUT column, examples of using IN, OUT and INOUT parameters you can find here
But if you need to return a few rows as a result, you have to use refcursor
as it's described here
CREATE OR REPLACE PROCEDURE SchemaName.SP_Testing_Creating_Procedure (INOUT result refcursor)
AS $$
BEGIN
OPEN result FOR
SELECT TOP 10 ColumnName
FROM SchemaName.TableName
WHERE ColumnName IS NOT null;
END;
$$
LANGUAGE plpgsql;
Then you can call the stored procedure in a transaction
BEGIN;
CALL logs.SP_Testing_Creating_Procedure('mycursor');
FETCH ALL FROM mycursor;
COMMIT;
another option is temp table which is also described in the above doc
Your procedure is running a SELECT command, but it is not doing anything with the results.
If your intention was to return a result set, you will need to put data in the OUT column.
See: Returning a result set - Amazon Redshift
My application uses multiple schemas to partition tenants across the database to improve performance. I am trying to create a plpgsql function that will give me an arbitrary result set based on the union of all application schemas given a table. Here is what I have so far (inspired by this blog post):
CREATE OR REPLACE FUNCTION app_union(tbl text) RETURNS SETOF RECORD AS $$
DECLARE
schema RECORD;
sql TEXT := '';
BEGIN
FOR schema IN EXECUTE 'SELECT distinct schema FROM tenants' LOOP
sql := sql || format('SELECT * FROM %I.%I %s UNION ALL ', schema.schema, tbl);
END LOOP;
RETURN QUERY EXECUTE left(sql, -11);
END
$$ LANGUAGE plpgsql;
This works great, but has to be called with a row type definition at the end:
select * from app_union('my_table') t(id uuid, name text, ...);
So, how can I call my function without providing a row type?
I know that I can introspect my tables using information_schema.columns, but I'm not sure how to dynamically generate the type declaration without a lot of case statements (columns doesn't report the definition sql the way that e.g., pg_indexes does).
Even if I could dynamically generate the row declaration, it seems I would have to append it to my former function call as dynamic sql anyway, which sort of chicken/eggs the problem of returning a result set of an arbitrary type from a function.
Instead of providing the table as a string, you could provide it as type anyelement to specify the actual type of the returning data, then infer the table's name using pg_typeof. You can also use string_agg rather than a loop to build your sql:
CREATE OR REPLACE FUNCTION app_union(tbl anyelement)
RETURNS setof anyelement AS $$
BEGIN
return query execute string_agg(
distinct format('select * from %I.%I', schema, pg_typeof(tbl)::text),
' union all '
) from tenants;
END
$$ LANGUAGE plpgsql;
select * from app_union(null::my_table);
Simplified example
I have the following script which contains a function named 'myFunction'. (declaration of types named rowValueTmp and rowValueTable are also attached for your information) Basically, I need to use a table name as an input parameter for myFunction. I found that I need to use dynamic SQL in order to use the table name as a parameter (Please correct me if there are alternative ways to do this). So the following code is what I have tried so far.
create or replace type rowValueTmp as object (
month number,
year number
);
/
create or replace type rowValueTable as table of rowValueTmp;
/
create or replace FUNCTION myFunction (TABLENAME in VARCHAR2)
return rowValueTable as
v_ret rowValueTable;
begin
execute immediate '
select rowValueTmp(month, year)
bulk collect into v_ret
from '||TABLENAME;
return v_ret;
end myFunction;
/
select * from table(myFunction('SCHEMA.TEST'));
But, this code gives me an error, and I assumed that this error is occurred because of using 'bulk collect' in execute immediate block.
ORA-03001: unimplemented feature
If I replace the content of execute immediate as the following, the above script is working..
select rowValueTmp(month, year)
bulk collect into v_ret
from SCHEMA.TEST;
Question
1] Is there any way(rather than Dynamic SQL) that I can use a table name as an input parameter for myFunction?
2] If I am not allowed to use bulk collect in execute immediate block, what do you suggest?
You can return values from execute immediately into a bulk collect:
CREATE OR REPLACE FUNCTION myfunction (tablename IN VARCHAR2)
RETURN rowvaluetable AS
v_ret rowvaluetable;
v_table VARCHAR2 (61) := DBMS_ASSERT.sql_object_name (tablename);
BEGIN
EXECUTE IMMEDIATE '
select rowValueTmp(month, year)
from ' || v_table
BULK COLLECT INTO v_ret;
RETURN v_ret;
END myfunction;
/
In the interest of an abundance of caution, I'd recommend using DBMS_ASSERT to validate the table parameter as well (as shown).
I'm using vb.net and oracle db, and currently I have a stored-procedure that is called from my code. Right now it looks similar to this:
CREATE OR REPLACE PROCEDURE MYPROCEDURE(
param1 table.field1%TYPE,
param2 table.field2%TYPE,
param3 table.field3%TYPE,
param4 varchar2,
output OUT number) AS
BEGIN
DO STUFF
END;
I want to ask if it is possible to change this to send multiple sets of parameters at once, so I could use a FOR LOOP inside my procedure to minimize the number of calls. I want to achieve something like this:
CREATE OR REPLACE PROCEDURE MYPROCEDURE(
param myArray
output OUT number) AS
BEGIN
FOR i IN 1..myArray.COUNT LOOP
UPDATE FIELD FROM TABLE WHERE ID = myArray(i).field1;
END LOOP;
END;
Or if there's anything else that would work the same it would be great.
Many thanks.
Yes you can pass a list of objects as parameter in oracle procedure. First you must create the datatype of this list of objects, but you can't do this inside a procedure you have to define it as an oracle object. For example:
CREATE OR REPLACE TYPE TEST."MY_TYPE" AS OBJECT
(PARAM1 VARCHAR (20), PARAM2 NUMBER);
Unfortunately you can define dynamic datatypes inside objects (table.field1%TYPE), but I think you know what datatype this field have.
Second, create a package that have the list of parameter and procedure definition like this:
CREATE OR REPLACE PACKAGE ARRAY_EXAMPLE2
AS
TYPE COL IS TABLE OF MY_TYPE;
PROCEDURE PROCESS_ARRAY (ArrayIn IN COL);
END;
And finally the package implementation
CREATE OR REPLACE PACKAGE BODY ARRAY_EXAMPLE2
AS
PROCEDURE PROCESS_ARRAY (ArrayIn IN COL)
IS
BEGIN
FOR i IN 1 .. ArrayIn.COUNT
LOOP
DBMS_OUTPUT.PUT_LINE ('Hello ' || ArrayIn (i).PARAM1);
END LOOP;
END;
END;
You can try it using this lines of code:
BEGIN
ARRAY_EXAMPLE2.
PROCESS_ARRAY (
array_example2.
COL (MY_TYPE ('Peter', 12),
MY_TYPE ('Jorge', 4),
MY_TYPE ('Bryan', 5)));
END;