Using Parameters in SQL query with sub-query - sql

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

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)

Populate results from SQL Server using Recordset in MS Access using VBA code

I am not sure how I can put the results from Record Set into a query or result pane in MS Access using VBA code. The results in the recordset are from SQL Server, so I want to display the results in MS Access. I need to do it this way, is that possible? I would think I need to do something where are x's are.
Dim conn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim strConnString As String
strConnString = "Provider=SQLNCLI11;Server=SRV;Database=Staging;Trusted_Connection=yes;"
Set conn = New ADODB.Connection
conn.Open strConnString
Set rs = conn.Execute("Select * from MSAccess_APP_ComplianceDashBoard ")
XXXXXXXXXXXXXXXXXX
rs.Close
Assuming a continues form,or a multiple items form?
In the forms on-load event just go:
me.ReocrdSource = "Select * from MSAccess_APP_ComplianceDashBoard"
It is of course assumed that you have a linked table to SQL server with the above name.
In fact, you can set the forms record source in design mode and as a result you need zero lines of code.
So, setup a linked table - you not need to write any code.
Create a pass-through query, put your connection string there, and do whatever you your needs over this query.

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")

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.

Dynamically create Datasheet from SQL query with VBA in Ms Office Access

I have Access database project connected with Ms SQL Server. Data (tables) is stored in the Ms SQL server and Forms and Reports are stored in the Access .ADP file. It is not possible to create Queries, Tables, Views using Design view but Tables and Views can be created using SQL queries and stored on the server. I don't have Ms SQL Server Management Studio and I can't install it in my computer in my office.
So, what I want is to get a dynamically generated Datasheet of a SELECT SQL query to see results temporarily for data analysis. I have placed a textbox and a button in a form and want to display a datasheet containing the result of the SQL query written in the textbox when the button is clicked.
I tried this but it is not working for me and doesn't seems what I want:
MS Access VBA - display dynamically built SQL results in datasheet subform
I also tried by assigning query to Recordsource property of a form. It is showing blank datasheet, but the navigation pane below the datasheet is showing the actual number of records retrieved. So, it is working but not showing the data.
I tried (from http://www.pcreview.co.uk/forums/create-query-dynamically-vba-t3146896.html):
Dim db As DAO.Database
Dim qd As DAO.QueryDef
Dim strSQL As String
Set db = CurrentDb
strSQL = "select * from analysts"
Set qd = db.CreateQueryDef("NewQueryName", strSQL)
DoCmd.OpenQuery "db.NewQueryName"
It is showing run-time error 91, Object variable or With block variable not set on the line Set qd = db....
And also (from the same page):
Dim strSql As String
strSql = "select * from analysts"
CurrentDb.QueryDefs("qryExport").SQL = strSql
DoCmd.OpenQuery "qryExport"
Returning same error on the line CurrentDb.QueryDefs.....
Any idea or workaround?
This doesn't seems efficient, but it is working and a bit satisfactory:
DoCmd.RunSQL "IF EXISTS (SELECT TABLE_NAME FROM INFORMATION_SCHEMA.VIEWS " & _
"WHERE TABLE_NAME = 'tv') DROP VIEW tv"
DoCmd.RunSQL "create view tv as " & txtQry
DoCmd.OpenView "tv"
Here I am creating a temporary VIEW (tv) in a button's click event. Before creating the view, I am checking that if a view with the same name exist or not; and if it exist then delete it so that a new view can be created with the same name with different query.
Access ADPs are a whole different beast than the normal MDB that you are probably used to working with. DAO isn't generally used in ADPs, you probably had to add a reference to DAO to get any part of the code above to work. ADPs are designed around using ADO to interact with the source database. If you just want to open a recordset to your data, use something along these lines.
Dim strSql As String
Dim rs as ADODB.Recordset
strSql = "select * from analysts"
set rs = New ADODB.Recordset
Set rs.ActiveConnection = CurrentProject.Connection
rs.Source = strsql
rs.Open
You can then interact with that recordset. If you want to bind that recordset to a form so that you can view the data, you can use:
Set Me.Recordset = rs
This is working how I want:
Dim frm As Form ' create a form dynamically
Set frm = CreateForm
Dim rs As New ADODB.Recordset
rs.Open Replace(txtQry, vbCrLf, " "), CurrentProject.Connection ' replace "enters" (vbCrLf) with space because it was throwing error while executing query!
Dim c As Control
For Each f In rs.Fields
Set c = CreateControl(frm.Name, IIf(f.Type = 11, acCheckBox, acTextBox)) ' if field type is "bit", add checkbox; for other datatypes, add textbox
c.ControlSource = f.Name
c.Name = f.Name ' sets column header to the same name as of field's name
Next
rs.Close
frm.RecordSource = txtQry
DoCmd.OpenForm frm.Name, acFormDS ' open form in "DataSheet" view otherwise it will be in "Form" view
This code is placed in a button's click event.
Other possible datatypes for rs.Fields(x).Type (here, f.Type) are (3 = int, 200 = varchar)