VBA ADO single command with Insert and Select query - vba

I'm trying to execute the following SQL statement in an Excel Macro, but it fails to return a valid recordset. I suspect that having both an INSERT and a SELECT in the same statement is the culprit.
strSQL :
DECLARE #PurchOrdersTmpXl_A147 Table( SrNo INT, PONum VARCHAR(255));
INSERT INTO #PurchOrdersTmpXl_A147 (SrNo, PONum)
VALUES (1, 'PO0001968'),
(2, 'PO0000260');
SELECT
XLPO.SrNo [PO Order],
POOrigLine.PURCHID [Orig PO],
POOrigLine.ITEMID [Orig Item],
POOrigLine.Name [Orig Txt]
FROM
dbo.PURCHLINE POOrigLine
INNER JOIN #PurchOrdersTmpXl_A147 [XLPO]
ON POOrigLine.PurchID = XLPO.PONum
WHERE
POOrigLine.PurchStatus != 4
VBA Code
Set ADOConn = New ADODB.Connection
ADOConn.connectionString = strConnect
ADOConn.Open
Set ADOcmd = New ADODB.Command
ADOcmd.ActiveConnection = ADOConn
ADOcmd.CommandText = strSQL
Set ADOrs = ADOcmd.Execute
Debug.Print ADOrs.RecordCount // Gives error "Operation Not Allowed when object is closed"
Any help?
Thanks in advance.
Note 1:
I can confirm that this error is because I'm trying to execute an Insert query and a SELECT query in the same command string.
There are no errors if I use a temp table instead of the above table variable and split the command execution (execute the CREATE and INSERT first and then execute the SELECT).
However, as my temp table will never hold more than 20 records, I figure table variables would be more performance effective.

Try this:
Set ADOrs = New ADODB.Recordset
With ADOrs
.CursorLocation = adUseClient
Call .Open(strSQL, ADOConn, , , adCmdText)
If Not (.BOF And .EOF) Then Debug.Print .RecordCount
End With
In this case, the Command object is not needed; you only need the Recordset object.

