"Must declare the variable #myvariable" error with ADO parameterized query - sql-server

i am trying to use parameterized queries with ADO. Executing the Command object throws the error:
Must declare the variable '#filename'
i declare the parameter #filename using CreateParameter/Append:
sql := 'INSERT INTO Sqm(Filename, data) VALUES(#filename, #data)';
command := CoCommand.Create;
command.Set_ActiveConnection(Connection.ConnectionObject);
command.Set_CommandText(sql);
command.Set_CommandType(adCmdText);
command.Parameters.Append(Command.CreateParameter('#filename', adLongVarWChar, adParamInput, -1, Filename));
command.Parameters.Append(Command.CreateParameter('#data', adLongVarWChar, adParamInput, -1, xml);
command.Execute({out}recordsAffected, EmptyParam, adCmdText or adExecuteNoRecords);
What am i doing wrong?

As far i know ADO doesn't supports named parameters in SQL sentences (SELECT, INSERT, UPDATE), so you must use the ? char to indicate the parameter
sql := 'INSERT INTO Sqm(Filename, data) VALUES(?, ?)';
and then assign the parameters values in the same order as are used in the sql sentence.
ADO 2.6 Introduces the NamedParameters property, but it seems which only works with stored procedures.

try this
uses ADODB, DB;
...
...
... and then in some event handler (e.g. button click),
var
aCommand :TADOCommand;
begin
aCommand := TADOCommand.create(self);
aCommand.ConnectionString := 'build the connection string or use TADOConnection and assign to Connection property instead of ConnectionString property';
aCommand.commandText := 'INSERT INTO Sqm(Filename, data) VALUES(:filename, :data);';
aCommand.parameters.paramByName('filename').value := 'test';
aCommand.parameters.paramByName('data').value := 'some data';
aCommand.execute;
aCommand.free;
end;
I have been using parameter by names this way for TADOCommand and TADOQuery with no problem.

Use Parameters.AddWithValue as shown below
connectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};Jet OLEDB:Database Password=RainbowTrout;";
InsertQry = "Insert into Sections(Name, PartNumber, VersionNumber, Channel, Address, Status, IPAddr) "
+ "values(#SectionName, #PartNumber, #VersionNumber, #Channel, #Address, #Status, #IPAddr)";
NewCfgConnection.ConnectionString = string.Format(connectionString, ConfigFN);
NewCfgCommand.Connection = NewCfgConnection;
NewCfgCommand.CommandText = InsertQry;
NewCfgConnection.Open();
// Clear parameter values from last record
NewCfgCommand.Parameters.Clear();
// Insert record into sections table - set parameters
NewCfgCommand.Parameters.AddWithValue("#SectionName", sSectionName);
NewCfgCommand.Parameters.AddWithValue("#PartNumber", sPartNumber);
NewCfgCommand.Parameters.AddWithValue("#VersionNumber", sVersionNumber);
NewCfgCommand.Parameters.AddWithValue("#Channel", iChannel);
NewCfgCommand.Parameters.AddWithValue("#Address", iAddress);
NewCfgCommand.Parameters.AddWithValue("#Status", iStatus);
NewCfgCommand.Parameters.AddWithValue("#IPAddr", iIP);
NewCfgCommand.ExecuteNonQuery();

Related

Call stored procedure specifying only parameters with a value

