VBA Access writing data from SQL server to the Access DB - sql-server

Im trying to open a SQL stored procedure, which contains a Select top 5* form table, and load the data into an Access table called Table3.
How do I set the Command Object ActiveConnection property to use the current Access DB?
when I provide it with an actual connection string, it says that the user has locked it.
At the moment, it runs and prints prints out the results but it does not insert the values. It does not give me an error either.
'Use this code to run the SP and extract all the records
Public Sub ExecuteSPAsMethod2()
Dim rsData As ADODB.Recordset
Dim sConnectSQL As String 'to create connection to SQL Server
Dim sConnectAccess As String 'to create connection with Access DB (may not be neccessary)
Dim objCommand As ADODB.Command 'for INSERT results of SP into Access table
'Creating the connection string to SQL server
sConnectSQL = "Provider=SQLOLEDB;Data Source=MYSERVER; " & _
"Initial Catalog=SQLDatabase;Integrated Security=SSPI"
'Creating the Connection object and Recordset object
Set objConn = New ADODB.Connection
Set rsData = New ADODB.Recordset
'Opening the connection
objConn.Open sConnectSQL
'Execute the SP and give the results to the Recordset
objConn.SurveyDataSP "4", rsData
Do While Not rsData.EOF
Debug.Print rsData!Stratum
rsData.MoveNext
Loop
'Now write the data into Access Table
'Create connection string to Access DB table
'sConnectAccess = "Provider=Microsoft.ACE.OLEDB.12.0;" & _
"Data Source=C:\Databse1.accdb;" & _
"Mode = Share Exclusive"
'Command object to be used for Access SQL query
Set objCommand = New ADODB.Command
'objCommand.ActiveConnection = sConnectAccess
'Insert new record in the DB
'Load the SQL string into the command object
Do While Not rsData.EOF
objCommand.CommandText = "INSERT INTO table3 (" & rsData!Stratum & ")"
objCommand.Execute
rsData.MoveNext
Loop
End Sub

There is no need to write such large amounts of code and create world poverty. Save the execute command as a pass through query.
Eg:
Exec 4
Assuming the above is called sp1, then this code will append all data from the above into the local table:
CurrentDb.Execute "INSERT INTO sp1Local select sp1.* from sp1"
So all of this code can be done with ONE line of VBA code.

Related

Call SQL Stored Procedure using Procedure clause

