MS Access crashes binding RS to a form from SQL Server stored procedure - vba

I am just starting to move our Access DB to SQL Server and am having trouble.
I have a stored procedure that successfully returns rows to an ado recordset.
When I try to bind the rs containing the results of the stored procedure to the Access form, Access crashes without displaying any error messages. I'm on O365 32b and SQL Server 2019.
Here's the code:
Dim sSQL As String, rs As ADODB.Recordset
1 sSQL = "Exec usp_TaskStatusWidget " & Me.Tag & ",0"
2 ADOConn.ConnectionString = conADO
4 ADOConn.Open
6 Set rs = New ADODB.Recordset
7 rs.CursorLocation = adUseClient
8 rs.Open sSQL, ADOConn
10 Set Me.Recordset = rs ' Access crashes here
. . .
Any help would be greatly appreciated!
tia.
SR

Ok, are you previous using ADO, or are you just introducing this?
In most cases, you are better off to just use a view. (replace the access query with a linked view), and then continue useing client side where clauses or filters (access will ONLY pull down the rows you request). So linked views are often a better choice and much less work (in fact, even existing filter for a open report etc. will work and only critera matching the were clause records are pulled.
And in most cases, i don't introduce ADO.
So for a PT query, I often do this:
dim rs as DAO.RecordSet
with CurrentDb.queryDefs("qryPt")
.SQL = "Exec usp_TaskStatusWidget " & Me.Tag & ",0"
set rs = .OpenRecordSet
end with
So, above assumes you have a pt query called qryPt. This also means that you never deal with or worry about connection strings in code. The pt query has the connection. (and your re-link code now can re-link tables and pt queries).
I ONLY suggest the above as a FYI in case that you introducing ADO for calling store procedures, and the rest of the application was previous DAO. If the application was previous DAO, then leave it alone, and use above approach for your PT queries - even code that needs to call store procedures.

Access tends to try and parse the query text to get filters/sorts/etc to work, and if it isn't a plain syntax error but isn't Access SQL either, strange things tend to happen, mostly crashes.
Try adding a comment up front to make sure Access knows not to parse:
sSQL = "-- Access no parse pls" & vbCrLf & "Exec usp_TaskStatusWidget " & Me.Tag & ",0"
The content of the comment is not relevant, of course, its purpose is to immediately cause a syntax error when Access tries to parse it as Access SQL (which doesn't have comments)

Related

In Excel VBA run SQL "SELECT ... INTO ... IN ...." Statement

I can't seem to find any good reference or example of how to get this to work. I have a database which is stored on an AS/400 (my local MS Access database [stored on a network drive] has linked tables to the 400, using ODBC/DSN). My utility works just fine passing SQL statements to through Access to retrieve data from the 400 using the linked tables. The problem is that with some of the larger reports and the fact that the 400 is several states away, it can take several hours to run the reports. The settled on solution to this is to create a local "copy" of the tables needed with just the data set that is relevant to the reports, which is a considerably smaller data set. Obviously this has the down side of not being "live" data but I can live with that. Ultimately what I want to do is gather the relevant data from the linked table and save it to separate database that is local to the client so that it could be used if offsite/offline and to increase the speed of the report.
network location stored database = DB1 (Tabled linked to AS/400)
local client stored database = DB2 (relevant data set created by below SQL, non-linked tables named the same as the linked tables)
Below is the SQL statement that I'm trying to get to work using VBA & DAO
SELECT
DB1_TABLEA.FIELD1,
DB1_TABLEA.FIELD2,
DB1_TABLEA.FIELD3,
DB1_TABLEA.FIELD4,
DB1_TABLEA.FIELD5,
DB1_TABLEA.FIELD6,
DB1_TABLEA.FIELD7,
DB1_TABLEA.FIELD8
INTO
DB1_TABLEA IN 'Local_DB_Copy.accdb' <== Creating non-linked copy
FROM
DB1_TABLEA
WHERE
(
((DB1_TABLEA.FIELD4) Like 99999)
AND
((DB1_TABLEA.FIELD6)="02" Or (DB1_TABLEA.FIELD6)="22")
)
;
I already have my program working fine and returning/processing data from the AS/400 DB. I just need to be able to get the above to work so that people have the option to run a local copy that will process much faster.
Below is the code that I tried, but of course it fails or I wouldn't be here.
Sub gCreateLocalDBTables()
Dim DBPath As String
Dim LocalDBPath As String
Dim sSQL As String
Dim DB As DAO.Database
Dim DB2 As DAO.Database
Dim RS As DAO.Recordset
LocalDBPath = "AS400_Local.accdb"
sSQL = "SELECT DB1_TABLEA.FIELD1, DB1_TABLEA.FIELD2, DB1_TABLEA.FIELD3, DB1_TABLEA.FIELD4, DB1_TABLEA.FIELD5, DB1_TABLEA.FIELD6, DB1_TABLEA.FIELD7, DB1_TABLEA.FIELD8 INTO DB2_TABLEA IN '" & LocalDBPath & "' FROM DB1_TABLEA WHERE (((DB1_TABLEA.FIELD4) Like 99999) AND ((DB1_TABLEA.FIELD6)='02' Or (DB1_TABLEA.FIELD6)='22'));"
Set DB = OpenDatabase(LocalDBPath, False, False)
DB.TableDefs.Delete ("DB2_TABLEA")
DB.Close
DBPath = Interaction.GetSetting("Cust_Tools", "Settings\Report_Planning", "400DB_Location")
Set DB2 = OpenDatabase(DBPath, False, False)
Set RS = DB2.OpenRecordset(sSQL)
RS.Close
DB2.Close
Set RS = Nothing
Set DB = Nothing
Set DB2 = Nothing
End Sub
I know the SQL works as I have tested it from inside MS Access. I just can't find info on how to get it to work being passed from Excel VBA
You cannot assign an action query like a make-table query (i.e., SELECT with INTO call) to a recordset. Consider executing your DROP and SELECT ... INTO action queries prior to opening recordset on the local table. Also, it is unclear why you are opening a second database or what the path points to. Below opens a recordset on the mainframe data:
Set DB = OpenDatabase(LocalDBPath, False, False)
DB.Execute "DROP TABLE DB2_TABLEA", dbFailOnError
DB.Execute sSQL, dbFailOnError
Set RS = DB.OpenRecordset("SELECT * FROM DB2_TABLEA")
Furthermore, the IN clause in your make-table query is unnecessary as you are currently connected to the very database you are running the action on. Simply remove it ('" & LocalDBPath & "'). Also, LIKE expressions without wildcards and on numbers should be replaced with =
SELECT
DB1_TABLEA.FIELD1,
DB1_TABLEA.FIELD2,
DB1_TABLEA.FIELD3,
DB1_TABLEA.FIELD4,
DB1_TABLEA.FIELD5,
DB1_TABLEA.FIELD6,
DB1_TABLEA.FIELD7,
DB1_TABLEA.FIELD8
INTO
DB2_TABLEA
FROM
DB1_TABLEA
WHERE
(
((DB1_TABLEA.FIELD4) = 99999)
AND
((DB1_TABLEA.FIELD6)='02' OR (DB1_TABLEA.FIELD6)='22')
)
;
In fact, consider saving the query inside the MS Access database (Ribbon -> Create -> Query Design -> SQL View) and call it as a named object and avoid any long SQL in VBA.
DB.Execute "DROP TABLE DB2_TABLEA", dbFailOnError
DB.Execute "mySavedQuery", dbFailOnError
Set RS = DB.OpenRecordset("SELECT * FROM DB2_TABLEA")

VBA - MariaDB - Query cannot be updated because it contains no searchable columns to use as a key

I am using MariaDB and VBA to read/write a flat database using ADODB. This is not by choice however I've been asked to make it work in this manner. An alternative would be to directly use SQL queries however this is a port of a very old VB3 application.
Here is my code that connects to the database, pulls back all records, updates the last record, then calls update to effectively write it back to the database
Global DB As New ADODB.Connection
Global TD As New ADODB.Recordset
DB.Open "Driver={MariaDB ODBC 2.0 Driver};Server=localhost;UID=???;PWD=???;DB=sf_log;Port=3306"
Dim query As String: query = "SELECT * FROM `" & TableName & "` ORDER BY `Record ID`"
TD.CursorLocation = adUseServer
TD.CursorType = adOpenDynamic
TD.Open query, DB, adOpenKeyset, adLockOptimistic
TD.MoveLast
Dim TestColumnField as string
TestColumnField = TD.Fields("TestColumn") 'This returns the correct value from the database indicating the connection was successful
TD.Fields("TestColumn") = "test"
TD.UpdateBatch (adAffectCurrent) ' This line throws the error
Error that is reported is "Query cannot be updated because it contains no searchable columns to use as a key"
The database is a flat relationship-less database with no keys. I have tried setting "record id" to be a primary key with no luck.
Is this error due to MariaDB not implementing/supporting ADODB recorset? Is it due to my database structure? Or is it simply I am utilising the ADODB recorset incorrectly?
Edit: Here is an example that shows you do not need an SQL update statement. You can simply select the data and call Update.
http://www.accessallinone.com/updating-adding-and-deleting-records-in-a-recordset/
Sub ADODBUpdating()
Dim sql As String
Dim rs As adodb.Recordset
sql = "SELECT * FROM tblTeachers WHERE TeacherID=5"
Set rs = New adodb.Recordset
rs.Open sql, CurrentProject.Connection, adOpenDynamic, adLockOptimistic
'Open RecordSet
With rs
If Not .BOF And Not .EOF Then
.MoveLast
If .Supports(adUpdate) Then
![FirstName] = "x" & ![FirstName]
.Update
End If
End If
.Close
End With
Server side cursor implementation seems to be limited, use client side cursors (DB.CursorLocation = adUseClient) instead.
1.) You can't do an update in SQL with a SELECT-Statement. Instead use the UPDATE-Statement.
2.) Dont't try to update all the records. Only Update the single row you want to update. As you write this should be the last record.
You update the testfield and you have to be sufficiently specific in the WHERE-Clause, that you only select the one and only record record you want to update.

