Calling scalar UDF function from ms access - sql

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)

Related

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

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)

Using the Month function as criteria in an Access SQL query

I'm trying to extract data from an Access 2016 database using SQL and VBA. Below is the code I'm trying to use and every time I run it, I get a "No value given for one or more parameters". I've also shown what I see in the immediate window.
vsql = "SELECT [ResDate],[ResNanme],[ResStart],[ResEnd] FROM [TrainingRoom] where Month([ResDate]) = " & MonNo
Set RecSet1 = Connection.Execute(vsql, dbrows, adCmdText)
Immediate Window:
SELECT [ResDate],[ResNanme],[ResStart],[ResEnd] FROM [TrainingRoom] where(Month([ResDate])) = 11
I don't see anything wrong but I'm sure this is user error. The "MonNo" variable is declared as an integer.
Any suggestions would be greatly appreciated. Thanks for the help.....
I've never used Execute method to open ADO recordset, only Open.
Assuming Connection is a declared and set variable - but it is a reserved word and really should not use as a variable. If code is behind same db pulling data from, could just use CurrentDb with DAO recordset variable and OpenRecordset method. Example that accepts default parameters for optional arguments:
Dim RecSet1 As DAO.Recordset
Set RecSet1 = CurrentDb.OpenRecordset(vsql)

Excel VBA Late Bind to Access and SQL Insert

