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
Related
I've a vendor table in SQL Server with vendor code, vendor ID and vendor name. Another table called disc_mast with columns vendor ID and disc_per. Vendors will revise disc% frequently and we will get the details in Excel with vendor code and disc%. How can I update the discount percent in the disc_mast from the data in excel without importing the data in Excel to SQL Server tables.
You can do something like this.
Sub Update()
'Declare some variables
Dim cnn As adodb.Connection
Dim cmd As adodb.Command
Dim strSQL As String
'Create a new Connection object
Set cnn = New adodb.Connection
'Set the connection string
cnn.ConnectionString = "Your_Server_Name;Database=Northwind;Trusted_Connection=True;"
'Create a new Command object
Set cmd = New adodb.Command
'Open the connection
cnn.Open
'Associate the command with the connection
cmd.ActiveConnection = cnn
'Tell the Command we are giving it a bit of SQL to run, not a stored procedure
cmd.CommandType = adCmdText
'Create the SQL
strSQL = "UPDATE TBL SET JOIN_DT = 2013-01-13 WHERE EMPID = 2"
'Pass the SQL to the Command object
cmd.CommandText = strSQL
'Open the Connection to the database
cnn.Open
'Execute the bit of SQL to update the database
cmd.Execute
'Close the connection again
cnn.Close
'Remove the objects
Set cmd = Nothing
Set cnn = Nothing
End Sub
This is VBA code that runs in Excel. Also, create a reference to 'Microsoft ActiveX Data Objects x.x Library'.
Tools -. References:
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"
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.
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.
I have an Access DB that has a bunch of linked tables from a SQL Server database. The Access DB calls a stored procedure on the SQL Server database that updates data on a form.
Dim sql As String
Dim cnn As ADODB.Connection
Set cnn = New ADODB.Connection
cnn.ConnectionString = "DSN=Records"
cnn.CommandTimeout = 90
cnn.Open
sql = "exec myStoredProcedure #param1=" & Me.txtParam1Field & ", #param2=" & Me.txtParam2Field
cnn.Execute sql
Set cnn = Nothing
frmMyForm.Requery
When I run this it either times out, if the CommandTimeout value isn't long enough, or it executes, but doesn't actually execute myStoredProcedure for some reason. If I take the string sql and past it into Sql Server Manager, myStoredProcedure executes in less than a second and everything works great.
I've tried debugging over this code in Access, but I'm not getting any useful results when I step over cnn.Execute sql.
Depending on the values of txtParam1Field and txtParam2Field you probably want to enclose the values with single quote like so:
sql = "exec myStoredProcedure #param1='" & Me.txtParam1Field & "', #param2='" & Me.txtParam2Field & "'"
If we take your original code and assume that txtParam1Field is equal to 1 and txtParam2Field is equal to John then your generated sql will not execute because it will look like this:
exec myStoredProcedure #param1=1, #param2=John
Your best bet is to output the value of "sql" variable in debug window and run that exact statement in sql query manager. That will tell you exactly where the problem is if it's malformed SQL.
You could try setting the Prepared property to false on the command object. This causes a recompile of the procedure before execution, but could result in a better plan depending on the parameters that are sent.
Dim sql As String
Dim cnn As ADODB.Connection
Dim Cmd As ADODB.Command
Set cnn = New ADODB.Connection
cnn.ConnectionString = "DSN=Records"
cnn.CommandTimeout = 90
cnn.Open
sql = "exec myStoredProcedure #param1=" & Me.txtParam1Field & ", #param2=" & Me.txtParam2Field
Set Cmd = New ADODB.Command
Set Cmd.ActiveConnection = cnn
Cmd.CommandType = adCmdText
Cmd.CommandText = sql
Cmd.Prepared = False
Cmd.CommandTimeout = 300
Cmd.Execute