I am trying to build a database for IT device inventory. It uses an MS Access Office 365 front-end with a SQL 2017 backend.
In the database, we don't want to delete records, simply archive them to another table. To do this, I created a stored procedure in SSMS and verified that it does the job properly.
I want VBA to call this stored procedure. For this procedure, I need to pass it identifying information. In VBA, I am trying to assign the server name value from a form to a variable that I can pass into a call of the stored procedure. I found examples using the EXEC command but Access tells me I must use the Procedure clause.
Private Sub Command148_Click()
Dim SrvNameVar As String
Dim strSQL As String
Dim strParm As String
SrvNameVar = Me.SrvName
strParm = "PARAMETERS [Server Name] As CHAR;"
Dim dbs As DAO.Database
Dim qdf As DAO.QueryDef
Set dbs = CurrentDb
strSQL = strParm & "PROCEDURE dbo.sp_ArchiveServer [Server Name];"
Set qdf = dbs.CreateQueryDef("SrvArchive", strSQL)
dbs.Execute ("SrvArchive")
End Sub
The stored procedure that functions properly in SSMS:
CREATE PROCEDURE sp_ArchiveServer #Server nvarchar(30) AS
BEGIN TRANSACTION;
INSERT INTO FSC.dbo.Archive_Servers ([SrvID],[SID],[SrvName],[Make],
[Model],[SN],[SrvIP],[RemoteMgmt],[OSID],[IsDP],[IsIEMRelay],
[IsGUP],[DatePurch],[WarrantyExp],[RAIDConfig],[PrintSrv],
[ConnectedToUPS],[VirtHost],[VirtMachine])
SELECT FSC.dbo.Servers.*
FROM FSC.dbo.Servers
WHERE FSC.dbo.Servers.SrvName = #Server;
DELETE FROM FSC.dbo.Servers
WHERE FSC.dbo.Servers.SrvName = #Server;
COMMIT;
Currently, you are conflating MS Access SQL dialect with SQL Server dialect. Only MS Access SQL queries supports PARAMETERS. However, you are attempting to run an SQL Server query, specifically to execute a stored procedure.
MS Access does allow pass-through queries to backend databases so you can adjust your QueryDef (defaults to Access backend) to connect to MSSQL database and then run EXEC command. All pass-through queries should conform to SQL dialect of backend.
Private Sub Command148_Click()
Dim dbs As DAO.Database
Dim qdf As DAO.QueryDef
Dim SrvNameVar, strSQL As String
SrvNameVar = Me.SrvName
strSQL = "EXEC dbo.sp_ArchiveServer #Server='" & SrvNameVar &"'"
Set dbs = CurrentDb
Set qdf = dbs.CreateQueryDef("SrvArchive")
' ASSIGN ODBC DSN CONNECTION
qdf.Connect = "ODBC; DATABASE=database; UID=user; PWD=password; DSN=datasourcename;"
qdf.SQL = strSQL
qdf.Execute
End Sub
To effectively use parameterization, consider a different API, namely ADO (extendable to any backend database) instead of DAO (more tailored for Access databases).
Private Sub Command148_Click()
' SET REFERENCE TO Microsoft ActiveX Data Object #.# Library
Dim conn As ADODB.Connection, cmd As ADODB.Command
Dim SrvNameVar As String
SrvNameVar = Me.SrvName
' OPEN DRIVER OR DSN CONNECTION
Set conn = New ADODB.Connection
conn.Open "DRIVER={SQL Server};server=servername;database=databasename;UID=username;PWD=password;"
' conn.Open "DSN=datasourcename"
' OPEN AND DEFINE COMMAND OBJECT
Set cmd = New ADODB.Command
With cmd
.ActiveConnection = conn
.CommandText = "sp_ArchiveServer"
.CommandType = adCmdStoredProc
' BIND PARAMETERS BY POSITION AND NOT NAME
.Parameters.Append .CreateParameter("param1", adVarchar, adParamInput, 255, SrvNameVar)
.Execute
End With
conn.close()
Set cmd = Nothing: Set conn = Nothing
End Sub
Create a pass-though query in the Access designer.
You can type in that command in the query (sql view). So, you have a pass-though query,and it will look like this:
EXEC dbo.sp_ArchiveServer #Server='test'
Save the above query. (make sure it is pass through query).
Ok, now your VBA code will look like this:
With CurrentDb.QueryDefs("qryPass")
.SQL = "EXEC dbo.sp_ArchiveServer #Server='" & Me.SrvName & "'"
.ReturnsRecords = False
.Execute
End With
Thank you everyone for your help! With all of the info that you provided, it is now working. To do it, I followed Albert's example creating the Pass Through query first and then appended his code with the information from Parfait and ErikA regarding the connection string. I then added a simple MsgBox command and a Close Form command to make it a little more "pretty". Here is the final code that worked:
Private Sub Command148_Click()
With CurrentDb.QueryDefs("SrvQryPass")
.Connect = "ODBC;DSN=ODBC_17;Description=FSC;Trusted_Connection=Yes;APP=Microsoft Office;DATABASE=FSC;Network=DBMSSOCN;"
.SQL = "EXEC dbo.sp_ArchiveServer #Server='" & Me.SrvName & "'"
.ReturnsRecords = False
.Execute
End With
MsgBox "Archived!"
DoCmd.Close
End Sub

How to dump data from SQL Server into MS Access table using hybrid connections

I need to pull data from SQL server using a query where its statement is stored in a MS access table and dump the data into another table.
There are 2 tables, 01-MyStoredSQLs and 02-tmpTableData
The problem is that there are 2 connections as well. That's why I called it Hybrid.
My Final result is the data from SQL Server into the tmpTableData (result of a query where its statement is stored in the MyStoredSQLs table)
Public Sub DumpSQLServerData()
Dim conn As ADODB.Connection
On Error GoTo errMSG
Set conn = New ADODB.Connection
conn.ConnectionString = "DSN=myDSN; UID=MyID;Pwd=MyPWd"
conn.Open
Dim db As DAO.Database
Set db = CurrentDb
Dim rs As DAO.Recordset
'Retrieving the SQL Statment that is stored in the MyStoredSQLs table
Set rs = db.OpenRecordset("SELECT [Statement] FROM MyStoredSQLs WHERE ID=1")
Dim tmpSQL As String
Dim tmpINTOSQL As String
tmpSQL = rs(0)
'Hybrid statement
tmpINTOSQL = "INSERT INTO tmpTableData" & tmpSQL
conn.Execute (tmpINTOSQL), dbFailOnError
conn.Close
rs.Close
db.Close
Set conn = Nothing
Set rs = Nothing
Set db = Nothing
errMSG:
Debug.Print Err.Description
End Sub
You can't expect SQL server to magically be able to access tables stored elsewhere.
Execute the query in Access, and either use a linked table, or specify the location of the table, e.g.
INSERT INTO tmpTableData SELECT Something FROM [ODBC;DSN=myDSN;UID=MyID;Pwd=MyPWd].Schema.TableInSQLServer
If you want to execute the query on SQL server, not Microsoft Access, then SQL server will need to be able to access the Access database file, and use OPENROWSET to query the Access table.
As an alternate, you can create a passthrough query, and use that to copy the table:
Dim qd As DAO.QueryDef
Set qd = db.CreateQueryDef("~tmpQuery")
qd.Connect = "ODBC;DSN=myDSN;UID=MyID;Pwd=MyPWd"
qd.ReturnsRecords = True
qd.SQL = tmpSQL
Set qd = Nothing
db.Execute "INSERT INTO tmpTable SELECT * FROM [~tmpQuery]"
db.QueryDefs.Delete "~tmpQuery"

