I need to create a pass-through query to SQL Server. I have a functioning pass-through query which only runs from the navigation bar or query design in Access, and it only creates a single record. I have dozens of records to insert into a SQL Server table based on values in an Access table. Thus, I need to loop through the Access table in code, and for each record there create the SQL string and execute it. I've got the looping working, just need the proper code to connect to the SQL database and execute the SQL string. Here's the ODBC Connect String in the pass-through query that works, with the PW and DB starred out: "ODBC;DSN=jobboss32; UID=support; PWD=****;DATABASE=****". The code below gives a Data Type Conversion Error.
Dim strFuturePTOSource, strPassThruInsert, strEmpName, strPTODate, strUpdated As String
Dim datPTODate As Date, lngPTOHours As Long
Dim rs As New ADODB.Recordset, q As QueryDef
strFuturePTOSource = "qryROBERT" 'temporary data set to test, change to actual after working
rs.Open strFuturePTOSource, CurrentProject.Connection, adOpenDynamic, adLockOptimistic
With rs
If Not .BOF And Not .EOF Then
.MoveLast
.MoveFirst
While (Not .EOF)
strEmpName = rs.Fields("EmpName")
strEmpName = "'" & strEmpName & "', "
datPTODate = rs.Fields("PTODate")
strPTODate = "'" & Format(datPTODate, "mm/dd/yyyy") & "'"
lngPTOHours = rs.Fields("PTOHrs") * 60
strUpdated = "'" & Format(Now(), "mm/dd/yyyy") & "'"
strPassThruInsert = "INSERT INTO Attendance (Attendance, Employee, Work_Date, Regular_Minutes, Attendance_Type, Lock_Times, Source, Last_Updated) VALUES (NewID(), "
strPassThruInsert = strPassThruInsert & strEmpName & strPTODate & ",lngPTOHours,2,-1,0, '" & Now() & "');"
Set q = CurrentDb.CreateQueryDef("", strPassThruInsert)
CurrentDb.QueryDefs(q).Execute
.MoveNext
Wend
End If
.Close
End With
First, you not given ANY valid reason to insert with a pass-through query.
And in fact, since EACH insert will be a 100% and separate and “discrete” insert?
Well, the time for the query processor to setup, parse the syntax, and do the insert?
Well, you not achieve ANY gains in performance, and in fact it will run no faster than a plain Jane and standard insert using DAO recodsets.
As a result:
You are knowingly as a developer wasting all kinds of time pursuing this approach and this is AFTER having been given this advice on places such as UA.
And if you knowingly are wasting developer time, and seeking an approach that costs more developer time, does not speed up your insets, then you are with full knowledge seeking an approach that costs more time, and more resources than necessary.
I suppose if you are paying for this time, and don’t care, or perhaps you are doing this as a learning exercise, then fine. But as for a working solution, the approach does not make sense, takes more code and time to develop and fix, and you are doing this will full knowledge as to the increased cost and time without any benefits I can see, or think of.
So then this begs the question?
Why would you attempt a solution will FULL knowledge that costs more time, costs more effort, and does not increase performance?
You do not given any reason as to the added benefits of this approach.
The following code is far less, far more simple, will run FASTER than your given approach. And FAR better is the date conversions that you have (are WRONG), are a non-issue.
So, you have to cook up a VERY good reason as to WHY you are perusing this course of action when:
It will not run faster than using the code I post here
(In fact, such a PT query will run slower – and by A WIDE margin).
You have errors in your date formats – you have to use ISO sql server format
(But, if you use ODBC and let the record set do the translation, then ZERO formatting and ZERO re-formatting of the date and date time values you have is NOT required).
So, until such time you explain why you are willing with full knowledge to write more code, cost more time, and cook up a solution that wastes time, and will not run faster?
Then I see little if any reason to pursue your approach?, right?
So, you need to come up with a VERY good reason as to why you are insisting on this pass-through query approach, and an approach that will run NOT run faster than what I am posting here... And in fact, as I point out, the posted solution will run MUCH faster. (Not I said a wee bit faster – but MUCH faster – in fact on a multiple order (not %, but factors of times faster). I can explain WHY this is the case (better speed) if you are wondering why.
In the meantime, you are going to have to figure out how to sleep with full knowledge of pursuing a solution that is going to cost more in developer time, and not yield any benefits in terms of speed, or even maintenance and writing of such code.
This code eliminates the date error conversions you have, and WILL RUN faster than your posted code and solution:
Dim strFuturePTOSource As String
Dim rstFrom As DAO.Recordset ' from table
Dim rstTo As DAO.Recordset ' SQL server linked table.
Set rstFrom = CurrentDb.OpenRecordset("qryROBERT", dbOpenDynaset, dbSeeChanges)
Set rstTo = CurrentDb.OpenRecordset("Attendance", dbOpenDynaset, dbSeeChanges)
Do While rstFrom.EOF = False
With rstTo
.AddNew
!Attendance = NewID()
!Employee = rstFrom!EmpName
!Work_Date = rstFrom!PTODate
!Regular_Minutes = 600
!Attendance_Type = 2
!Lock_Times = -1
!Source = 0
!Last_UPdated = Now
.Update
End With
rstFrom.MoveNext
Loop
rstTo.Close
rstFrom.Close
Edit
Given that the poster HAS made a good case that a PT query is to be used?
then this code should work:
We assume that you ALREADY created a working PT query. (and it has return records set = false). I tend to create ONE PT query in Access, and then any and all places in code can re-use that PT query at well. Also if NewID() is a scalar function (t-sql), as noted, it MUST be prefixed with dbo. So, we have to use dbo.NewID()
So, the code that is "close" or that I would suggest is this:
Dim rs As DAO.Recordset
Dim strSQL As String
Dim strSQLS As String
strSQLS = "INSERT INTO Attendance (Attendance, Employee, Work_Date, Regular_Minutes, " & _
"Attendance_Type, Lock_Times, Source, Last_Updated) " & _
"VALUES (dbo.NewID(),"
Set rs = CurrentDb.OpenRecordset("qryROBERT")
With rs
Do While .EOF = False
strSQL = strSQLS
strSQL = strSQL & qu(!EmpName) & "," & quDate(!PTODate) & _
"," & quDate(Date) & "," & (!PTOHrs * 60) & ",2,-1,0," & quDate(Now())
With CurrentDb.QueryDefs("MyPassQuery")
.SQL = strSQL
.Execute
End With
.MoveNext
Loop
rs.Close
End With
In addtion to the above, I used two helper functions to format the date, as it is a pain to do this "in-line" code, so, I have qu() (for strings), and qudate() for dates.
Public Function qudate(myDate As Variant) As String
' returns a formatted string of date, ISO format, for sql sesrver.
' format is yyyy-mm-dd regardless of local date settings
If IsNull(myDate) = True Then
qudate = ""
Else
' use ISO date format
qudate = "'" & Format(myDate, "yyyy-mm-dd HH:NN:SS") & "'"
End If
End Function
And our qu() function
Function quS(vText As Variant) As String
' takes a string and surrounds it with single quotes
If IsNull(vText) = False Then
If InStr(vText, Chr(34)) > 0 Then
vText = Replace(CStr(vText), Chr(34), "'")
End If
End If
quS = "'" & vText & "'"
End Function
Edit 2
So, the steps are:
From sql studio, get a example working that inserts data. Once you have a working insert command, then take that same (and known) working command and cut + paste it into an access query - but just ensure that the query on the Access side is created and set as pass-though. Once again, test running this PT query. If the existing sql worked in SSMS, then it will work in Access 100% exactly with the SAME query. Once that is working?
so, whatever name you gave this query is what you use in place of MyPassQuery. You can give it any name you want, but you have to use the correct (same) name in your VBA code that going to use/set the sql for that PT query. So, each loop run will in fact overwrite what is in the query, and then you do the .Execute to run it.
Well, then you run your code. And on this line for testing?
.SQL = strSQL
Do this:
debug.print strSQL
.SQL = strSQL
So, as you single step though that code, the sql the VBA is creating will have to match the known working sql you had. So, if the string output has any syntax errors or does not look 100% the same as that known working sql? Well, then you have to tweak the VBA code until such time it spits out the SAME sql string that you know works.
I would like to have an Access VBA sub that sends an instruction to a SQL Server database to copy some data from one of the remote tables to another table (at least one of the tables is not linked to the local Access database). I want this to all happen on the server, as this is a lot of data.
I'm trying something like the following, but it returns an run-time error 3065 (Cannot execute a select query). Any insight into how to fix? Is there some reason I couldn't do this from Access?
Also, I'm using a DAO approach, but is there a better approach (ADO?)? Somewhat new to this so not always sure I understand the nuances of the different approaches.
Public Sub myTest()
Setup:
Dim dbs As DAO.Database
Dim qdf As DAO.QueryDef
Set dbs = CurrentDb()
Set qdf = dbs.CreateQueryDef("")
qdf.SQL = "INSERT INTO tmp SELECT [Applicant], [CaseName], [DecisionDate], [Filed], [Docket] " _
& "FROM Cases WHERE [DecisionDate] >= '01/01/2018';"
qdf.Connect = "ODBC;Driver={SQL Server};server=myServer;database=myDB;"
qdf.Execute
End Sub
What you have looks ok. I would “test” the sql first by running the exact same command by using SQL management studio.
As for your code? It also looks ok, but I find it MUCH better to save a PT query and thus you don’t have to mess with the connection string in code. And thus your code becomes:
With CurrentDB.querydefs("MyPassR")
.SQL = "INSERT INTO tmp SELECT [Applicant], [CaseName], [DecisionDate], [Filed], [Docket] " & _
"FROM Cases WHERE [DecisionDate] >= '01/01/2018';"
.ReturnsRecords = False
.Execute
End With
The weirdest thing. I have a simple procedure that I developed in Microsoft Access 2010 with a SQL Server 2012 backend. I am now trying to deploy this into production which is Access 2016 and a SQL Server 2014 backend.
I've compiled, compact and repaired in the new environment... but I can not get Access to execute this simple stored procedure. Even worse it still executes several other stored procedures fine... but a couple of them it times out and refuses to execute?
Here is my VBA and stored procedure:
Private Sub GenerateUnitKey(UnitColumns As String)
Dim Msg, Style, Title, Response As Variant
Dim lngProcessID As Long
Dim Conn As ADODB.Connection
Dim Cmd As ADODB.Command
Dim CurrentConnection As String
CurrentConnection = LinkMasterConnection()
Msg = "Are you sure you want to update the UnitKey with the selected columns?"
Style = vbYesNo + vbCritical + vbDefaultButton2
Title = "Save Campaign?"
Response = MsgBox(Msg, Style, Title)
If Response = vbYes Then
Call OpenSixHatLoader("Generating Unit Key Across Campaign Records", 1, "")
Set Conn = New ADODB.Connection
Conn.Open CurrentConnection
Set Cmd = New ADODB.Command
With Cmd
.ActiveConnection = CurrentConnection
.CommandText = "usp_GenerateUnitKey"
.CommandType = adCmdStoredProc
.CommandTimeout = 30
.Parameters.Append .CreateParameter("#UnitColumns", adVarChar, adParamInput, 4000, UnitColumns)
.Execute
End With
End If
End Sub
And stored procedure:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[usp_GenerateUnitKey]
#UnitColumns AS VARCHAR(4000)
AS
SET NOCOUNT ON
DECLARE #SQL AS VARCHAR(MAX)
UPDATE tblStagingTable SET UnitKey =''
SET #SQL = 'UPDATE tblStagingTable SET UnitKey = ' + #UnitColumns + ' FROM tblStagingTable st'
EXEC(#SQL)
-- UPDATE Interests to match Staging Table
UPDATE tblInterests SET UnitKey = st.[UnitKey] FROM tblInterests i
INNER JOIN tblStagingTable st ON i.StagingTableID = st.StagingTableID
I am fairly confident there is nothing wrong with the code... as I said it worked fine in my development environment... even more I am manually able to execute the stored procedure within SQL Server. My SQL Server Native Client 11.0 connection works in executing other stored procedures... but for a couple of them it does not work. I am thinking I need to configure something within SQL Server itself or maybe within the Native Client 11.0 driver?
Unfortunately it gives no exception. I've set the CommandTimeout property to 0 and let it chug for a few hours hoping it would throw and exception to give me a clue but nothing... it just was frozen trying to execute. Any suggestions or ideas would be greatly appreciated because this one has me really stumped because it should be fine!
I would first launch SSMS, and from the SQL studio type in
Exec xxxxx ''
And ensure it runs (and use the SAME logon and connection to SSMS that you currently have for Access.
I would also consider creating a pass-though query, and saving that query in access. (set returns records = false if the sp does not return records). Then in code to run any proc, you can go:
With CurrentDb.QueryDefs("qryPass")
.SQL = "exec usp_GenerateUnitKey '" & UnitColumns & "'"
.Execute
End With
You note how simple the above code is - so if sp works from SSMS, then try the above code.
This was a difficult one that took me about 3 solid days of troubleshooting to get a solution to. Although I am not satisfied with the end solution as it should have just worked... but in the end my theory of the server being an Virtual Machine proved correct. When I deployed this exact same setup to Microsoft Access 2016 32 bit and SQL Server 2014 32 bit on a dedicated server it worked exactly as it was supposed to compared to the Azure VM and 1&1 Cloud Servers I had attempted to deploy to.
SQL Server integration with VM's is getting better from what all I have read, but apparently there is a ways to go. Maybe SQL Server needs to release a special VM version. Thank you to all those who took the time to look into this.
I have a stored procedure which when run from SQL Server Management Studio consistently takes 5 seconds to run when called like this.
exec dbo.MyStoredProc '2009-04-30 00:00:00', '2009-04-30 20:00:00'
When called from an excel spreadsheet via VBA it takes 6 minutes plus (not including the time taken to copy the recordset to a sheet. The VBA is nothing fancy simply using an ADO connection to return a recordset. Unfortunately the Excel approach is a client requirement that I can't get rid of yet.
Public Function GenerateSQL(strQueryName As String) As Recordset
Dim rs As Recordset, cm As Command, dbsConn As Connection
Set dbsConn = New ADODB.Connection
dbsConn.Open Configuration.Range("ConnectionString")
Set cm = New ADODB.Command
With cm
.CommandText = strQueryName
.CommandType = adCmdStoredProc
.CommandTimeout = 300
.ActiveConnection = dbsConn
Set rs = .Execute()
End With
Set GenerateSQL = rs
End Function
Does anyone have any idea why this would happen or how I could begin to trace what is happening?
Thanks,
Steve
Everything you need to know about this topic: Slow in the Application, Fast in SSMS? Understanding Performance Mysteries
I believe I have the same problem as Steve Homer.
In addition to this SO question I also found this thread on eggheadcafe.com Very slow SP execution when using .net - very fast in Management Studio - totico
The answers say it's about parameter sniffing and how that affects which execution plan is used. The answers there specifically mentions the arithabort set option and how that affects the selection of plan.
Now I just need to find out how to change the set options from VBA...
Finally thanks to this forum entry on social.msdn.com i managed to get it right. First, set multiple connections to false:
connectionObject.Properties("Multiple Connections") = False
and then use the following function on your connection to set arithabort on ...
Private Sub OptionSet(ByRef cnn As adodb.Connection)
Dim cmd As adodb.Command
Set cmd = New adodb.Command
With cmd
Set .ActiveConnection = cnn
.CommandType = adodb.CommandTypeEnum.adCmdText
.CommandText = "set arithabort on"
Call .Execute
End With
Set cmd = Nothing
End Sub
Use SQL Server Profiler
Set up a trace on your database.
Limit the trace only to the stored procedure object in question
Limit to the username used by the VBA code
An introduction to SQL Server Profiler
In particular, check the SET options used by the connection and compare these with the defaults used when running the stored procedure in SSMS.
I have come across scenarios before where the SET options were different between calling code and within SSMS and the difference in performance was HUGE.
Thanks I'll take a look at the trace tools.
In reply to the comments on the original question
Are you using the exact same parameter values?
Yes exactly the same.
How much data is being returned (roughly) - number of rows and columns (and are any of them particularly big)?
Under 200 rows, perhaps 15 fields mostly ints with a couple of 20 character varchars.
Can you run SQL profiler and confirm if the sql is the issue or the remains of the macro in excel ?
The SQL is pretty ugly, as is the underlying database schema, and unfortunately is under NDA so I can't post it. If the query were the issue then wouldn't it be slow in management studio too though?
I have a script that runs a stored procedure in my SQL server database, the problem is the stored procedure takes a uniqueidentifier parameter. I have a function that grabs a session id from the database (which is an nvarchar), so VBScript makes it a string and I need to convert it to pass it to the stored procedure.
Function GetOpenSession
Set conn = CreateObject("ADODB.Connection")
Set rs = CreateObject("ADODB.Recordset")
conn.Open "Provider=SQLOLEDB.1;Integrated Security=SSPI;Data Source=" & Source
rs.CursorLocation = 3
rs.Open "SELECT top 1 OpenSession FROM OpenSessions with (nolock)" , conn, 3, 3
If rs.RecordCount = 0 Then
MsgBox "No Connection"
Else
GetOpenSession = rs.Fields(0).Value
End If
End Function
I get "Invalid character value for cast specification" when I try to execute the stored procedure.
set cnParam = cmd.CreateParameter("#ActiveSession",72,1,,GetOpenSession)
cmd.Parameters.Append cnParam
I can't change anything in the database, so I need a way to overcome this in my script.
I believe VBScript expects GUIDs to be brace terminated.
Is your Session id of the same format as the following {D6CA6263-E8E1-41C1-AEA6-040EA89BF030}
Depending on the data type of the SELECT OpenSession, you may be able to cast/convert it in the query and VBScript may possibly keep the data type as a GUID:
SELECT top 1 CONVERT(uniqueidentifier, OpenSession)
FROM OpenSessions with (nolock)
When you use GetOpenSession or rs.Fields(0).Value, hopefully VBScript will keep it as a GUID.
The other possibility seems to be a Win32 API using CoCreateGuid and StringFromGUID2. An example is found here, but it requires external Win32 functions and a Type for GUID.
I usually change the parameter type of the stored procedure to varchar(38).
SQL Server makes a better job of coercing the string to a GUID when needed.
What is the value of GetOpenSession after you have assigned it? Which datatype? It sounds like it is not compatible with type 72 (GUID), which you are stating in the CreateParameter call.
You could forget about using ADO Commands altogether and just execute the stored procedure using plain SQL.
Set rs = CreateObject("ADODB.Recordset")
rs.Open "EXEC usp_MySP '" & GetOpenSession & "'", cnn
Obviously it's terrible to build SQL commands like this, but it's just for a test, after all...