In an instance of SQL Server 2016 I have a stored procedure with dozens of parameters. For example:
CREATE PROCEDURE spName (
#par1 INT = NULL,
#par2 VARCHAR(10) = NULL,
....
....
#par98 INT = NULL,
#par99 INT = NULL,
) AS
BEGIN
....
....
END
I have a client written in C# that calls the stored procedure specifying only the parameters with a value. Ex:
SqlCommand cmd = new SqlCommand();
cmd.CommandText = "spName";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Connection = dbConn;
cmd.Parameters.Add(new SqlParameter("par1", "val1"));
cmd.Parameters.Add(new SqlParameter("par47", "val47"));
...
cmd.ExecuteNonQuery();
It works perfectly! So, the procedure is executed and only the 2 parameters (par1 and par47) have a value. Other parameters maintain the default value (NULL).
I would do the same from a Java client using Microsoft JDBC driver 6.2.
I specify the parameters with List<Map<String, Object>>, so a list of couple parameterName-->parameterValue. The following method builds the PreparedStatement object:
private CallableStatement prepareStatement(String spName, Map<String, ?> parameters) throws SQLException {
setupConnection();
CallableStatement stmt = null;
try {
stmt = conn.prepareCall(getSpCallString(spName, parameters));
if (parameters != null) {
for (String parName : parameters.keySet())
stmt.setObject(parName, parameters.get(parName));
}
} catch (SQLException e) {
ApplicationLogging.severe("Cannot prepare callable statement", e);
throw e;
}
return stmt;
}
The method getSpCallString() generates a string of the type { call spName ?,?, ... , ? } with a number of ? as the number of parameters with a value passed to the procedure, so not all 99 parameters. If I have 2 parameter it generates the string { call spName ?,? }.
By passing for example par15=val15 and par47=val47 it raises the following exception:
com.microsoft.sqlserver.jdbc.SQLServerException: The index 2 is out of range.
I could resolve this putting in the call command the same number of ? as the number of parameter of the stored procedure but... I don't know the number of parameters for each stored procedure (and their position)!
In C# this is simply resolved because the parameters are assigned only with their name, so the number and the order of parameters can be really a black box.
Can I do this in some way in Java?
This is a confirmed deficiency in the current implementation of named parameter support for CallableStatement in the mssql-jdbc driver. Despite section 13.3.2 of the JDBC 4.2 specification stating ...
Named parameters can be used to specify only the values that have no default value.
... we seem to be required to provide a parameter placeholder for every possible parameter, and there doesn't appear to be a way to specify DEFAULT for the parameters we might otherwise simply omit.
As a workaround we could use code like this
public static ResultSet executeStoredProcedureQuery(
Connection conn, String spName, Map<String, Object> paramItems)
throws SQLException {
StringBuffer sqlBuf = new StringBuffer("EXEC ");
sqlBuf.append(spName);
int paramCount = 1;
for (String paramName : paramItems.keySet()) {
sqlBuf.append(
(paramCount++ > 1 ? ", " : " ") +
(paramName.startsWith("#") ? "" : "#") + paramName + "=?");
}
String sql = sqlBuf.toString();
myLogger.log(Level.INFO, sql);
// e.g., EXEC dbo.BreakfastSP #helpings=?, #person=?, #food=?
PreparedStatement ps = conn.prepareStatement(sql);
paramCount = 1;
for (String paramName : paramItems.keySet()) {
ps.setObject(paramCount++, paramItems.get(paramName));
}
return ps.executeQuery();
}
which we could call like this
// test data
Map<String, Object> paramItems = new HashMap<>();
paramItems.put("#person", "Gord");
paramItems.put("#food", "bacon");
paramItems.put("#helpings", 3);
//
ResultSet rs = executeStoredProcedureQuery(conn, "dbo.BreakfastSP", paramItems);
If using a third party library to facilitate calling such procedures is an option for you, then jOOQ certainly helps via its code generator for stored procedures, which generates stubs for each of your procedures, making such calls type safe. It includes support for:
Table valued functions
Table valued parameters
Defaulted parameters
In / Out parameters
Optional return value of procedures
Fetching undeclared update counts and result sets
Much more
In your case, you could write:
Spname sp = new Spname();
sp.setPar1("val1");
sp.setPar47("val47");
sp.execute(configuration); // The object containing your JDBC connection
sp.getResults(); // The result set(s) and update counts, if any
Behind the scenes, a JDBC CallableStatement is created, just like you would do manually:
try (CallableStatement s = c.prepareCall(
"{ ? = call [dbo].[spName] (#par1 = ?, #par47 = ?) }"
)) {
// Get the optional procedure return value that all procedures might return
s.registerOutParameter(1, Types.INTEGER);
s.setString(2, "val1");
s.setString(3, "val47");
s.execute();
// Lengthy procedure to fetch update counts and result set(s)
}
See this article if you want to generically fetch update counts and result set(s) with JDBC.
Disclaimer: I work for the company behind jOOQ.

LIKE statement using parametrized SQL in vbscript

I'm trying to create a LIKE SQL statement in vbscript using parametrized SQL.
The command text passed through is along the lines of
SET NOCOUNT ON;
DECLARE #pname as nvarchar(50);
SELECT #pname = ?;
SELECT *
FROM tblProject
WHERE projName LIKE #pname
And I'm concatenating % onto either side of the string that I'm passing through as pname. If I change the LIKE to an = and remove the %s then it works fine. And if I run the statement directly and replace #pname with '%searchterm%' it works fine. Any help would be much appreciated.
Edit: vbscript:
Set rsAnswers = Server.CreateObject("ADODB.Recordset")
Set cmd = server.createobject("ADODB.Command")
cmd.CommandText = mySQL
cmd.CommandType = 1
cmd.CommandTimeout = 900
cmd.ActiveConnection = svrPerformanceConnectionset prm = cmd.CreateParameter("#pname", 129, 1, 50, thisProjName)
cmd.Parameters.Append prm
rsAnswers.CursorLocation = 3
rsAnswers.Open cmd, , 0, 1
(thisProjName is getting a string from a form)
Thanks,
Tim
it is the expected behaviour because the parameter value is escaped to handle it as a value.
the solution is to move the concatenation of the % out of the parameter:
SET NOCOUNT ON;
SELECT *
FROM tblProject
WHERE projName LIKE '%' + #pname + '%';
notice the missing declaration & initialization of the parameter: inizialization and value assignement are performed by the VBScript engine and data access layer when creating the related objects in the script.
After trying many things, I found that the problem was the data type I was declaring my parameter as. I'm not sure why exactly but using adVarChar (200) rather than adChar (129) made the LIKE statement work.
Thanks to everybody who answered and commented!