Is there some fast way to dump an ADO recordset into a new MS ACCESS table?

We have a SQL Server Backend with MS ACCESS 2016 Front. We have a general purpose ADO connection object that we use to send SQL commands off to our DB. When we have SQL that returns something, we were using code to build a passthru query object, which was a handly way to get SQL output into the frontend.
We are now in a situation where we have some really complex SQL that returns an output, but consists of several statements, including the construction of several temp tables on the server. We can't use the "build a passthru query object" method because passthrus can't handle multiple SQL Statements, and we can't send the SQL statements one at a time because the temp tables get destroyed after the query finishes.
At present, we are now using our ADO objects to get the output into an ADO recordset, then saving the recordset down as an .XML and then finally importing the .XML file into the DB. But this is a lot of I/O and it goes slow for large outputs.
Is there any better (faster) way to get the output of our complex SQL into a local table in the MS Access Frontend?
Heres a snippit of our vba class object:
Private Const DB_CONNECTION_STRING As String = "<our connection string>"
Private conn As ADODB.Connection
Private cmd As ADODB.Command
Private recAff As Long
______________________________________________________________
Private Sub Class_Initialize()
Set conn = New ADODB.Connection
Set cmd = New ADODB.Command
conn.Open DB_CONNECTION_STRING
With cmd
.ActiveConnection = conn
.CommandTimeout = 0
End With
End Sub
...
<more properties/methods>
...
Public Sub LocalizeOutput(fetchSQL As String, InputTableName As String)
'If the InputTableName does not exist, a new table will be created. All fields will be datatyped as Text
'Otherwise, if the InputTableName does exist, data will be appended. Caller is responsible for handling errors
Dim rs As ADODB.Recordset
Dim XMLFilePath As String
Dim domIn As Object
Dim domOut As Object
Dim domStylesheet As Object
XMLFilePath = Environ("TEMP") & "\TableImport.xml"
fetchSQL = "SET NOCOUNT ON; " & vbNewLine & fetchSQL
Set domIn = CreateObject("Microsoft.XMLDOM")
domIn.async = False
'Execute the query
With cmd
.CommandType = adCmdText
.CommandText = fetchSQL
Set rs = .Execute(recAff)
End With
Dim tbl As TableDef
'Save off the output as xml file
If FileExists(XMLFilePath) Then
Call FileDelete(XMLFilePath)
End If
rs.Save XMLFilePath, adPersistXML
'Write the XML Transformation File
Call WriteXSLTransform(InputTableName)
'Convert the ado_xml file into an Access readable xml file
domIn.Load XMLFilePath
Set domStylesheet = CreateObject("Microsoft.XMLDOM")
domStylesheet.Load Environ("TEMP") & "\ADOXMLToAccess.xsl"
Set domOut = CreateObject("Microsoft.XMLDOM")
domIn.transformNodeToObject domStylesheet, domOut
'Set domIn = Nothing
Call FileDelete(XMLFilePath)
'Save the output
domOut.Save XMLFilePath
'Import the saved document into Access
Application.ImportXML XMLFilePath, acAppendData
'clean up
Call FileDelete(XMLFilePath)
Call FileDelete(Environ("TEMP") & "\ADOXMLToAccess.xsl")
Set rs = Nothing
Set domIn = Nothing
Set domOut = Nothing
Set domStylesheet = Nothing
End Sub

Executing stored procedure in sql-server from vb6?

Iam running a legacy VB6 application. I'm trying to execute a stored procedure that would go through a bunch of tables in SQL-SERVER, grab Data and put it into a table in SQL - SERVER. Do I need to declare and set a new recordset?
dim strSQL as string
strSQL = "Exec FillEmptyTable #blah = "& blah
It would seem that I don't need a recordset, but this doesn't execute
Now when i SET a new recordset, then it works
dim rs as adodb.recordset
set rs = new adodb.recordset
dim strSQL as string
strSQL = "Exec FillEmptyTable #blah = "&blah
rs.open strSQL, Connection
Is this right? I don't know why I need a recordset if I'm only creating one on SQL-SERVER side?
If you don't need a recordset because the SP returns no rows or you don't care about any rows it does return you can simply pass the SQL string to the connection object:
Connection.Execute strSQL, 0, adCmdText
See here for a more formal way using a Command object that removes potential the SQL injection vulnerabilities implicit in manually building SQL in a string.

