Hi i have one doubt in snowflake how to write multiple update stments using stored procedure.
i have tried like below
create or replace procedure sp_multipleupdate()
returns table()
lANGUAGE sql
as
$$
declare res rsultset(
update TEST.PUBLIC.DEPT set Dname='PM' where deptid=10;
update TEST.PUBLIC.emp set name='veavi' where deptno=20;
update TEST.PUBLIC.loc set locname='del' where id=5;
)
begin
return table(res);
end;
$$;
getting error :
000006 (0A000): Multiple SQL statements in a single API call are not supported; use one API call per statement instead.
Syntax error: unexpected '('. (line 2)
please let me know how to write query to achive this task in snowflake server .
Multiple SQL statements inside the resultset are not supported.
Rather than writing the UPDATE statements like that I would create a more generic procedure and pass arguments to it, so maybe split the above one in 3 procedures since these UPDATE statements are for different tables.
Here is a sample of a generic stored procedure:
create or replace procedure find_invoice_by_id_via_execute_immediate(id varchar)
returns table (id integer, price number(12,2))
language sql
as
declare
select_statement varchar;
res resultset;
begin
select_statement := 'SELECT * FROM invoices WHERE id = ' || id;
res := (execute immediate :select_statement);
return table(res);
end;
You can read more here.
Related
Am trying to create a procedure using sql in snowflake,but its giving an error when calling it.
create or replace procedure get_max_date( )
returns datetime not null
language sql
as
$$
begin
set max_date= (select max(last_updated) from Control_Variables);
return max_date;
end
$$;
Error:SQL compilation error: error line 5 at position 9 invalid
identifier 'MAX_DATE'
please give me a solution ,actually i want to declare a variable inside proc and store data in that variable as shown in procedure
Regards,
Nadeem
Tried to create snowflake procedure
Indeed, you need to declare your variable inside the procedure. Try this:
create or replace procedure get_max_date( )
returns datetime not null
language sql
as
$$
declare
max_date datetime;
begin
max_date := (select max(last_updated) from Control_Variables);
return max_date;
end
$$;
Example how it works with some test data:
create or replace table Control_Variables (last_updated datetime);
insert into CONTROL_VARIABLES values ('2022-11-28 23:59:59');
insert into CONTROL_VARIABLES values (current_timestamp());
call get_max_date();
(For readers who don't use Delphi: Although the following is couched in terms of Delphi coding, my actual technical question isn't Delphi-specific, but is about how to find out how the Sql Server will "understand" a TransactSql batch submitted to it. A "TAdoQuery" is a Delphi class which basically wraps an ADO Command and a RecordSet and submits a TSql batch to a Sql Server. Usually, using the TAdoQuery, the batch is a single statement, but my q is specifically concerned with the possibility that the batch may contain more than one statement.)
Suppose I have a TAdoQuery whose Sql.Text contains a TransactSql batch comprising
one or more statements S1[...Sn].
What I'm trying to do is to find out without executing the batch whether a) the first (or only) statement, S1, in the batch will return a result set (even if empty) e.g by dint of it being a SELECT statement or an invocation of a stored procedure or table function, or whatever, AND b) how many statements the server thinks there are in the batch.
Regular users of Delphi's TAdoQuery will know that it's easy but slightly messy to test whether the first (or only) statement in a batch returns a result set just by calling TAdoQuery.Open. If it does, then it retrieves that result set, but if doesn't, then calling .Open will provoke an exception.
So, instead, I do something like this:
type
TMyDataSet = class(TDataSet);
procedure TForm1.Button1Click(Sender: TObject);
begin
if AdoQuery1.Active then
AdoQuery1.Close;
AdoQuery1.FieldDefs.Clear;
TMyDataSet(AdoQuery1).OpenCursor(True);
AdoQuery1.FieldDefList.Update;
//AdoQuery1.FieldList.Update;
//Listbox1.Items.Assign(AdoQuery1.FieldList);
end;
The call to .OpenCursor with its InfoQuery param set to true causes the AdoQuery's FieldDefs
to be populated iff the first statement in its Sql would return a result set, but, unlike calling .Open, it will not cause the batch to be executed.
So far, so good. Here's my question:
How (via the AdoQuery or otherwise) do I get the Sql Server to tell me how many
statements it thinks the batch contains? (I think I may have stumbled on a way (subject to a lot more testing), but am interested in whether anyone knows an "official" technique for doing this.)
Btw, for now I'm using an antique (Sql Server 2000!) server via its OleDB driver for Sql Server.
You can use the SET SHOWPLAN_ALL function to analyze the statement in SQL server instead of executing the query. Please note that you can't use this functionality with TADOQuery but only with a TADOCommand object (Like TADOConnection.Execute).
test table:
USE [TestCustomer]
GO
CREATE TABLE [dbo].[Tbl_test](
[Id] [int] NULL,
[col1] [varchar](50) NULL
) ON [PRIMARY]
GO
small demo program:
program SO27007086;
{$APPTYPE CONSOLE}
uses
ActiveX,
Db,
AdoDb,
SysUtils;
var
DbConn : TADOConnection;
function GetNumberOfStatements(SQLQuery: String): Integer;
var
Rs : _RecordSet;
LastId : Integer;
begin
Result := 0;
LastId := -1;
DbConn.Execute('SET SHOWPLAN_ALL ON');
Rs := DbConn.Execute(SQLQuery, cmdText, []);
while not Rs.EOF do
begin
if Rs.Fields['StmtId'].Value <> LastId then
begin
Inc(Result);
LastId := Rs.Fields['StmtId'].Value;
end;
if Rs.Fields['Parent'].Value = 0 then
Writeln(Rs.Fields['Type'].Value);
Rs.MoveNext;
end;
DbConn.Execute('SET SHOWPLAN_ALL OFF');
end;
begin
try
try
CoInitialize(nil);
DbConn := TADOConnection.Create(nil);
try
DbConn.ConnectionString := 'Provider=SQLOLEDB;Integrated Security=SSPI;Initial Catalog=TestCustomer;Data Source=localhost\SQLEXPRESS;MARS Connection=True;';
DbConn.Connected := True;
Writeln(GetNumberOfStatements('SELECT * FROM Tbl_test'));
Writeln(GetNumberOfStatements('SELECT * FROM Tbl_test DELETE FROM Tbl_test WHERE 1 = 2'));
Writeln(GetNumberOfStatements('SELECT * FROM Tbl_test INSERT INTO Tbl_Test (Id, Col1) VALUES (3, ''c''),(4, ''d'')'#13#10'DELETE FROM Tbl_test WHERE 1 = 2'));
finally
DbConn.Free;
end;
finally
CoUninitialize;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
Output:
SELECT
1
SELECT
DELETE
2
SELECT
INSERT
DELETE
3
i have 3 oracle databases: db1, db2, db3.
I have created database links from db1 to db2 and db3, called db002link and db003link.
Now i have a procedure which takes as input a date and takes different actions on tables according to that input. One of them though requires to connect to one of the db2 or db3 databases. Before the execution of the procedure, i don't not know to which one, as it depends on the data gathered by the procedure itself in previous steps.
So i need to concatenate some variables to create the db link and then connect through it.
i have the variable v_dbnumber which is varchar(3) and looks like '003' for instance and is the result of a select from a table. I have tried the following:
v_dbconn := 'db'||v_dbnumber||'link'
But then the next step, select * from s1.t1#v_dbconn gets a compilation error for the procedure: ORA-04052, ORA-00604, ORA-02019 referring to the non existing connection. But the object is shown as:
#v_dbconn instead of #db003link.
Can somebody please help me with this?
If you need the statement to be dynamic, you'd need to use dynamic SQL.
If you just want to open a cursor using a dynamically generated SQL statement, you can do something like
DECLARE
l_sql_stmt varchar2(1000);
l_dblink varchar2(100) := 'db002link';
l_rc sys_refcursor;
BEGIN
l_sql := 'select * from s1.t1#' || l_dblink;
open l_rc for l_sql;
END;
Normally, though, you're doing something with the data that you're selecting. That would generally involve using either dbms_sql or EXECUTE IMMEDIATE to execute the statement and fetch data into some local variable or collection. Assuming that the table definitions are the same in each of the databases, you could do something like
EXECUTE IMMEDIATE l_sql
BULK COLLECT INTO <<some appropriate collection>>
My solution is very similar to Justin's, though I am using a procedure with dynamic sql.
APPS#tst> CREATE OR REPLACE PROCEDURE test_dblink(
2 db_link VARCHAR2 )
3 AS
4 v_sql VARCHAR2(500);
5 v_test dual.dummy%TYPE;
6 BEGIN
7 v_sql := 'select dummy from dual#'|| db_link;
8 EXECUTE IMMEDIATE v_sql INTO v_test;
9 DBMS_OUTPUT.PUT_LINE(v_test);
10 END;
11 /
Procedure created.
APPS#tst> commit;
Commit complete.
APPS#tst>
APPS#tst>
APPS#tst>
APPS#tst> begin
2 test_dblink('db003link');
3 end;
4 /
X
PL/SQL procedure successfully completed.
This does not have error handling and it assumes one record will be returned (typically not a good assumption).
I have a stored procedure that can NOT be modified, the result of this stored procedure is normal select statement as following :
CREATE PROCEDURE LockedProcedure
AS
BEGIN
SELECT * FROM COLORS_TABLE
END
my problem is that I need to get its result as XML result like how the select statement returns when you provide "FOR XML" but without modifying the procedure itself, maybe we can create another stored procedure to call that or user defined function.
This is an example of the procedure that we CAN NOT modify because it is locked.
how to get its result as XML result NOT XML FILE...I don't want any physical file on hard disk.
Thanks.
Solution 1)
Create a temp table matching the output definition of stored procedure.
Use INSERT INTO #Tmp EXEC SPName to insert the stored procedure results into the temp table.
Use FOR XML in combination with SELECT command to fetch the results as xml from temp table.
Solution 2)
Create a CLR User-Defined function to execute the stored procedure and use the BCL facilities to convert the results to xml.
I had a similar problem to this but in my case editing the stored procedure is possible. Even though the OP mentions the stored procedure is locked, I still wanted to post this solution here for others that stumble upon it. This solution assumes the stored procedure uses dynamic SQL to select some data, but could just as easily be adapted to a non-dynamic SQL case.
Add a parameter to the sp such as "#lp_ReturnAsXML BIT = 0". When set to true, you will return the result set as XML using "FOR XML RAW" or some similar command.
Then add this to the end of the stored procedure as an alternative for running the dynamic SQL:
IF #lp_ReturnAsXML = 1
BEGIN
DECLARE #l_XML XML
SET #l_SQL = 'SET #l_XML = (SELECT * FROM ( ' +
#l_SQL + '
) d FOR XML RAW)'
EXEC sp_executesql #l_SQL, N'#l_XML XML OUTPUT', #l_XML = #l_XML OUTPUT
SELECT #l_XML
END
Now something like this should work:
DECLARE #table TABLE
(
Results XML
)
INSERT INTO #table
EXEC p_MyStoredProc ..., #lp_ReturnAsXML = 1
There are several limitations between a SQL Server stored procedure and a user defined function.
UDF's Can't
use nondeterministic functions
change the state of the database
Return messages to the caller
have any side effects
A stored procedure can return multiple record sets and they are not required to return the same fields each time.
create proc custom.sproc_CrazyFields
#ThisItem int
as
begin
if #ThisItem < 10
begin
select 'this' as ThisField, 'that' as ThatField, 'theOther' as theOtherField;
end
else
begin
Select 'theOther' as theOtherField, 'that' as thatField, 'this' as thisField;
end
end
go
exec custom.sproc_CrazyFields 4
exec custom.sproc_CrazyFields 40
An inline function is only going to return a single select statement.
A multistatement function has to declare the returned table.
Is there a way to dynamically return a result with changing columns with a UDF or is this one of the differences?
Sorry, you can't use dynamic SQL in a function. Maybe what you can do is write a stored procedure that creates a function in dynamic SQL, calls the function, then drops it. But then why not just build the query inline at that point.