I am having a frustrating issue with late binding to MS Access from Excel VBA to execute a DML statement like Insert or Update. All of the data I use in vba comes from user defined Classes. I can query just fine but writing to the DB gets different errors each time I try a different way to do the same thing. Below are some links to the same/similar issues, however each is slightly out of context and therefore I could not get passed my problem.
Microsoft.ACE.OLEDB.12.0 Current Recordset does not support updating error received when trying to update access
Operation must use an Updateable Query / SQL - VBA
Update an excel sheet using VBA/ADO
Operation must use an updatable query. (Error 3073) Microsoft Access
https://msdn.microsoft.com/en-us/library/bb220954%28v=office.12%29.aspx?f=255&MSPPError=-2147217396
http://www.access-programmers.co.uk/forums/showthread.php?t=225063
My end goal is to simply execute a DML string statement and it has to use late binding. Mainly I get the 3251 error saying my connection is 'Read Only' or a missing ISAM when I add ReadOnly=0 to the connection string. Fyi, getProjectFile just returns a path to a file starting from the parent folder of my project. I am pretty sure I can just use the connDB.Execute so I only need SQL Insert, I don't want to query first because the queries will get fat quick. I also think something might be wrong with the enum params because the ExecuteOptions want bitmasks instead of just a Long and I don't really know how to do that. From most of my research, I kept getting referred to the LockType and/or cursor not being right. For my environment; Windows 8.1 64bit, MS Office 2010 32bit(required). Does anyone see what is wrong here?
Sub ADO_Tester()
Dim strSQL, strFile, strConnection As String
Dim connDB As Object
'late bind to the ADODB library and get a connection object
Set connDB = CreateObject("ADODB.Connection")
'Connect to the DB
strFile = Application.ActiveWorkbook.Path & "\" & "PortfolioDB.accdb"
strConnection = "Provider = Microsoft.ACE.OLEDB.12.0; data source=" & strFile & ";"
connDB.Open strConnection
'insert statement for a test record
strSQL = "INSERT INTO underlying_symbol (symbol) VALUES ('xyz')"
'execute the
connDB.Execute strSQL, , 2, 1 + 128
'clear the object
connDB.Close
Set connDB = Nothing
End Sub
Edit:
Early binding:
connDB.Execute strSQL, , adCmdText + adExecuteNoRecords
Late Binding: How to enter the value for adExecuteNoRecords? On msdn it is 0x80 and another post says &H0001,either way it gives a syntax error. It says enter a bitmask for this enum value.
connDB.Execute strSQL, , 1 + 0x80
Edit: Now the correct way -
adExecuteNoRecords (the ADO enum value) = 0x80 (a binary value) = 128 (a decimal value)
connDB.Execute strSQL, , 1 + 128
Edit: Now the issue gets even deeper. When I execute the code in a test spreadsheet into a test database, it works. When I copy and paste into the actual project spreadsheet and point to actual project db, I get the error: operation must use an updateable query . . . again. Same db name, same dml, same table name. The only difference is the actual DB is a product of a split to separate it from the forms and code in Access. Could this have changed some setting to make it read only?
Edit: It just gets deeper and deeper. The issue causing it not to work in the project db is because I have some Excel Tables querying the db. I made these through the Excel UI, Ribbon -> External Data -> Access -> etc. . . It has now become obvious these are causing me to be unable to insert DML because they are probably set to read only. How can I change the tables connections permissions? Is there another way I could be making these tables so that I can provide the connection? How to get Tables to be friendly with DML in VBA?
This worked for me:
Option Explicit
Private Const acCmdText As Integer = 1
Sub ADO_Tester()
On Error GoTo ErrorHandler
Dim strSQL As String
Dim strFile As String
'Dim adoRecSet As Object
Dim connDB As Object
'late bind to the ADODB library and get a connection object
Set connDB = CreateObject("ADODB.Connection")
'Connect to the DB
strFile = getProjectFile("core", "PortfolioDB.accdb")
connDB.Open connectionString:="Provider = Microsoft.ACE.OLEDB.12.0; data source=" & strFile & ";"
'If State = 1, db connection is okay.
MsgBox "ADO Connection State is " & connDB.State & "."
'SQL to get the whole [underlying_symbol] table
'strSQL = "underlying_symbol" 'if options 2
'strSQL = "SELECT * FROM underlying_symbol" 'if options 1
strSQL = "INSERT INTO underlying_symbol (symbol) VALUES ('xyz')"
'late bind to adodb and get recordset object
'Set adoRecSet = CreateObject("ADODB.Recordset")
'&H0001 = bitmask for aCmdText
connDB.Execute strSQL, , acCmdText
'With adoRecSet
' .Open Source:=strSQL, _
' ActiveConnection:=connDB, _
' CursorType:=1, _
' LockType:=3, _
' Options:=&H1
'.AddNew
'.fields("symbol") = "XYZ"
'.Update
'End With
'------------------
'close the objects
'adoRecSet.Close
connDB.Close
'destroy the variables
'Set adoRecSet = Nothing
Set connDB = Nothing
ExitMe:
Exit Sub
ErrorHandler:
MsgBox Err.Number & ": " & Err.Description
GoTo ExitMe
End Sub
Added some error handling, a constant that defines acCmdText (Why just not add a reference to ADO library? Up to you, though.), and a message box to check the connection state to the database, as I can't test your getProjectFile function. Late binding doesn't seem to be the issue here, I think the key line is:
connDB.Execute strSQL, , 2, &H1
Can really say what's going on here as I've never done it like this (code doesn't even compile), but changing it to
connDB.Execute strSQL, , acCmdText
worked for me.

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.

How to use ASP variables in SQL statement

<%
postit = request.querystring("thispost")
response.write(postit)
%>
postit is the variable. The response.write works and this is all above the SQL statement below.
This is the SQL however when I add the postit variable I get this error message:
delCmd.CommandText="DELETE * FROM post WHERE (pos_ID = postit )"
Microsoft Access Database Engine error '80040e10'
No value given for one or more required parameters.
/student/s0190204/wip/deleterecord.asp, line 32
Add a parameter to the SQL:
delCmd.CommandText="DELETE * FROM post WHERE (pos_ID = ?)"
delCmd.Parameters.Append delCmd.CreateParameter("posid", adInteger, adParamInput) ' input parameter
delCmd.Parameters("posid").Value = postit
Couple of things that will help you in the future
Use Option Explicit to avoid hiding issues that will come back to bite you later on
Use ADODB.Command object, which is very versatile enabling to do a range of database calls, from simple dynamic SQL statements to Stored Procedures without the risk of SQL injection.
There are a few tips that can speed things up when using the ADODB.Command object in your code which will be demonstrated in the example below (assumes you already have a connection string stored in a global config call gs_connstr);
<%
Option Explicit
Dim postit
postit = Request.QueryString("thispost")
'Always do some basic validation of your Request variables
If Len(postit) > 0 And IsNumeric(postit) Then CLng(postit) Else postit = 0
Dim o_cmd, o_rs, a_rs, i_row, i_rows, l_affected
Dim SQL
'SQL statement to be executed. For CommandType adCmdText this can be any dynamic
'statement, but adCmdText also gives you an added bonus - Parameterised Queries
'instead of concatenating values into your SQL you can specify placeholders (?)
'that you will define values for that will get passed to the provider in the order
'they are defined in the SQL statement.
SQL = "DELETE * FROM post WHERE (pos_ID = ?)"
Set o_cmd = Server.CreateObject("ADODB.Command")
With o_cmd
'ActiveConnection will accept a Connection String so there is no need
'to instantiate a separate ADODB.Connection object the ADODB.Command object
'will handle this and also open the connection ready.
.ActiveConnection = gs_connstr
.CommandType = adCmdText
.CommandText = SQL
'When using Parameters the most important thing to remember is the order you
'appended your parameters to the Parameters collection as this will determine
'the order in which they are applied to your SQL query at execution. Because
'of this the name you give to your parameters is not important in terms of
'execution but I find specifying a meaningful name is best (especially when
'revisiting some code a few years down the line).
Call .Parameters.Append(.CreateParameter("#pos_ID", adInteger, adParamInput, 4))
'Parameter values can be passed in via the Execute() method using an Array
'without having to define the parameter values explicitly. You can also specify
'the records affected value to return number of rows affected by a DELETE,
'INSERT or UPDATE statement.
.Execute(l_affected, Array(postit))
End With
'Always tidy up after yourself, by releasing your object from memory, this will
'also tidy up your connection as it was created by the ADODB.Command object.
Set o_cmd = Nothing
%>
Try this code:
<% Dim postit, stringSQL, objectCon
postit = request.querystring("thispost")
Set objectCon = Server.CreateObject("ADODB.Connection")
objectCon.ConnectionString "Driver={SQL SERVER};Server=server_name;UID=user_name;PWD=password;Database=database_name" 'SET CONNECTION STRING OF YOUR DATABASE
stringSQL = "DELETE FROM post WHERE pos_id='" & postit & "'"
objectCon.Open
objectCon.Execute(stringSQL)
objectCon.Close() %>
You're not passing the value of postit to Access; instead, you're telling Access to find & use a variable called postit. Of course, said variable doesn't exist in Access -- it only exists in your code. The fix is just a couple of quote marks and a pair of ampersands.
delCmd.CommandText="DELETE * FROM post WHERE (pos_ID = " & postit & " )"
(Naturally, you should validate postit before you go sending it off to your database. A simple CDbl() can do the trick, assuming it's a numeric value.)
Here I'm trying to get the car_color of the car using the id of the car.
Now I can use the car_color record set in my code.
I would also recommend using CLng when passing in values, it'll prevent sql injections.
If the carID is not a number you'll get the following error:
"500 response from the server. Remember to open and close the sql connection."
Here is the code:
sql = "Select * from Cars Where ID = " & clng(carID)
rs.open
if not rs.eof then
carID = rs("car_ID")
carColor = rs("car_color")
end if
rs.close
More easy for delete, this way is useful when not need to check the recordset:
cn.open "yourconnectionstring"
cn.execute "DELETE * FROM post WHERE pos_ID = " & request.querystring("thispost")
cn.close