I try to execute a stored procedure and I have the following issue.
I don't know if I have a problem with the format of the string.
This is my error message:
ERROR: unexpected error 12 in EXECUTE of query 'MERGE INTO GTN..ON_ORDER_REPORT_TSS_TEST a
USING QUANTISENSE_PROD_STAGE..VW_CURRENT_VIEW_OF_LINE_SHEET_HIST b
ON TRIM(a.channel) = TRIM(upper(b.channel_desc))
and TRIM(a.style_colour) = TRIM(b.style_opt_numb)
and TRIM(a.season_code) = TRIM(b.season_cd)
and a.STYLE_COLOUR = 'IG1501S-034121'
WHEN MATCHED THEN
UPDATE SET a.REVISED_IN_STORE_DT = b.REVISED_IN_STORE_DT || ' 00:00:00',
a.PLANNED_IN_STORE_DT = b.PLANNED_IN_STORE_DT || ' 00:00:00';'
This is the code of my stored procedure:
CREATE OR REPLACE PROCEDURE USP_UPDATE_LINE_SHEET_TEST(CHARACTER VARYING(ANY))
RETURNS CHARACTER VARYING(ANY)
LANGUAGE NZPLSQL AS
BEGIN_PROC
DECLARE
TABLENAME ALIAS FOR $1;
SQL TEXT;
SQLFULL varchar(4000);
BEGIN
SQL := 'MERGE INTO GTN..'||TABLENAME||' a
USING QUANTISENSE_PROD_STAGE..VW_CURRENT_VIEW_OF_LINE_SHEET_HIST b
ON TRIM(a.channel) = TRIM(upper(b.channel_desc))
and TRIM(a.style_colour) = TRIM(b.style_opt_numb)
and TRIM(a.season_code) = TRIM(b.season_cd)
and a.STYLE_COLOUR = ''IG1501S-034121''
WHEN MATCHED THEN
UPDATE SET a.REVISED_IN_STORE_DT = b.REVISED_IN_STORE_DT || '' 00:00:00'',
a.PLANNED_IN_STORE_DT = b.PLANNED_IN_STORE_DT || '' 00:00:00'';';
EXECUTE IMMEDIATE SQL;
END;
END_PROC;
What can I do to fix this error?
Related
I'm trying to come up with a SP that creates a certain task, appended by year, for a generic approach.
I can create the tasks outside, alone, with the $$ marks,
but I can't do it inside JS SP like this:
CREATE OR REPLACE PROCEDURE create_exec_tasks_by_year_range()
RETURNS varchar
LANGUAGE JAVASCRIPT
EXECUTE AS OWNER
AS
$$
var return_value = "";
var range_years = Array.from(Array(new Date().getUTCFullYear() - 2006), (_, i) => (i + 2007).toString());
//CREATE a task TO CALL SP BY YEAR, FROM 2007-current year
range_years.forEach((year_elem) => {
rs = snowflake.createStatement( {
sqlText: `CREATE OR REPLACE TASK MY_TSK_YEAR_`+year_elem+`
SCHEDULE = 'USING CRON 30 22 * * SUN UTC'
AS
EXECUTE IMMEDIATE
$$
DECLARE
year_track float;
rs resultset;
BEGIN
year_track := :1;
rs := (execute IMMEDIATE 'INSERT INTO MY_TABLE VALUES(?)' using (year_track));
return TABLE(rs);
END;
$$
;`
, binds: [year_elem] }).execute();
rs.next();
//rs.getColumnValue(1);
return_value += "MY_TSK_YEAR_"+year_elem+", ";
to_exec = snowflake.createStatement( {
sqlText: `EXECUTE TASK MY_TSK_YEAR_`+year_elem+`
to_exec.next();
return_value += to_exec.getColumnValue(1)+", ";
});
return return_value;
$$;
because it throws me
syntax error line ...at position 2 unexpected 'DECLARE'
while creating the TASK manually, works, because I don't have a conflict between $$?
CREATE OR REPLACE TASK SHARED.SRC_EXT_WEATHER.TSK_DUMMY
SCHEDULE = 'USING CRON 30 22 * * SUN UTC'
AS
EXECUTE IMMEDIATE
$$
DECLARE
year_track float;
rs resultset;
BEGIN
year_track := 2007;
rs := (execute IMMEDIATE 'INSERT INTO MY_TABLE VALUES(?)' using (year_track));
return TABLE(rs);
END;
$$;
Is it possible to make the SP to work for the TASK created with EXECUTE_IMMEDIATE block and bind parameter? The problem seems to be the way I write in inside the $$ scope of the stored procedure, no?
The problem is you're using the $$ string terminator for the stored procedure. Then inside the body of the stored procedure you're using $$ to terminate the string containing the body of the task. As soon as the compiler runs into that, it sees it as the end of the string defining the stored procedure body. To use $$ in the body of a stored procedure, you can do something like this:
CREATE OR REPLACE PROCEDURE create_exec_tasks_by_year_range()
RETURNS varchar
LANGUAGE JAVASCRIPT
EXECUTE AS OWNER
AS
$$
const double_dollar = '$' + '$';
var return_value = "";
var to_exec;
var rs_exec;
var range_years = Array.from(Array(new Date().getUTCFullYear() - 2006), (_, i) => (i + 2007).toString());
//CREATE a task TO CALL SP BY YEAR, FROM 2007-current year
range_years.forEach((year_elem) => {
rs = snowflake.createStatement( {
sqlText: `CREATE OR REPLACE TASK MY_TSK_YEAR_${year_elem}
WAREHOUSE = MOS_WEATHER
SCHEDULE = 'USING CRON 30 22 * * SUN UTC'
AS
EXECUTE IMMEDIATE
${double_dollar}
DECLARE
year_track float;
rs resultset;
BEGIN
year_track := :1;
rs := (execute IMMEDIATE 'INSERT INTO MY_TABLE VALUES(?)' using (year_track));
return TABLE(rs);
END;
${double_dollar};`
, binds: [year_elem] }).execute();
rs.next();
//rs.getColumnValue(1);
return_value += "MY_TSK_YEAR_"+year_elem+", ";
to_exec = snowflake.createStatement({sqlText: `EXECUTE TASK MY_TSK_YEAR_${year_elem}`});
rs_exec = to_exec.execute();
rs_exec.next();
return_value += rs_exec.getColumnValue(1)+", ";
});
return return_value;
$$;
#Greg Pavlik I don't know my final approach is OK, but it's working, as far as I run this on SF UI!
The above code, for me worked with a minor change from :1 to ${year_elem} , but then I face issue with binding a string value (single quoted) ... like this:
CREATE OR REPLACE PROCEDURE create_exec_tasks_by_year_range(str_arg string)
RETURNS varchar
LANGUAGE JAVASCRIPT
COMMENT ='[DRAFT] SP that creates tasks since a certain rangw with exec immediate block using 2 args: one is year in range, other is a string to isnert as well'
EXECUTE AS OWNER
AS
$$
const double_dollar = '$' + '$';
var return_value = "";
var to_exec;
var rs_exec;
var arg = STR_ARG;
var range_years = Array.from(Array(new Date().getUTCFullYear() - 2020), (_, i) => (i + 2021).toString());
//CREATE a task TO CALL SP BY YEAR, FROM 2007-current year
range_years.forEach((year_elem) => {
rs = snowflake.createStatement( {
sqlText: `CREATE OR REPLACE TASK MY_TSK_YEAR_${year_elem}
SCHEDULE = 'USING CRON 30 22 * * SUN UTC'
AS
EXECUTE IMMEDIATE
${double_dollar}
DECLARE
year_track float;
rs resultset;
my_str varchar;
BEGIN
year_track := ${year_elem};
my_str:= ${arg} ;
rs := (execute IMMEDIATE 'INSERT INTO Z_TEST_TASK_INSERT2 VALUES(?,?)' using (year_track,my_str));
return TABLE(rs);
END;
${double_dollar};`
//, binds: [year_elem]
}).execute();
rs.next();
return_value += rs.getColumnValue(1);
});
return return_value;
$$;
CALL create_exec_tasks_by_year_range('testing_String');
EXECUTE TASK MY_TSK_YEAR_2021;
EXECUTE TASK MY_TSK_YEAR_2022;
000904 SQL compilation error: error line 8 at position 21
invalid identifier 'testing_String'
One thing I would like to understand is:
How to use bind variable as a single quoted string? How to escape it or at leats distinguish it from the query prefixed by ' alread?
The SP creation succeeds via SQL Editor (DBEaver for instance) or SF UI
But the CALL is only working for me on SF UI (and SNOWSQL must happen the same, I assume). Is that something to do with the drivers used by JDBC vs. SF UI?
In Snowflake, when I create a store proc like so
create procedure stack_overflow_question(select_table varchar)
returns varchar
language sql
as
declare
select_statement varchar;
begin
select_statement := '
SELECT * FROM ' || :select_table || '
';
end;
Then, later when I use select get_ddl('procedure', 'stack_overflow_question(varchar)'); function to make edits to the store proc, the result of this function call has extra single quotes.
Here is the result
CREATE OR REPLACE PROCEDURE "STACK_OVERFLOW_QUESTION"("SELECT_TABLE" VARCHAR(16777216))
RETURNS VARCHAR(16777216)
LANGUAGE SQL
EXECUTE AS OWNER
AS 'declare
select_statement varchar;
begin
select_statement := ''
SELECT * FROM '' || :select_table || ''
'';
end';
Note the difference between the two! The extra single quotes. Also double quotes in the name of the store proc.
Is there something that I can do to prevent this from happening? I am using Snowsight - but don't think that this actually is the problem. Also, I am using snowflake as the language for store procs.
Any ideas?
I wrote a UDF that you can wrap around get_ddl that will convert the DDL from using doubled single quotes to single quotes and wrap the body with $$:
create or replace function CODE_DDL_TO_TEXT(CODE_TEXT string)
returns string
language javascript
as
$$
var lines = CODE_TEXT.split("\n");
var out = "";
var startCode = new RegExp("^AS '$", "ig");
var endCode = new RegExp("^'\;$", "ig");
var inCode = false;
var isChange = false;
var s;
for (i = 0; i < lines.length; i++){
isChange = false;
if(!inCode) {
inCode = startCode.test(lines[i]);
if(inCode) {
isChange = true;
out += "AS $" + "$\n";
}
}
if (endCode.test(lines[i])){
out += "$" + "$;";
isChange = true;
inCode = false;
}
if(!isChange){
if(inCode){
s = lines[i].replace(/''/g, "'") + "\n";
s = s.replace(/\\\\/g, "\\");
out += s;
} else {
out += lines[i] + "\n";
}
}
}
return out;
$$;
You can then call the UDF by wrapping it around the get_ddl function. Here is an example of fishing its own DDL out of get_ddl:
select CODE_DDL_TO_TEXT(get_ddl('function', 'CODE_DDL_TO_TEXT(string)'));
Edit:
You can also use this SQL to reconstruct a stored procedure from the INFORMATION_SCHEMA:
select 'create or replace procedure ' || PROCEDURE_NAME || ARGUMENT_SIGNATURE ||
'\nreturns ' || DATA_TYPE ||
'\nlanguage ' || PROCEDURE_LANGUAGE ||
'\nas $' || '$\n' ||
PROCEDURE_DEFINITION ||
'\n$' || '$;'
from INFORMATION_SCHEMA.PROCEDURES
;
This only returns body -
SELECT PROCEDURE_DEFINITION
FROM INFORMATION_SCHEMA.PROCEDURES
WHERE PROCEDURE_SCHEMA = 'SCHEMA_NAME' AND PROCEDURE_NAME = upper('stack_overflow_question');
I have the need to capture the o/p of "execute immediate" statement and use that o/p variable in incremental steps execution of the proc.
Like the code is :
var_01 := 'SELECT COUNT(1) AS cnt FROM ALL_RESOURCE_MONITOR_MASTER WHERE' || ' '
|| 'account_name = ' ||''''|| lv_acct_name_new ||''''|| ' ' || 'AND' || ' '
|| 'rm_type= ' ||''''|| type || ''''||' ' || 'AND' || ' '
|| 'env= ' ||''''|| env || ''''||' ';
cnt := (execute immediate :var_01);
Now post I get the cnt as populated, then I need to run the below if~else statement:
if (cnt > 0) then
return 'execute the statement';
else
return 'Resource monitor already present hence insert skipped';
end if;
But the problem is when I try to execute this block I am always getting an error like :
SQL compilation error: Invalid expression value (?SqlExecuteImmediateDynamic?) for assignment.
Please do provide your inputs on resolving this. Thanks in advance!!
Unfortunately there is no syntax like EXECTUE <sql> INTO <output_variable_list> USING <input_variable_list> known from different dialects.
EXECUTE IMMEDIATE:
Executes a string that contains a SQL statement or a Snowflake Scripting statement.
EXECUTE IMMEDIATE '<string_literal>'
[ USING (bind_variable_1 [, bind_variable_2 ...] ) ] ;
In this scenario there is no need to use dynamic SQL for checking in metadata table as it could be rewritten to use parametrized if-statement:
CREATE OR REPLACE TABLE ALL_RESOURCE_MONITOR_MASTER
AS
SELECT 'acc1' AS account_name, 'TEST' AS env, 'WAREHOUSE' AS rm_type;
Code:
DECLARE
lv_acct_name_new TEXT := 'acc1';
type TEXT := 'WAREHOUSE';
env TEXT := 'TEST';
BEGIN
IF (EXISTS (SELECT 1
FROM ALL_RESOURCE_MONITOR_MASTER
WHERE account_name = :lv_acct_name_new
AND rm_type = :type
AND env = :env
)) THEN
RETURN 'Executing some code';
ELSE
RETURN 'Resource monitor already present hence insert skipped';
END IF;
END;
I need to execute a procedure from python as below
DECLARE
#LayoutID INT = 9
,#PrivateFL INT =0
,#FromDate DATETIME = '20100101'
,#ToDate DATETIME = '20211001'
,#VesselIDs dbo.UniqueIntList
insert into #VesselIDs values (202),(330)
EXEC usp_GetComments #LayoutID, #PrivateFL, #FromDate, #ToDate, #VesselIDs
How do i pass '#VesselIDs' as parameter while executing it using python?
I tried below but its not working
list = [202,330]
storedProc = "EXEC usp_GetComments #LayoutID=?, #PrivateFL=?, #FromDate=?, #ToDate=?,
#VesselIDs=?"
params = (9,0,'20100101','20211001',list)
Do as below:
storedProc = "EXEC usp_GetComments #LayoutID=?, #PrivateFL=?, #FromDate=?, #ToDate=?, #VesselIDs=?"
fktvp = [(202,),(330,)]
fkparams = (9,0,'20100101','20211001',fktvp)
cursor.execute( storedProc, fkparams )
Some of my MS SQL stored procedures produce messages using the 'print' command. In my Delphi 2007 application, which connects to MS SQL using TADOConnection, how can I view the output of those 'print' commands?
Key requirements:
1) I can't run the query more than once; it might be updating things.
2) I need to see the 'print' results even if datasets are returned.
That was an interesting one...
The OnInfoMessage event from the ADOConnection works but the Devil is in the details!
Main points:
use CursorLocation = clUseServer instead of the default clUseClient.
use Open and not ExecProc with your ADOStoredProc.
use NextRecordset from the current one to get the following, but be sure to check you have one open.
use SET NOCOUNT = ON in your stored procedure.
SQL side: your stored procedure
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[FG_TEST]') AND type in (N'P', N'PC'))
DROP PROCEDURE [dbo].[FG_TEST]
GO
-- =============================================
-- Author: François
-- Description: test multi ADO with info
-- =============================================
CREATE PROCEDURE FG_TEST
AS
BEGIN
-- SET NOCOUNT ON absolutely NEEDED
SET NOCOUNT ON;
PRINT '*** start ***'
SELECT 'one' as Set1Field1
PRINT '*** done once ***'
SELECT 'two' as Set2Field2
PRINT '*** done again ***'
SELECT 'three' as Set3Field3
PRINT '***finish ***'
END
GO
Delphi side:
Create a new VCL Forms Application.
Put a Memo and a Button in your Form.
Copy the following text, change the Catalog and Data Source and Paste it onto your Form
object ADOConnection1: TADOConnection
ConnectionString =
'Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security In' +
'fo=False;Initial Catalog=xxxYOURxxxDBxxx;Data Source=xxxYOURxxxSERVERxxx'
CursorLocation = clUseServer
LoginPrompt = False
Provider = 'SQLOLEDB.1'
OnInfoMessage = ADOConnection1InfoMessage
Left = 24
Top = 216
end
object ADOStoredProc1: TADOStoredProc
Connection = ADOConnection1
CursorLocation = clUseServer
ProcedureName = 'FG_TEST;1'
Parameters = <>
Left = 24
Top = 264
end
In the OnInfoMessage of the ADOConnection put
Memo1.Lines.Add(Error.Description);
For the ButtonClick, paste this code
procedure TForm1.Button1Click(Sender: TObject);
const
adStateOpen = $00000001; // or defined in ADOInt
var
I: Integer;
ARecordSet: _Recordset;
begin
Memo1.Lines.Add('==========================');
ADOStoredProc1.Open; // not ExecProc !!!!!
ARecordSet := ADOStoredProc1.Recordset;
while Assigned(ARecordSet) do
begin
// do whatever with current RecordSet
while not ADOStoredProc1.Eof do
begin
Memo1.Lines.Add(ADOStoredProc1.Fields[0].FieldName + ': ' + ADOStoredProc1.Fields[0].Value);
ADOStoredProc1.Next;
end;
// switch to subsequent RecordSet if any
ARecordSet := ADOStoredProc1.NextRecordset(I);
if Assigned(ARecordSet) and ((ARecordSet.State and adStateOpen) <> 0) then
ADOStoredProc1.Recordset := ARecordSet
else
Break;
end;
ADOStoredProc1.Close;
end;
In .net's connection classes there is an event called InfoMessage. In a handler for this event you can retrieve the InfoMessage (print statements) from the event args.
I believe Delphi has a similar event called "OnInfoMessage" that would help you.
I dont think that is possible.
You might use a temp table to dump print statements and return it alongwith results.
Some enhancements to Francois' code (as tested with DXE2) to cater for multiple print statements and the results from a variable number of selects. The changes are subtle.
procedure TForm1.ADOConnection1InfoMessage(Connection: TADOConnection;
const Error: Error; var EventStatus: TEventStatus);
var
i: integer;
begin
// show ALL print statements
for i := 0 to AdoConnection1.Errors.Count - 1 do
begin
// was: cxMemo1.Lines.Add(Error.Description);
cxMemo1.Lines.Add(
ADOConnection1.Errors.Item[i].Description);
end;
end;
procedure TForm1.cxButton1Click(Sender: TObject);
const
adStateOpen = $00000001; // or uses ADOInt
var
records: Integer;
ARecordSet: _RecordSet;
begin
cxMemo1.Lines.Add('==========================');
ADOStoredProc1.Open;
try
ARecordSet := ADOStoredProc1.RecordSet; // initial fetch
while Assigned(ARecordSet) do
begin
// assign the recordset to a DataSets recordset to traverse
AdoDataSet1.Recordset := ARecordSet;
// do whatever with current ARecordSet
while not ADODataSet1.eof do
begin
cxMemo1.Lines.Add(ADODataSet1.Fields[0].FieldName +
': ' + ADODataSet1.Fields[0].Value);
AdoDataSet1.Next;
end;
// fetch next recordset if there is one
ARecordSet := ADOStoredProc1.NextRecordSet(records);
if Assigned(ARecordSet) and ((ARecordSet.State and adStateOpen) <> 0) then
ADOStoredProc1.Recordset := ARecordSet
else
Break;
end;
finally
ADOStoredProc1.Close;
end;
end;