How do I send data from Excel to SQL temp tables for processing using VBA?

My group uses an Excel macro-based tool to do some serious number crunching. I would like to move the number-crunching bit to SQL because using VBA has become intolerable due to the runtime. Users need to be able to use Excel as the interface and also need to be able to run their macros simultaneously as the Excel workbook is self-contained. I've been testing my plan to call an SQL stored procedure from VBA to pull the data from Excel into SQL temp tables, crunch it, and send it back to Excel. I'm able to pull the data from Excel if I run my SP in SQL Management Studio. Here is the SP:
ALTER PROCEDURE sp_test_import
-- Add the parameters for the stored procedure here
#path varchar(1000)
AS
BEGIN
SET NOCOUNT ON;
declare #SQL varchar(2500);
set #SQL = '
select *
from openrowset(''Microsoft.ACE.OLEDB.12.0'',
''Excel 12.0 Macro;
Database='+#path+''',
[Sheet1$]); '
create table [#test](
col1 varchar(15),
col2 varchar(15),
col3 varchar(15),
col4 varchar(15)
)
insert into #test
exec(#SQL)
select * from #mb_test
END
So that works fine. I then try to call this SP from the Excel file containing the data.
Option Explicit
Sub ado_test()
Dim adoConnection As ADODB.Connection
Dim adoRecordset As ADODB.Recordset
Dim connectString As String
Dim strSQL As String
Dim sPath As String
Worksheets("Sheet1").Select
sPath = ThisWorkbook.FullName
'-Create a new ADO connection --
Set adoConnection = New ADODB.Connection
'-Create a new ADO recordset --
Set adoRecordset = New ADODB.Recordset
'-Build our connection string to use when we open the connection --
connectString = "DRIVER=SQL Server;SERVER=MyServer;Trusted_Connection=yes;DATABASE=testDB"
adoConnection.ConnectionTimeout = 20
adoConnection.CommandTimeout = 20
adoConnection.Open connectString
strSQL = "EXEC testDB.dbo.sp_test_import " & vbCr _
& "#path = " & "'" & sPath & "'"
adoRecordset.Open strSQL, adoConnection
End Sub
The code hangs on the 'adoRecordset.Open' call. If I instead pass a path to a separate Excel file in the variable #path, then everything works swimmingly. Is there a simple way that I can make this SP call from the same workbook? I'm not worried about security since the SQL db will be a dedicated structure for pulling in and processing temporary data. I just need users to be able to run their Excel tools whenever they want to, so I don't want to use permanent tables in the DB in case their respective inputs get mixed up together.
Everything I've found online deals with ASP or ISS and I know nothing about ASP and ISS doesn't seem like the right solution to my particular problem. I could have VBA pass the data to external text files and then pass the paths to those text files to the SQL SP, but if there is a cleaner solution then I would like to know about it. Thanks in advance!
I think it is because you are passing in strSQL, a String data type, as the first parameter of the .Open method, but the Open method requires a Command object (according to MSDN).
What you'll want to do is declare an ADODB.Command object and pass that through. I've modified your code to do this:
Option Explicit
Sub ado_test()
Dim adoConnection As ADODB.Connection
Dim adoRecordset As ADODB.Recordset
Dim adoCommand As ADODB.Command
Dim connectString As String
Dim strSQL As String
Dim sPath As String
Worksheets("Sheet1").Select
sPath = ThisWorkbook.FullName
'-Create a new ADO connection --'
Set adoConnection = New ADODB.Connection
'-Create a new ADO recordset --'
Set adoRecordset = New ADODB.Recordset
'-Create a new ADO command --'
Set adoCommand = New ADODB.Command
'-Build our connection string to use when we open the connection --'
connectString = "DRIVER=SQL Server;SERVER=MyServer;Trusted_Connection=yes;DATABASE=testDB"
adoConnection.ConnectionTimeout = 20
adoConnection.CommandTimeout = 20
adoConnection.Open connectString
strSQL = "sp_test_import"
With adoCommand
.ActiveConnection = adoConnection
.CommandText = strSQL
.CommandType = adCmdStoredProc
.Parameters.Refresh
.Parameters(1).Value = sPath
End With
Set adoRecordset = adoCommand.Execute
If adoRecordset.EOF = False Then ThisWorkbook.Worksheets("YourSheetName").Cells(1, 1).CopyFromRecordset adoRecordset
'--adoRecordset.Open adoCommand, adoConnection'
'--Close the database connection'
adoConnection.Close
End Sub
More information on the Command object.
I also added how I get values from SQL Server into the Excel workbook using the CopyFromRecordset method.

Resources