Calling scalar UDF function from ms access

I am trying to call a scalar function from my database in ms access.
This function should return one row of my salted and hashed parameters.
My initial attemt looked like this and returned the following error: "undefined function 'dbo.SaltAndHashPassword' in expression"
Dim rs As Recordset
Set rs = CurrentDb.OpenRecordset("SELECT dbo.SaltAndHashPassword('" & Me.txtWW & "','" & Me.salt & "') as salted", dbOpenSnapshot, dbReadOnly)
I tried the solution from this question but I got the error "object required"
I also tried the solution from this question but I can't get it to work(probably due to wrong parameters)
Can anybody help me with this problem?
Thanks in advance.
You can't use CurrentDbin that case. You have to set a Connection object to the server/db where the dbo.SaltAndHashPassword function is stored (the example you mention uses a connection, even if the code to set it up is not shown), since it is totally unknown from Access.
Alternatively you can create a passthru query in Access and use DAO. I find it easier and it avoids creating yet another external reference.
Something like:
Set db = CurrentDb()
'Create a temporary passthrough query '
Set ptq = db.CreateQueryDef("")
'set ODBC connection '
ptq.Connect = "your connect string"
ptq.SQL = "SELECT * from your SQL"
ptq.ReturnsRecords = True
Set rs = ptq.OpenRecordset(dbOpenSnapshot)

