I am trying to execute a stored procedure which performs an Insert action but I am getting an error (as shown in the screenshot). Following is my PB code and the subsequent one is the declaration of my params in the stored procedure.
DECLARE sp_insert_EndorsementUnderlying_AP PROCEDURE FOR
#EndNo = :endnum1,
#PolicyId = :policyid1
USING SQLCA;
EXECUTE sp_insert_EndorsementUnderlying_AP;
CHOOSE CASE SQLCA.sqlcode
CASE 0
// Execute successful; no result set
COMMIT;
CASE ELSE
MessageBox ("INSERT of New Endorsement Rows Failed", &
string (SQLCA.sqldbcode) + " = " + &
SQLCA.sqlerrtext)
RETURN
END CHOOSE
Stored procedure declaration:
ALTER PROCEDURE [dbo].[sp_insert_EndorsementUnderlying_AP]
#EndNo SMALLINT,
#PolicyId INT
AS
BEGIN
Error message:
[enter image description here][1]
[1]: https://i.stack.imgur.com/error msg screen shot
The syntax in PB to declare a procedure is:
DECLARE **PBPROCNAME** PROCEDURE FOR **DATABASEPROCNAME**
#parm1 = :pbvariable1, #parm2 = :pbvariable2 USING SQLCA;
Which is not what you have in your example. Can't comment on the error since your link does not work.
Try this one (note the slight difference procedure name - this is not compulsory but it helps make the difference between both worlds):
DECLARE pb_insert_EndorsementUnderlying_AP PROCEDURE FOR sp_insert_EndorsementUnderlying_AP
#EndNo = :endnum1,
#PolicyId = :policyid1
USING SQLCA;
EXECUTE pb_insert_EndorsementUnderlying_AP;
CHOOSE CASE SQLCA.sqlcode
CASE 0
// Execute successful; no result set
COMMIT;
CASE ELSE
MessageBox ("INSERT of New Endorsement Rows Failed", &
string (SQLCA.sqldbcode) + " = " + &
SQLCA.sqlerrtext)
RETURN
END CHOOSE
Related
I have created a stored procedure that returns a create table sql statement; I want to be able to now call that procedure and assign the result to a variable like:
set create_table_statement = call sp_create_stage_table(target_db, table_name);
snowflake will not let me do this, so is there a way I can.
Context
We have just been handed over our new MDP which is built on AWS-S3, DBT & Snowflake, next week we go into production but we have 200+ tables and snowlpipes to code out. I wanted to semi automate this by generating the create table statements based off the tables metadata and then calling the results from that to create the tables. At the moment we're having to run the SQL, copy+paste the results in and then run that, which is fine in dev/pre-production mode when it's a handful of tables. but with just 2 of us it will be a lot of work to get all those tables and pipes created.
so I've found a work around, by creating a second procedure and calling the first one as a se=ql string to get the results as a string - then calling that string as a sql statement. like:
create or replace procedure sp_create_stage_table("db_name" string, "table_name" string)
returns string
language javascript
as
$$
var sql_string = "call sp_get_create_table_statement('" + db_name + "','" + table_name + "');";
var get_sql_query = snowflake.createStatement({sqlText: sql_string});
var get_result_set = get_sql_query.execute();
get_result_set.next();
var get_query_value = get_result_set.getColumnValue(1);
sql_string = get_query_value.toString();
try {
var main_sql_query = snowflake.createStatement({sqlText: sql_string});
main_sql_query.execute();
return "Stage Table " + table_name + " Successfully created in " + db_name + " database."
}
catch (err){
return "an error occured! \n error_code: " + err.code + "\n error_state: " + err.state + "\n error_message: " + err.message;
}
$$;
It is possible to assign scalar result of stored procedure to session variable. Instead:
SET var = CALL sp();
The pattern is:
SET var = (SELECT * FROM TABLE(RESULT_SCAN(LAST_QUERY_ID())));
Sample:
CREATE OR REPLACE PROCEDURE TEST()
RETURNS VARCHAR
LANGUAGE SQL
AS
BEGIN
RETURN 'Result from stored procedrue';
END;
CALL TEST();
SET variable = (SELECT * FROM TABLE(RESULT_SCAN(LAST_QUERY_ID())));
SELECT $variable;
-- Result from stored procedrue
I have created a stored procedure for deleting record. In this stored procedure I am first checking for the usage of data which I am going to delete. If it is being used, then the stored procedure will return -2 otherwise it deletes the record.
But the problem is that even the record exists its return -1 instead of -2. I have also set the NOCOUNT OFF but don't know where is the problem.
I know this question is already answered by setting NOCOUNT OFF but its not working for me
ALTER PROCEDURE [dbo].[spDeletePIDNumber]
#Id int
AS
BEGIN
SET NOCOUNT OFF;
-- Insert statements for procedure here
if(exists(select * from tblBills where PID = #Id))
begin
return -2
end
else
begin
Delete from HelperPIDNumber
where Id = #Id
end
END
public int DeletePIDNumber(int Id)
{
try
{
int result = 0;
using (SqlConnection conn = new SqlConnection(Properties.Settings.Default.connection))
{
var cmd = new SqlCommand("spDeletePIDNumber", conn);
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#Id", Id);
conn.Open();
result = cmd.ExecuteNonQuery();
}
return result;
}
catch
{
throw;
}
}
From the ExecuteNonQuery documentation:
Executes a Transact-SQL statement against the connection and returns the number of rows affected.
Having SET NOCOUNT ON; in your procedure explicitely tells to SQL Server not to return a row count. In that case the return of the ExecuteNonQuery function is -1.
Also if the procedure does not affect any rows, it will not return a row count either even if NOCOUNT is OFF. In that case the return will also be -1.
What you appear to want to do is get the return value of the stored procedure. You will not get that from the result of ExecuteNonQuery. Please refer to this question on StackOverflow: Getting return value from stored procedure in ADO.NET
Generally ExecuteNonQuery will return number of affected records. It will return -1 in two cases:
When SET NOCOUNT ON has been set. From your code, its clear, you have SET NOCOUNT OFF and so this is not an issue at your case.
If number of affected rows is nothing, it will return -1. In your case, it looks like you are checking the data exists from one table tblBills and delete from another table HelperPIDNumber. So there is more chance there will be no matching record and nothing deleted.
Please check the point # 2 above.
if( exists(select * from tblBills where PID = #Id))
begin
return -2
end
else
begin
Delete from HelperPIDNumber where Id = #Id
end
use cmd.ExecuteScalar() instead of cmd.ExecuteNonQuery() ascmd.ExecuteNonQuery() return only the number of affected rows and not the value you are selecting.
CREATE PROCEDURE [dbo].[spTest]
#Pozitii varchar(max),
#NrZile int
AS
set #Pozitii = SUBSTRING(#Pozitii,0,LEn(#Pozitii))
CREATE TABLE #Pozitii (part varchar(20) null)
INSERT INTO #Pozitii(part)
SELECT part
FROM dbo.SDF_SplitString(#Pozitii,',')
if exists (SELECT * FROM #Pozitii)
RAISERROR('asdf',16,-1)
else RAISERROR('else',16,-1)
So runing this SP in SQL like this
exec [spTest] '11,12,13,',1
Returns:
(3 row(s) affected)
Msg 50000, Level 16, State 1, Procedure spTest, Line 27
asdf
Now if I run my procedure in delphi ( using an ADO object)
procedure TframePlanificatorPozitieComanda.Button5Click(Sender: TObject);
begin
try
with dm.spTest do
begin
Close;
Parameters.ParambyName('#Pozitii').Value := '11,12,13,';
Parameters.ParambyName('#NrZile').Value := 1;
ExecProc;
end;
except
on E: Exception do
begin
ShowMessage(E.Message);
end;
end;
end;
This code is not raising any errors?Any ideas why?
Have you tried adding:
SET NOCOUNT ON;
to your stored procedure? I think the exception is in a second resultset and is getting hidden by the first select result count.
The error is raising in sql-server.. but in delphi is not .. because there the procedure is already executed..
But in Delphi you can check if the procedure is run successfully or not regards what the result of the procedure (some of procedures have no output parameters).
In delphi please check your adostoredprocedure.parameters[0] like:
showmessage(vartostr(self.ADOStoredProc1.Parameters[0].Value))
.
if the result <> 0 that means error.
Using Sql Server 2014, I don't get the behaviour you describe.
I have a stored proc on the server defined as
CREATE PROCEDURE spRaiseError(#AnError int)
AS
BEGIN
declare #Msg Char(20)
if #AnError > 0
begin
Select #Msg = 'MyError ' + convert(Char(8), #AnError)
RaisError(#Msg, 16, -1)
end
else
select 1
END
I have a minimalist D7 Ado project with a TAdoConnection, TAdoQuery, TDataSource and TDBGrid connected up as you'd expect, a TEdit and a TButton.
Using this code
procedure TForm1.Button1Click(Sender: TObject);
var
S : String;
ErrorCount : Integer;
begin
S := 'exec spRaiseError ' + Edit1.Text;
AdoQuery1.SQL.Text := S;
try
AdoQuery1.Open;
except
end;
ErrorCount := AdoConnection1.Errors.Count;
Caption := IntToStr(ErrorCount);
end;
, if the number in Edit1 is > 0 I get only the error number on the form's caption, whereas if it contains 0 I see the value 1 in the DBGrid.
Btw, with "Stop on language exceptions" checked in the D7 debugger settings, without the try/except around AdoQuery1.Open, the debugger sees and catches the exception from the server.
Anyway, the take-home message from this answer is that you can use the TAdoConnection's Errors collection to detect whether there was an error and, if there was, you can get more information from it. See the Delphi OLH amd MS Ado documentation for more info about the Errors collection of TAdoConnection and other Ado-based Delphi classes.
I have a stored procedure that works fine but it has inside it three "select"s.
The selects are not from an inner temporary table.
This is mainly the format of the procedure:
ALTER PROCEDURE [dbo].[STProce]
#param1 int,
#param2 int,
#param3 int,
#param4 int,
#param5 int
AS
select #param1 as p1, #param2 as p2, #param3 as p3
.
.
.
select #param4 as p4
.
.
.
select #param5 as p5
I'm executing the procedure from another procedure and need to catch it there.
I created a table and inserts into it the "exec" from the procedure, like that:
CREATE TABLE #stalledp
(
RowNumber INT,
fldid INT,
fldLastUpdated datetime,
fldCreationDate datetime,
fldName nvarchar(255),
fldPending nvarchar(255)
)
INSERT INTO #stalledp (RowNumber,fldid,fldLastUpdated,fldCreationDate,fldName,fldPending)
EXEC spDebuggerViews_GetStuckWorkflowInstances #workflowSpaceId='00000000-0000-0000-0000-000000000000',#pageNum=1,#pageSize=100000,#orderByColumn=N'fldid',#sortOrder=1,#workflowInstanceId=0,#stuckInstanceType=1,#createdDateFrom='1900-01-01 00:00:00',#createdDateTo='9999-01-01 23:59:59',#updatedDateFrom='1900-01-01 00:00:00',#updatedDateTo='9999-01-01 23:59:59'
Afterwards I receive this error:
Column name or number of supplied values does not match table definition.
The order and name of columns of the table is exactly like the procedure returns.
Is there a possibility to catch only one of the tables that the procedure returns and avoid the other? I cannot change the procedure at all.
I tried declaring a table the same fields as the first select of the procedure and I get an error says that
Thank you in advance!
If all of the result sets returned are of the same structure, then you can dump them to a temp table as you are trying to do. However, that only gets you so far because if the data in the fields cannot be used to determine which result set a particular row came from, then you just have all of the result sets with no way to filter out the ones you don't want.
The only way to interact with multiple result sets individually, regardless of them having the same or differing structures, is through app code (i.e. a client connection). And if you want to do this within the context of another query, then you need to use SQLCLR.
The C# code below shows a SQLCLR stored procedure that will execute a T-SQL stored procedure that returns 4 result sets. It skips the first 2 result sets and only returns the 3rd result set. This allows the SQLCLR stored procedure to be used in an INSERT...EXEC as desired.
The code for the T-SQL stored proc that is called by the following code is shown below the C# code block. The T-SQL test proc executes sp_who2 and only return a subset of the fields being returned by that proc, showing that you don't need to return the exact same result set that you are reading; it can be manipulated in transit.
C# SQLCLR proc:
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
public class TheProc
{
[Microsoft.SqlServer.Server.SqlProcedure]
public static void Get3rdResultSetFromGetStuckWorkflowInstances()
{
int _ResultSetsToSkip = 2; // we want the 3rd result set
SqlConnection _Connection = null;
SqlCommand _Command = null;
SqlDataReader _Reader = null;
try
{
_Connection = new SqlConnection("Context Connection = true;");
_Command = _Connection.CreateCommand();
_Command.CommandType = CommandType.StoredProcedure;
_Command.CommandText = "tempdb.dbo.MultiResultSetTest";
// (optional) add parameters (but don't use AddWithValue!)
// The SqlDataRecord will be used to define the result set structure
// and act as a container for each row to be returned
SqlDataRecord _ResultSet = new SqlDataRecord(
new SqlMetaData[]
{
new SqlMetaData("SPID", SqlDbType.Char, 5),
new SqlMetaData("Status", SqlDbType.NVarChar, 30),
new SqlMetaData("Login", SqlDbType.NVarChar, 128),
new SqlMetaData("HostName", SqlDbType.NVarChar, 128),
new SqlMetaData("BlkBy", SqlDbType.VarChar, 5),
new SqlMetaData("DBName", SqlDbType.NVarChar, 128)
});
SqlContext.Pipe.SendResultsStart(_ResultSet); // initialize result set
_Connection.Open();
_Reader = _Command.ExecuteReader();
// Skip a predefined number of result sets
for (int _Index = 0;
_Index < _ResultSetsToSkip && _Reader.NextResult();
_Index++) ;
// Container used to move 1 full row from the result set being read
// to the one being sent, sized to the number of fields being read
Object[] _TempRow = new Object[_Reader.FieldCount];
while (_Reader.Read())
{
_Reader.GetValues(_TempRow); // read all columns
_ResultSet.SetValues(_TempRow); // set all columns
SqlContext.Pipe.SendResultsRow(_ResultSet); // send row
}
}
catch
{
throw;
}
finally
{
if(SqlContext.Pipe.IsSendingResults)
{
SqlContext.Pipe.SendResultsEnd(); // close out result set being sent
}
if(_Reader != null && !_Reader.IsClosed)
{
_Reader.Dispose();
}
_Command.Dispose();
if (_Connection != null && _Connection.State != ConnectionState.Closed)
{
_Connection.Dispose();
}
}
return;
}
}
T-SQL test proc:
USE [tempdb]
SET ANSI_NULLS ON;
IF (OBJECT_ID('dbo.MultiResultSetTest') IS NOT NULL)
BEGIN
DROP PROCEDURE dbo.MultiResultSetTest;
END;
GO
CREATE PROCEDURE dbo.MultiResultSetTest
AS
SET NOCOUNT ON;
SELECT 1 AS [ResultSet], 'asa' AS [test];
SELECT 2 AS [ResultSet], NEWID() AS [SomeGUID], GETDATE() AS [RightNow];
EXEC sp_who2;
SELECT 4 AS [ResultSet], CONVERT(MONEY, 131.12) AS [CashYo];
GO
EXEC tempdb.dbo.MultiResultSetTest;
To do:
Adjust _ResultSetsToSkip as appropriate. If you only want the first result set, simply remove both _ResultSetsToSkip and the for loop.
Define _ResultSet as appropriate
Set _Command.CommandText to be "spDebuggerViews_GetStuckWorkflowInstances"
Create the necessary parameters via SqlParameter (i.e. #workflowSpaceId='00000000-0000-0000-0000-000000000000',#pageNum=1,#pageSize=100000,#orderByColumn=N'fldid',#sortOrder=1,#workflowInstanceId=0,#stuckInstanceType=1,#createdDateFrom='1900-01-01 00:00:00',#createdDateTo='9999-01-01 23:59:59',#updatedDateFrom='1900-01-01 00:00:00',#updatedDateTo='9999-01-01 23:59:59')
If needed, add input parameters to the SQLCLR proc so that they can be used to set the values of certain SqlParameters
Then use as follows:
INSERT INTO #stalledp
(RowNumber,fldid,fldLastUpdated,fldCreationDate,fldName,fldPending)
EXEC Get3rdResultSetFromGetStuckWorkflowInstances;
There is a way to get the first record set but the others, I'm afraid, you're out of luck.
EXEC sp_addlinkedserver #server = 'LOCALSERVER', #srvproduct = '',
#provider = 'SQLOLEDB', #datasrc = ##servername
SELECT * FROM OPENQUERY(LOCALSERVER, 'EXEC testproc2')
EDIT: If you only need to check the other result set for columns to be not null you could predefine the expected results sets like so:
EXEC testproc2 WITH RESULT SETS (
(a VARCHAR(MAX) NOT NULL, b VARCHAR(MAX) NOT NULL),
(a VARCHAR(MAX) NOT NULL)
);
If the query within the stored procedure returns null values a exception is raised at that point in procedure. This will only work on sql server 2012 and upwards though.
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;