Is it an option to turn this SQL into a stored procedure? You might have more luck that way as you can hide the other recordsets (which might be why it's closed)
Also is this the whole piece of SQL Code? Why not just select directly from the table? If you want to run this for a set of SrNo/PONum, then there is even more reason to run as a stored proc as you can pass table valued parameters.
http://msdn.microsoft.com/en-us/library/bb675163(v=vs.110).aspx
Here is an example stored proc that works for one set. See the link above for using many sets.
CREATE PROC p_Stuff
#SrNo INt, #PONum VARCHAR(255)
AS
SET NOCOUNT ON
DECLARE #PurchOrdersTmpXl_A147 Table( SrNo INT, PONum VARCHAR(255));
INSERT INTO #PurchOrdersTmpXl_A147 (SrNo, PONum)
VALUES (#SrNo, #PONum);
SELECT
XLPO.SrNo [PO Order],
POOrigLine.PURCHID [Orig PO],
POOrigLine.ITEMID [Orig Item],
POOrigLine.Name [Orig Txt]
FROM
dbo.PURCHLINE POOrigLine
INNER JOIN #PurchOrdersTmpXl_A147 [XLPO]
ON POOrigLine.PurchID = XLPO.PONum
WHERE
POOrigLine.PurchStatus != 4
and then execute as a stored procedure
EXEC p_Stuff 1, 'PO0001968'

Related

SQL Server - Issue with UPDATE inside while loop in a function?

I have one table and I am willing to fill two columns with values generated using other column value using function.
NOTE: I am working with a .mdf file in Visual Studio and not SQL Server.
Like if EmployeeName is 'XYZ' then Password will be 'XYZ#123' and mailid will be 'XYZ#gmail.com'
Here is the procedure
CREATE FUNCTION [dbo].[fnTempSetAllEmployeeMailIdAndEmployeePassword]()
RETURNS #OutputTable TABLE
(
EmployeeName NVARCHAR(250),
TempEmployeeMailId NVARCHAR(250),
TempEmployeePassword NVARCHAR(250)
)
AS
BEGIN
DECLARE #Initialiser INT = 1, #NumberOfRowsInTable INT, #TempEmployeeId INT, #TempEmployeeName NVARCHAR(250);
SELECT #NumberOfRowsInTable = COUNT(*)
FROM tbEmployee;
WHILE(#Initialiser <= #NumberOfRowsInTable)
BEGIN
SELECT
#TempEmployeeName = [EmployeeName],
#TempEmployeeId = [EmployeeId]
FROM
(SELECT
ROW_NUMBER() OVER(ORDER BY [EmployeeId] ASC) AS ROwNumber,
[EmployeeId], [EmployeeName]
FROM
tbEmployee) AS TempTable
WHERE
RowNumber = #Initialiser;
UPDATE tbEmployee
SET [EmployeeMailId] = LOWER(#TempEmployeeName) + '#gmail.com',
[EmployeePassword] = LOWER(#TempEmployeeName) + '#123'
WHERE [EmployeeId] = #TempEmployeeId;
SET #Initialiser = #Initialiser + 1;
END
INSERT #OutputTable
SELECT [EmployeeName], [EmployeeMailId], [EmployeePassword]
FROM tbEmployee;
RETURN
END
The problem is the above statements works when I execute in new query file.
But when I put in function and try to update it. I will not save and says something went wrong when executing.
But saves when I comment the UPDATE command.
Is it problem with Update being in while loop?
There are a couple of things going on here.
Firstly, the reason that it doesn't work in a function is because in SQL Server functions cannot change anything in the database. You are attempting to change the data in the table and that isn't allowed. It would be allowed in a stored procedure.
Secondly, it looks like a pretty inefficient way of doing the update. For each iteration of the loop this code:
grabs all the employees, sorts them
takes a single row and updates it
inserts that row into a table variable to later output
As a starting point, try just updating every single row in the table in one go:
CREATE PROCEDURE dbo.TempSetAllEmployeeMailIdAndEmployeePassword AS
BEGIN
UPDATE tbEmployee
SET [EmployeeMailId] = LOWER(#TempEmployeeName) + '#gmail.com',
[EmployeePassword] = LOWER(#TempEmployeeName) + '#123';
SELECT EmployeeName, EmployeeMailID, EmployeePassword
FROM tblEmployee;
END
If it turns out that you have problems because there are too many rows that you're trying to update at once, then maybe you could look at batching, but that's probably a separate topic.

Return SCOPE_IDENTITY() and OUTPUT with Classic ASP in SQL Server 2012 - Error Obj Closed

I am working with a Classic ASP (VB) application which needs to get the insert identity from a SQL Server 2012 table.
In development, I'm comparing SCOPE_IDENTITY() and OUTPUT, but the code:
returns more results than expected. The results are always 4 rows inserted into the table, with the last Identity returned in the application. Previously, I had tried the SQL without the 'Set NOCOUNT ON,' but received the error:
ADODB.Recordset error '800a0e78'
Operation is not allowed when the object is closed.
an error message saying Incorrect syntax near 'OUTPUT'.
How might the multi-row insert scenario be corrected, and what is the best approach to syntax with both of these, in VB? Would the RecordSet need to be closed after insert, prior to select?
Thanks
First, the table:
CREATE TABLE [dbo].[table_a]
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[col1] [int] NULL,
[col2] [int] NULL,
[col3] [int] NULL
) ON [PRIMARY]
GO
Next, the Classic ASP (VB) code:
<%
'---- CursorTypeEnum Values ----
Const adOpenForwardOnly = 0
Const adOpenKeyset = 1
Const adOpenDynamic = 2
Const adOpenStatic = 3
'---- LockTypeEnum Values ----
Const adLockReadOnly = 1
Const adLockPessimistic = 2
Const adLockOptimistic = 3
Const adLockBatchOptimistic = 4
strSQL = "SET NOCOUNT ON;INSERT INTO [table_a] ([col1],[col2],[col3]) VALUES (1,2,3) SELECT Scope_Identity() as ID;"
Set rsResults_a = Server.CreateObject("ADODB.Recordset")
rsResults_a.Open strSQL, objConnection, adOpenDynamic,adLockOptimistic
if not rsResults_a.eof then
Do until rsResults_a.EOF
ResultID_a = rsResults_a("ID")
response.write("<BR>"&ResultID_a&"<BR>")
rsResults_a.MoveNext
Loop
end if
rsResults_a.close
Set rsResults_a=nothing
'/////
strSQL = "INSERT INTO [table_a] ([col1],[col2],[col3]) VALUES (1,2,3) OUTPUT inserted.ID;"
Set rsResults_b = Server.CreateObject("ADODB.Recordset")
rsResults_b.Open strSQL, objConnection, adOpenDynamic,adLockOptimistic
if not rsResults_b.eof then
Do until rsResults_b.EOF
ResultID_b = rsResults_b("ID")
response.write("<BR>"&ResultID_b&"<BR>")
rsResults_b.MoveNext
Loop
end if
rsResults_b.close
Set rsResults_b =nothing
If objConnection.State = adStateOpen Then
objConnection.Close
End If
%>
Your first strSQL needs a semicolon after VALUES (1,2,3).
And the second one is invalid syntax and should be like the first (plus the missing semi-colon).
You need to add a semicolon after VALUES (1,2,3) in your strSQL
strSQL = "SET NOCOUNT ON; INSERT INTO [table_a] ([col1],[col2],[col3]) VALUES (1,2,3); SELECT Scope_Identity() as ID;"
if you realy realy want to achive the same result using the output keyworh then you need a SQL like this:
Declare #InsertOutput table
(
ID int
);
INSERT INTO [table_a] ([col1],[col2],[col3])
OUTPUT INSERTED.ID into #InsertOutput
VALUES (1,2,3);
select * from #InsertOutput;

MS SQL get values after INSERT

I am facing an issue where I need to calculate some data based on existing data, then insert that data and finally, return it to an Excel file using VBA.
I have successfully been able to create a stored procedure that returns a table of values after inserting:
[...]
INSERT INTO [ExcelRGA] (RM_prefix, RM_suffix, editing, createdBy, editedBy) values (#RM_prefix, #RM_suffix, 1, #user, #user);
DECLARE #tab table (RM varchar(20))
INSERT #tab SELECT (#RM_prefix +'-' + right('00' + #RM_suffix, 3)) as 'RM';
SELECT * FROM #tab
END
and this works! However, I am unable to get the values it is returning using VBA
Set oRS = New ADODB.Recordset
Set cmd = New ADODB.Command
Dim objRec As Object
cmd.ActiveConnection = oCon
cmd.CommandType = adCmdStoredProc
cmd.CommandText = "dbo.newRM"
cmd.Parameters.Refresh
cmd.Parameters("#user").Value = "john"
Set oRS = cmd.Execute()
but I try to do something like
while not oRS.eof
....
wend
get an error message stating that the recordset is closed and I cannot do my thing.
All I am trying to do is secure the information that I have computed (RM_prefix and RM_suffix), insert them into my table and return them to my Excel file.
If there is a way to do this without using a store procedure (such as a function) that would also be great!
Please keep in mind that this is a multi-user environment, so generating the values in Excel, sending them to the SQL server for an insert doesn't give a 100% guarantee regarding the uniqueness of said data.
Thank you all for your precious help and inputs!
Peter
You need something like this -
Private Function GetIDENTITY() As Long
oRS.Open "SELECT ##identity AS NewID", oCon
If Not IsNull(oRS.Fields("NewID")) Then
GetIDENTITY = oRS.Fields("NewID"): oRS.Close
Else
MsgBox "Error: Identity value not returned.", vbCritical
End
End If
End Function
Actually, the issue was coming from the SQL Stored Procedure. I've added
SET NOCOUNT ON;
right before the
INSERT INTO [ExcelRGA] (RM_prefix, RM_suffix, editing, createdBy, editedBy) values (#RM_prefix, #RM_suffix, 1, #user, #user);
And it has solved my problem! I can now browse the recordset in VBA.
Thank you all for your help and inputs!

How to insert NULL value into datetime field via stored procedure

I've searched for answers to this, but none I've found have really helped. I have a stored procedure that writes data to a table. The insert includes a DateTime field, but this field can be empty depending on what data is entered. The code in my Classic ASP page is similar to the following:
If Request.Form("Status") = "Closed" Then
Status = "Closed"
DateClosed = Now()
Else
Status = "OPEN"
DateClosed = NULL
End If
SP_AddToTable = "MyTable '" & Status & "', '" & DateClosed & "'"
Set SQLQuery = objConn.Execute(SP_AddToTable)
The stored procedure looks like this:
USE [MyDatabase]
GO
/****** Object: StoredProcedure [dbo].[SP_AddToTable] Script Date: 04/30/2013 10:00:10 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[SP_AddToTable]
#Status varchar(20),
#DateClosed datetime
AS
INSERT INTO MyTable (
[Status],
DateClosed,
)
VALUES (
#Status,
#DateClosed)
If I pass an actual date value, it works fine, but if the NULL value is passed, or if I don't pass any data to the DateClosed field then the field defaults to 1 Jan 1900. What do I need to do (either in my ASP code, or in the stored procedure) to leave the DateClosed field empty in the table?
I'm quite new to stored procedures, so please excuse any beginners errors. :)
Thanks.
First, you need to change your ASP code to call the sp using parameterized queries -- your query above is highly vulnerable to SQL Injection:
Set cmd = Server.CreateObject("ADODB.Command")
cmd.ActiveConnection = objConn
cmd.CommandText = "SP_AddToTable"
cmd.CommandType = adCmdStoredProc
cmd.Parameters.Refresh
cmd.Parameters(1) = Status
cmd.Parameters(2) = DateClosed
cmd.Execute
This should also fix your null issue as before you were inserting the value 'NULL' (with single quotes).

SQL Server 2005 Stored Procedure Using Table Variable Doesn't Return ResultSet to VB6 App

I have a stored procedure which selects a single field from a single row (based on a conditional statement) and stores it in a local variable. Based on the value of this field I update the same field in the same row of the table (using the same conditional statement). So the procedure first does a select into a local variable and then later updates the same field of the same row. The procedure then returns a result set via a select on a table variable (I have also attempted using a temporary table). The result set does not contain the variable or field I have updated. It doesn't even include any fields from this table.
The procedure works correctly when called from either Management Studio or from a test C# application. However, when called from my VB6 app the result set is not returned. All database updates are still performed however.
I have tried writing the stored procedure with a transaction and without, with TRY...CATCH and without, and both at the same time. I've tried various combinations of transaction isolation. No exceptions are thrown and the transaction will always commit. I've also used the WITH (NOLOCK) hint on the select statement. If I leave out the table update it will work. If I leave out the assignment to a local variable and instead hard code a value it works. If I simply use the select where I would put the variable it will NOT work.
Interestingly, if I add some random select statement to the procedure it will return that result set. I can even select that same field from the same record I assign to my variable with no issue. But it still will not return my desired result set.
My result set is a select from a table variable which is populated via insert statements using variables set throughout the procedure. There are no table joins at all. I do pass 2 parameters to the procedure - one of which is used in my conditional statement in the original select. But I still get the same behavior when I omit both parameters and hard code values.
I have tried restarting my SQL Server (2005 version 9.0.4053), restarting my machine, i have tried with NOCOUNT ON and OFF, I'm basically out of ideas.
Edit - Details of VB call and stored procedure signature:
I'll try to give as good as a description as I can without publishing actual code. I'm actually posting this for another developer who works with me, so please bear with me.
VB6 Call:
With cmdCommand
.ActiveConnection = cnnConn
.CommandType = adCmdStoredProc
.CommandText = "uspMyStoredProcedure"
.Parameters("#strParam1") = strFunctionParameter1
.Parameters("#bolParam2") = bolFunctionParameter2
.Execute
End With
MyResultSet.CursorLocation = adUseClient
MyResultSet.Open cmdCommand, , adOpenStatic, adLockReadOnly
Stored Procedure signature:
CREATE PROCEDURE uspMyStoredProcedure
#strParam1 NVARCHAR(XX),
#bolParam2 BIT
AS
BEGIN
SET NO COUNT ON
DECLARE #var1 NVARCHAR(XX),
#var2 NVARCHAR(XX),
#var3 NVARCHAR(XX),
#var4 INT,
#var5 BIT
--DECLARATION OF OTHER VARIABLES
DECLARE #varTableVariable TABLE
(
strTblVar1 NVARCHAR(XX) ,
intTblVar2 INT ,
strTblVar3 NVARCHAR(XX) ,
bolTblVar4 BIT ,
datTblVar5 DATETIME
)
SELECT #var1 = t.Field1, #var2 = t.Field2
FROM Table1 t
WHERE t.ID = #strParam1
SELECT #var3 = t2.Field1
FROM Table2 t2
IF (Condition)
BEGIN
SET #var4 = 1
IF (Condition)
BEGIN
--SET SOME VARIABLES
END
ELSE
BEGIN
UPDATE TABLE1
SET Field3 = #var4
WHERE Field1 = #strParam1
END
END
ELSE
BEGIN
IF(Condition)
BEGIN
SELECT #var5 = ISNULL(Condition)
FROM Table3 t3
WHERE t3.Field = #strParam1
--SET SOME MORE VARIABLES
END
END
IF(Condition)
BEGIN
UPDATE Table1
SET Field5 = #SomeVariable
WHERE Field1 = #strParam1
END
INSERT INTO Table4 (Field1, Field2, Field3)
SELECT #SomeVar1, #someVar2, #SomeVar3
FROM SomeOtherTable
WHERE Field3 = #someVariable
IF(Condition)
BEGIN
INSERT INTO #varTableVariable (strTblVar1, intTblVar2,
strTblVar3, bolTblVar4, datTblVar5 )
VALUES (#SomeVar1, #SomeVar2, #SomeVar3, #SomeVar4, #SomeVar5)
END
SELECT *
FROM #varTableVariable
END
So, essentially, the procedure takes two parameters. It carries out a number of simple operations - inserting and selecting data from a couple of different tables, an update to a table and inserting a row into a table variable.
The procedure finishes with a select from the table variable. There's nothing fancy about the procedure, or the call from VB6. As previously stated, the behaviour observed is unusual in that by commenting out certain sections the call and return will work - data is returned. Calling the same procedure from a C#.NET test app works and successfully returns the desired result.
All we manage to get back in the VB6 app is an empty recordset.
Edit 2: We've just found out that if we create an arbitrary table to hold the data to be returned by the final select statement instead of using a table variable, the procedure works...
We discovered that the stored procedure was actually executing twice, due to the way it was being called from VB6:
With cmdCommand
.ActiveConnection = cnnConn
.CommandType = adCmdStoredProc
.CommandText = "uspMyStoredProcedure"
.Parameters("#strParam1") = strFunctionParameter1
.Parameters("#bolParam2") = bolFunctionParameter2
.Execute
End With
MyResultSet.CursorLocation = adUseClient
MyResultSet.Open cmdCommand, , adOpenStatic, adLockReadOnly
The command object 'cmdCommand' is executed the first time with the explicit call as the final line in the 'With' statement, '.Execute'.
What we found was that the last line: 'MyResultSet.Open cmdCommand...' is also implicitly executing the stored procedure a second time.
Since the stored procedure's function is essentially to activate and deactivate an alarm, by executing twice we were getting the activation and deactivation occurring at once and therefore no resultset returned.
Hopefully this might help avoid someone else getting stuck on something like this.