Can I create a pass-through query via VBA to call a parameterized tSQL UDF and send it a dynamic parameter to return results to Access?

I currently have a SQL 2008 R2 database backend with an Access 2013 accdb front end with ODBC DSN-less connection and linked tables. In SQL I have many parameterized tSQL UDFs created to feed data into reports (currently working well in my Access 2010 adp frontend). The reports are complicated: multiple tSQL UDFs run calculations and then feed into a final UDF that feeds the respective report. I would like to keep the UDFs on the server – rewriting into Access queries would be a poor solution.
My problem is that I have not been able to figure out how to write the VBA correctly to send a pass-through query to call the tSQL UDF and give it a parameter, which would change for each report. I know pass-through queries are read-only, that’s ok. I’ve read that I can call a stored procedure (SP) from VBA, but can I call the UDF rather than having to convert each to a SP or create a SP just to call the UDF so that I could call the SP from VBA. Based on my research, I think I might have to either create a SP to call the UDF or convert the UDF to a SP to get the VBA to work (i.e., return results without error). Is this correct?
I found this discussion: https://social.msdn.microsoft.com/Forums/office/en-US/898933f5-73f9-44e3-adb9-6aa79ebc948f/calling-a-sql-udf-from-access?forum=accessdev , but it has conflicting statements “You can't call a tSql udf from Access.”, and “You can use a passthrough query to call UDF's or stored procedures or anything else written in tsql.” Also, their code is written in ADO instead of DOA so it’s a bit cryptic to me since I’ve only written DAO so far, but the general gist that I got was they converted their UDF to a SP.
I found this article a great read, but again did not get a clear “yes” to my question:
http://technet.microsoft.com/en-us/library/bb188204(v=sql.90).aspx
It may be possible to remove the parameter from the Server side and add it to the Access side similar to this Access 2007 forms with parameterized RecordSource , but wouldn't that cause Access to load the entire dataset before filtering, instead of processing on the Server side – possibly causing performance issues?
I can successfully create a pass-through query in the Access interface if I supply it with a constant parameter, for example “Select * from udf_FinalReport(2023)”, but what I really need is to be able to pass a dynamic parameter. For example, the parameter would be from Forms!Control![txtboxValue]. Can I do this? The following code is what I’m using– it works if I use a table name in the SQL (ex, “SELECT * FROM Table WHERE tblYear = “&intYear ) in line 9 so I feel like I have everything coded right, but when I put my UDF in the SQL like below I get the error #3131 “Syntax error in FROM clause.” (I did verify that I should not use the prefix schema (dbo.) – this gives error 3024 “could not find file”.) Is this user error or just plain telling me I can’t call a UDF this way?
1 Sub AnnualSummary()
2 Dim dbs As DAO.Database
3 Dim qdfPoint As DAO.QueryDef
4 Dim rstPoint As DAO.Recordset
5 Dim intYear As Integer
6 intYear = Reports!Annual_Delineation_Summary!txtYear
7 Set dbs = OpenDatabase("", False, False, "ODBC;DRIVER=sql server;SERVER=******;APP=Microsoft
8 Office 2010;DATABASE=*******;Network=DBMSSOCN")
9 Set qdfPoint = dbs.CreateQueryDef("", "Select * from udf_AnnualReport(" & intYear& ")")
10 GetPointTemp qdfPoint
11 ExitProcedure:
12 On Error Resume Next
13 Set qdfPoint = Nothing
14 Set dbs = Nothing
15 Set rstPoint = Nothing
16 Exit Sub
17 End Sub
18
19 Function GetPointTemp(qdfPoint As QueryDef)
20 Dim rstPoint As Recordset
21 With qdfPoint
22 Debug.Print .Name
23 Debug.Print " " & .SQL
24 Set rstPoint = .OpenRecordset(dbOpenSnapshot)
25 With rstPoint
26 .MoveLast
27 Debug.Print " Number of records = " & _
28 .RecordCount
29 Debug.Print
30 .Close
31 End With
32 End With
33 End Function
I also tried writing the code a little differently, using the following instead of lines 5, 6, and 9. This also works when I use a table name in the select statement, but I get error #3131 when I use a UDF name:
Set qdfPoint = dbs.CreateQueryDef("", "Parameters year int; Select * from Point_Info where
year(Sample_Date) = year")
qdfPoint.Parameters("year").Value = intYear
Both code variations also work if I try use the name of a SQL View in the tSQL SELECT statement.
My consensus is using ADO language instead of DAO to write the pass-through query works well. But, I have found that it is still probably better to execute a stored procedure than to try to call the UDF. Here is the code that ended up working most smoothly for me: (my ADO Connection uses Public variables strUID and strPWD)
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim strPoint As String
strPoint = Forms!FRM_Vegetation_Strata!Point_ID
Set cn = New ADODB.Connection
cn.Open "Provider = sqloledb;Data Source=imperialis.inhs.illinois.edu;" & _
"Initial Catalog=WetlandsDB_SQL;User Id=" & strUID & ";Password=" & strPWD
Set rs = New ADODB.Recordset
With rs
Set .ActiveConnection = cn
.Source = "sp_Report_VegWorksheet '" & strPoint & "'"
.LockType = adLockOptimistic
.CursorType = adOpenKeyset
.CursorLocation = adUseClient
.Open
End With
Set Me.Recordset = rs
On a side note I found that to get set the .Recordset to fill a subform put this code in the "Open" event of the subform.
Then to clean up your connection:
Private Sub Form_Unload(Cancel As Integer) 'use "unload", not "close"
'Close the ADO connection we opened
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Set cn = Me.Recordset.ActiveConnection
cn.Close
Set cn = Nothing
Set rs = Nothing
Set Me.Recordset = Nothing
End Sub
This approach does not work for populating a report. "Set Me.Recordset" only works for forms. I believe I will have to call a stored procedure then populate a temp table to use as the report recordset.
EDIT: I have found that I can call a SQL UDF or SP from VBA in Access using DOA. This is particularly helpful when one wants to pull the data from a complicated SQL function/procedure and put it into an Access-side temp table. See Juan Soto's blog https://accessexperts.com/blog/2012/01/10/create-temp-tables-in-access-from-sql-server-tables/#comment-218563 This code puts the info into a temp table, which is what I wanted to populate my reports. I used his code example and the following to call the sub:
To execute as SP: CreateLocalSQLTable "testTBL","exec dbo.sp_Report_WetDet_point '1617-1-1A'",False
To call a UDF: CreateLocalSQLTable "testTBL","Select * from dbo.QryReport_Main('1617-2-2A')",False
I don't know if it's the most efficient method of passing a variable parameter through a pass-through query into a function and returning the results to Access, as I am still relatively new to Access, but I came across this earlier when I was attempting a similar problem.
I managed it by creating a couple of pass-through queries that executed functions in SQL server and returned a result. I then made a small VBA script that re-wrote the pass-through queries with the new variable every time I wanted to change it, and executed them.
I got the result back out using OpenRecordset, and stored it as a string to use in the rest of my code.

Using Parameters in SQL query with sub-query

I have a fairly complicated SQL query with a nested subquery. When I try to use parameters in Microsoft Query is say I can use parameters in queries that cant be represented graphically. So I need another option. I think you can place your SQL query in a cell as a string then have a Macro run it. Any ideas how I could do this?
Thanks
-Jesse
Here's what I do to work around the limitations of Microsoft Query in Excel 2007:
A produce a dummy query (SELECT NULL AS Test, for example) in Microsoft Query and insert it into the worksheet.
Right-click on the table that MS Query just inserted and click Table->Edit External Data Properties.
Click on the Connection Properties button, then click the Definition tab.
In the Command Text section, write out or paste in the query that you want, using the usual '?' convention for parameters, then click OK.
Click OK to exit the External Data Properties window.
Right click on the table again, and select Table->Parameters to bind the parameters in the usual way.
The idea is the bypass the GUI that MS Query provides, which has some arbitrary limitations that the underlying engine does not.
This works for many complex queries, but not all. When I encounter a query that MS Query refuses to digest at all, I either refactor the query (when feasible) or create a VIEW on the SQL server and query against that.
Another way to solve this is to use stored procedures
CREATE PROCEDURE [dbo].[yourprocedure] #DATEFROM DATETIME, #DATETO DATETIME
AS
SELECT Query
where date >= #datefrom
and date <= #dateto
then on the table properties click Connection Properties button, then click the Definition tab. In the Command Text section:
EXEC yourprocedure #DATEFROM = ?, #DATETO = ?
and direct the ? to the cells you want
Unfortunately the ? doesn't work for most of my queries and a lot of them are not necessarily suited to being turned into views.
The main alternative I use is getting a macro to return the code
Dim Con As New ADODB.Connection
Dim RS As New ADODB.Recordset
Dim server, Database As String
Dim Data as Worksheet
Set data = ThisWorkBook.Worksheets("data")
'rename field here and elsewhere to your variable eg SD or StartDate
Dim field as string
server = "servername"
Database = "database"
'set connection string
If Con.State <> 1 Then
Con.ConnectionString = "Provider=SQLOLEDB;Data Source=" & server & ";Initial Catalog=" & Database & ";Integrated Security=SSPI;"
'this is just setting the connection time out to infinite
setcono:
Con.ConnectionTimeout = 0
Con.CommandTimeout = 0
'this is making sure it set the connection time out to infinite
If Con.ConnectionTimeout > 0 Then GoTo setcono
If Con.CommandTimeout > 0 Then GoTo setcono
Con.Open
Set oRS = New ADODB.Recordset
oRS.ActiveConnection = Con
field = Range("A2").value
oRS.Source = "YOUR SQL QUERY "
oRS.Source = oRS.Source & " WHERE field = '" & field & "'"
oRS.Open
data.Range("A2").CopyFromRecordset oRS
End If
oRS.Close
Con.Close
If Not oRS Is Nothing Then Set oRS = Nothing
If Not Con Is Nothing Then Set oCon = Nothing
I would love Microsoft to fix the bug where it returns errors for the more complex queries as I find it frustrating creating macros just for the sake of returning a simple dataset