Receiving one out of few Result Sets in stored procedure

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.

ADO - how to get primary key fields

I'm using ADO (Delphi & C++ Builder) and I would like get primary key fields (their names) that are in some TADOTable component. How to do it?
I found out that I need to use ADOConnection->OpenSchema but don't know how to use this..
Now I tried this:
int bounds[] = {0,2};
OleVariant A(bounds,1, varVariant);
A.PutElement(varEmpty,0);
A.PutElement(varEmpty,1);
A.PutElement("MyDBTable",2);
OleVariant EmptyParam;
EmptyParam.VType = VT_ERROR;
EmptyParam.VError = DISP_E_PARAMNOTFOUND;
TADODataSet *temp = new TADODataSet(NULL);
AdoConnection1->OpenSchema(siPrimaryKeys, A, EmptyParam, temp);
temp->Open();
temp->First();
while (!temp->Eof)
{
Memo1->Lines->Add(temp->Fields->Fields[0]->AsString);
temp->Next();
}
temp->Close();
delete temp;
When running this code I get: "Object or provider is not capable of performing requested operation."?
References for OpenSchema Method (ADO), examples can be found here
An example implemention in Delphi for Microsoft Access and MSSqlServer could look like this:
Procedure OpenPrimaryKeyInfo ( Connection:TAdoConnection
; DatabaseName , SchemaName , TableName : Variant
; Display:TAdodataset );
begin
Connection.OpenSchema( siPrimaryKeys
, VarArrayOf([ DatabaseName , SchemaName , TableName ])
, EmptyParam , Display );
end;
Example call for Microsoft Access:
OpenPrimaryKeyInfo( AdoConnection2 , UnAssigned , UnAssigned , 'TableX' , Adodataset1 );
Example call for MSSqlServer:
OpenPrimaryKeyInfo( AdoConnection1 , 'MyDataBase' , 'dbo' , 'TableX' , Adodataset1 );
You establish the connection and open it as usual (using TADOConnection.ConnectionString and TADOConnection.Open), and then ask for the schema using OpenSchema. The TADODataSet you provide as the last parameter will contain a RecordSet that you can use just like any other dataset.
Here's a quick sample I threw together (thanks to #bummi for the correction to the third parameter - Unassigned and Null both compiled, but didn't actually work when tested). I dropped a TADOConnection, TADODataSet, and TMemo on a new form and configured the TADOConnection quickly to point to a simple SQL Server Express database I have for some testing - I included the connection string; the only change I made to it was in the computer name provided in the Data Source portion).
procedure TForm3.FormShow(Sender: TObject);
var
i: Integer;
sLine: string;
begin
Memo1.Clear;
ADOConnection1.ConnectionString := 'Provider=SQLOLEDB.1;' +
'Integrated Security=SSPI;' +
'Persist Security Info=False;' +
'Initial Catalog=Contacts;' +
'Data Source=MyComputer\SQLEXPRESS';
ADOConnection1.Connected := True;
ADOConnection1.OpenSchema(siPrimaryKeys, Unassigned, EmptyParam, ADODataSet1);
sLine := '';
for i := 0 to ADODataSet1.FieldCount - 1 do
sLine := sLine + ADODataSet1.Fields[i].FieldName + #9;
Memo1.Lines.Add(sLine);
Memo1.Lines.Add('');
while not ADODataSet1.Eof do
begin
sLine := '';
for i := 0 to ADODataSet1.FieldCount - 1 do
sLine := sLine + ADODataSet1.Fields[i].AsString + #9;
Memo1.Lines.Add(sLine);
ADODataSet1.Next;
end;
end;
The possible values for the SchemaInfo value (the first parameter passed to OpenSchema) can be found in the ADODB unit - they're documented in the Delphi help file (note that the documentation says that not all of them are available via ADO):
TSchemaInfo = (siAsserts, siCatalogs, siCharacterSets, siCollations,
siColumns, siCheckConstraints, siConstraintColumnUsage,
siConstraintTableUsage, siKeyColumnUsage, siReferentialConstraints,
siTableConstraints, siColumnsDomainUsage, siIndexes, siColumnPrivileges,
siTablePrivileges, siUsagePrivileges, siProcedures, siSchemata,
siSQLLanguages, siStatistics, siTables, siTranslations, siProviderTypes,
siViews, siViewColumnUsage, siViewTableUsage, siProcedureParameters,
siForeignKeys, siPrimaryKeys, siProcedureColumns, siDBInfoKeywords,
siDBInfoLiterals, siCubes, siDimensions, siHierarchies, siLevels,
siMeasures, siProperties, siMembers, siProviderSpecific);

View output of 'print' statements using ADOConnection in Delphi

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;

Resources