MS Access - Pass Through query best practice solution - sql-server

So I have a particular situation here with the CRM I'm tasked with developing, and I'd like to see if I can get some insight on more of a sanctioned, best-practice approach for this specific scenario. Basically, I have a form called Accounts that displays information specific to that account as well as a subform that needs to display quotes and orders associated with that account to give the user a snapshot of work done.
Due to the complexity of the query and number of possible records, scrolling through this subform can be slow if using a SQL VIEW or more obviously, a local access query. The best solution here is a pass through query so I can shoot that processing straight to SQL.
I've done quite a bit of research and the best approach I've seen is to either 1) use VBA to change the definition of the pass through query or 2) use an INSERT INTO statement to populate a local table which can be used as the recordsource for that subform. At this point I just want to confirm that either or both of these approaches would qualify as a best practice situation and possibly an example of code to make this happen. Right now I'm struggling with where this processing belongs. I don't want to call the code from the subform that launches the main Accounts form since this form can be opened from a few different places and I don't want to duplicate that code.
Here is the code I'm using to copy the dataset from the pass through query to a local table and then using the Master/Child relationship fields to only show the appropriate data for that account.
Dim db As DAO.Database
Dim strSQL As String
Set db = CurrentDb
'Purge local table where pass through results are copied
strSQL = "Delete * FROM tblPassThruResults"
db.Execute strSQL
'Insert the results of the pass through query into local table
strSQL = "INSERT INTO tblPassThruResults Select Q_ACCOUNT_BUSINESS_OVERVIEW_PT.* FROM Q_ACCOUNT_BUSINESS_OVERVIEW_PT"
db.Execute strSQL
db.Close
Set db = Nothing

Related

User-editing a linked SQL Server table filtered by the results from a passthrough query

I'm running MS Access 2016 connecting to a linked table on SQL Server (2012, I think, if it matters), in order to allow a single user (me) to quickly manipulate data in that table. I have a fairly intensive pass-through query which creates a list of primary key values representing rows where there's something wrong with the data in the table (usually just nulls where there shouldn't be nulls, but also invalid combinations of values and invalid dates).
I would like to display only the rows that are identified by my pass-through query, so I can quickly input missing values and make other corrections. However, I am at a complete loss as to how to do that.
In an attempt to sort-of follow best practices, I tried to make the relevant rows display in a form where only specific fields would be editable. However, MS Access keeps throwing an error about how a relationship isn't defined between the queries and the table, even though I set up the relationships.
Failing that, I tried to make an editable query using the relevant queries and table, but none of the queries I've made have had editable recordsets, for reasons I don't really understand. As far as I can tell, the relationships between the queries and the table has been one-to-one, and the linked table is normally editable directly in MS Access. Even when I ditch the additional info query and just join the linked table to my error-finding passthrough query, I can't create an editable recordset.
Is there a good way to accomplish my goals? I'm starting to feel like my best option here is to create some sort of temporary table in Access to store the values I'm editing, and then merge them into the linked table, but that seems kind of clunky to me. Do I have any other options?
Ok, perhaps you have the stored procedure after finding those bad rows (or whatever reason), you have it return that list of keys.
The next question then becomes?
Well, if you only ever returning say < 100 rows, then I think that just returning a string of PK values would work fine.
So, the stored procedure can either:
Return one string to access with pk values, say like
34,3443,3,55333
(in the final statement of your stored procedure, just do a select on the string and Access will see the one row, one column as a recordset).
So, your code would be something like:
Sub EditBad()
Dim strKeys As String ' list of keys from stored procedure
Dim rstKeys As DAO.Recordset
With CurrentDb.QueryDefs("MyStoredProc")
.SQL = "exec sp_myproc " & "some possible parameters you send to stored procedure"
Set rstKeys = .OpenRecordset
End With
strKeys = rstKeys(0)
rstKeys.Close
' now launch our edit form based on the keys returned
Dim strWhere As String
strWhere = "ID IN (" & strKeys & ")"
DoCmd.OpenForm "frmEditBadGuys", , , strWhere
End Sub
Now, if the list to be returned of bad keys is going to larger? Well, then from the stored procedure, simply return the values as a table. So, in place of a single select on the "string of keys" from the stored procedure, you return a select of the bad key values you want to check.
So, now we have:
With CurrentDb.QueryDefs("MyStoredProc")
.SQL = "exec sp_myproc " & "some possible parameters you send to stored procedure"
Set rstKeys = .OpenRecordset
End With
strKeys = ""
do while rstKeys.EOF = False
if strKeys <> "" then strKeys = strKeys & ","
strKeys = strKyes & rstKeys(0)
.movenext
loop
rstKeys.Close
strWhere = "ID IN (" & strKeys & ")"
DoCmd.OpenForm "frmEditBadGuys", , , strWhere
Again, I think this solution is good for say 100-200 rows.
If the results returned are say 1000-2000 records? Well then that "in (value1, value2) etc. string gets too long, runs too slow, and will blow up - you limited to I think about 4000 characters - but such a where clause in SQL is simply too long aleady, and it going to run turtle slow.
So, in place of procesisng the returned recordset into a long string?
Send the results to a local temp table. You can then created a editable form, and join on the local table to the linked table. In this case, make sure the base table is the linked table, and you may well have to do a left join to the local temp table (an inner might work, but left should work.
And if performance is really required? Well, send the data sql server side to a temp table (now, by temp table, I don't mean an actual sql server temp table, since you can't use those client side in Access. (what setup you use would depend on how multi-user this applcaiton has to be).
So, you could make a sql server view (that does this join). Keep in mind that sql server views ARE editable from client side Access (unlike pass-though query - they are read only for the most part).
So, you could also have a "user" column in that "sort of temp" table sql side, and you not only add the PK values, but also a user column name. And then in access client side? You launch the "bad guys/bad data" edit form and use a where clause based on user name (this would allow mutli-user setup). And this setup would by far perform the best.
So, get the stored procedure to spit back either:
A one column select from the stored procedure that has the keys, and try the "in clause" as a where clause against a client side form that you know works (can edit data). However, I seem to recall that the access client does not do a great job with "in clause" for SQL Server - you have to give this idea a try.
The local table of PK values also would work well - not much more code, and this would be based on a linked table join + a left join to the local table of PK values that you wrote out. If the linked table is not too large, then this can work (but again, performance issues will crop up).
So, ultimate performance would be a server side view and it being joined to the temp working table of bad PK values you want to work on. And for multi-user, then you have to add a "user" column or some means to allow more then one user. Keep in mind that a "where" clause from access client works VERY well against a view. (but not those where "in (1,2,3) types of where clauses!
So, 3 possible roads above to try.

Control Database form Excel through VBA

I am trying to create an excel addin which has set of Functions to pull value from the database (I use MS SQL Server). So my query will return only one record set. I use something like below in my vba code.
Using Excel VBA to run SQL query
But the problem with this is if I have my custom function in 100 cells, the macro makes connection to the DB everytime and retrive data from the DB.
Is there a way where I can make one connection and use that connection to write as many queries as I want?
Simple, run all your 100 function/ loops to access the database. One you've finished then close the connection. Look at your modified code below...
Option Explicit
Sub ConnectSqlServer()
Dim conn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim sConnString As String
' Create the connection string.
sConnString = "Provider=SQLOLEDB;Data Source=INSTANCE\SQLEXPRESS;" & _
"Initial Catalog=MyDatabaseName;" & _
"Integrated Security=SSPI;"
' Create the Connection and Recordset objects.
Set conn = New ADODB.Connection
Set rs = New ADODB.Recordset
' Open the connection and execute.
conn.Open sConnString
'>>>> run 100 loops
Dim i As Integer
For i = 1 To 100
Set rs = conn.Execute("SELECT * FROM Table" + 1 + ";")
' Check we have data.
If Not rs.EOF Then
' Transfer result.
' I assume that you've 100 Sheets
Sheets(i).Range("A1").CopyFromRecordset rs
' Close the recordset
rs.Close
Else
MsgBox "Error: No records returned.", vbCritical
End If
Next
' Clean up
If CBool(conn.State And adStateOpen) Then conn.Close
Set conn = Nothing
Set rs = Nothing
End Sub
I've add 100 loops and run it before the connection to database is closed.
Hope it useful.
In this kind of cases do not do db operations in loops. This is time consuming and not proper useage. Instead, in a loop create select, insert or whatsoever statement and then complate the loop operations and calculations then open the connection just one time and send request (created sql script) to db and get response from db then close connection. Thats all. db operations must be sparated from dailiy and recursive opeations.(antipattern) best regards.
Disclaimer: while this is not a direct solution to the problem described in the post I would like to add this approach as a much faster and easier solution to the problem described.
Step 1: create a (possibly hidden) sheet where you pull all the SQL data that you need in this Excel file. Pull the data into one table with all the necessary columns / dimensions to get the data afterwards from this table.
Here is what I mean by that. Let's assume that you need in this Excel file some data from the table Users on the SQL server as well as some information from a StockMarket table on the server. From the table Users you want the UserID, the first name, last name, and the job title. From the table StockMarket you will need the Stockmarket ID and the price for this particular share. As these prices are by date you also need the the quote date for the price.
Now, since you want all in one table you must think of a smart way to combine all this data into one table. One approach could be the following:
Afterwards you can get all the data from the above table with lookup functions like these:
=INDEX(SQLdata,MATCH(1,(SQLdata[Table]="Users")*(SQLdata[UserID]=25),0),4)
Note, that I named the table SQLdata to make it easier when looking at the formula and understanding it. Also, like this you can easily scan your Excel file for any reference to this table.
Another approach could be the following to make the table more concise:
Note, that this time I am mixing Strings with Numbers and also Strings with Dates (which is very poor design and for some people here even impossible to think of). Also, the column headers are now less descriptive. Yet, this works too:
=INDEX(SQLrev,MATCH(1,(SQLrev[Table]="Users")*(SQLrev[Dimension1]=25),0),5)
Note, that I called the table this time SQLrev.
Both solutions allow you also to aggregate data from the table. So, if you want (for example) the average price for Apple in 2017 then you can use the following formula to sum up the the quotes for this year and divide them by 3:
=SUM(IF("StockMarket"=SQLrev[Table];1;0)*IF("AAPL"=SQLrev[Dimension1];1;0)*SQLdata[Price])/3
The most notable advantage for this approach is that you merely have to update one table in the entire Excel file which means that there is only one SQL pull from the server.
The most notable disadvantage (apart from the SQL select which might get pretty complicated to write) is that you need to know of all the data that needs to reside in this table. If the data is not pulled into this table then none of the above formulas will be able to retrieve these values.
While this approach certainly has its downsides this is much easier to implement than the Excel AddIn you are aiming for.
All above formulas are array formulas and must be entered pressing Ctrl + Shift + Enter. For more information on array formulas please read the following: https://support.office.com/en-us/article/Guidelines-and-examples-of-array-formulas-7D94A64E-3FF3-4686-9372-ECFD5CAA57C7

Updating Access from Data yielded in Excel

I am trying to find the best way to extract report from Access into Excel and updating Access, once data has been yieled by Questionnaire participants.
The data in Access is quite complex and large (contained in a single master table - 50+ columns & 200K+ rows) - which doesn't make it easy on the eyes.
I would like to extract reports from Access (essentially a subset of data - M columns out of N). The report will be updated by individuals (i.e. values updated) and will need to be fed back into Access.
I'm looking for the most user - friendly approach to achieve the above. Does anyone have any ideas?
Ps obvious answers welcome, I am a complete Access/Excel noob - but have a background in MySQL, SQL Server.
As far as i'm aware, there is no single tool to do this, if you want to create your own, i recommend you to use any .NET language, ADO.NET and Interop to read data from and into Excel. If you don't feel you want to do some .NET programming, there is a reporting tool called DBxtra that take Excel, Access and many databases as input and let you export data to Excel (either static or dynamic) and other formats. The route will be like this:
1.- Create your query from your Access database in DBxtra
2.- Export the results into Excel (either linked or static sheet)
3.- Let yout users update the data in Excel
4.- Use Excel macros to update the data in the Access database.
To update the access database you will need to put a macro like this into in your Excel file:
Sub appProb()
On Error GoTo 1
Dim XLTable As TableDef
Set Db = OpenDatabase(mdbFile)
Set XLTable = Db.CreateTableDef("tblProbTemp")
XLTable.Connect = "Excel 5.0;DATABASE=" & xlsFile
XLTable.SourceTableName = "tblXLProb"
Db.TableDefs.Append XLTable
strSQL = "Insert into tblProb Select * FROM tblProbTemp"
Db.Execute strSQL
1:
Err.Clear
On Error Resume Next
Db.TableDefs.Delete "tblProbTemp"
Db.Close
End Sub
The only thing worth to note is that tblXLProb is named range.
Note: This macro was copied from here: http://www.mrexcel.com/forum/microsoft-access/51157-update-access-records-excel.html

How to solve this in Access forms with Visual basic SQL server connection?

I created a form that essentially show me the Orders from the familiar Northwind database, I´m using Microsoft SQL Server 2008, Microsoft Access 2007 with Visual basic (ADO), so I created the basic form, which pretty much looks like this:
http://www.flickr.com/photos/63259070#N06/7014481001/in/photostream/
Granted it is in Spanish and I'm terrible sorry but hopefully that wont hamper your ability to help me out, So as you can see there is a big main form and then there is a little form consisting of four text bars which of course are attributes or columns in a database called order details So as you can see from this relation:
flickr.com/photos/63259070#N06/6868373952/in/photostream/
You have the table orders (Pedido) and the table order details (Detalle de pedidos), what I want to do is when I click the main >>, the products show accordingly to the Orders, hopefully this will make sense when im on a order and click the sub form with the four elements, it should only show me the products, price, quantity, product id, according to the Order ID in the main form when I click the >> or << in the sub form....
So to exemplify say I have an order ID of 001 and 001 ordered three products, salt, pepper, chicken ... it should only show me that in the sub form.. Hopefully you got this I hope.. please
Here is my complete VB 6 code... again any help is well appreciated thanks...
https://docs.google.com/document/d/1bn71VqxzB1W55sHcKMstCSxnIbmP5cyYWkBVnF1tzVs/edit
I've experimented a great deal with ADO in Access and I finally concluded that it is not the best technology to use when it comes forms and reports in MS Access. It's OK for connecting to data sources that can only be access through ADO, or in which there is a significant advantage to using ADO (such as speed) but in those cases you must make compromises which often require you to omit certain features or work very hard to code them. Comboboxes that use a SQL statement (as opposed to a Value List) are difficult to setup and will require you to load a separate recordset in order to fill them with options. I've found sorting to not always work properly and reliably, specifically in datasheet view forms. In short, it's a lot more work and code to make ADO work in Access forms unless you keep your forms exceedingly simple (and that often makes them clumsy to use).
What I recommend you do instead is use ODBC linked tables. This might require you to first install a SQL Server ODBC driver. After you have your linked tables setup in Access, you can then use Access as though those tables were local tables. You can use bound forms (which automatically use DAO to access data) and setting up master/detail forms is a breeze under this configuration. You can even use the wizards though I wouldn't necessarily recommend that.
If you do insist on using ADO, I recommend you experiment with setting the form's recordset to the ADO recordset. This would then be a bound form and would keep you from having to write code to move data from the recordset into the controls and vice versa. Here's some of your code modified to do that:
Private Sub Form_Load()
Set con = Nothing
Set rs = Nothing
Set rs2 = Nothing
Set con = New ADODB.Connection
Set rs = New ADODB.Recordset
Set rs2 = New ADODB.Recordset
con.ConnectionString = "Provider=SQLNCLI10.1;User ID=sa0378;password=123;Initial Catalog=Northwind;Data Source=(local);"
con.Open
rs.Open "SELECT Orders.OrderID, Orders.ShipCountry," & _
"Orders.EmployeeID, Orders.CustomerID, Orders.ShipCity," & _
"Orders.ShipAddress, Orders.ShipPostalCode, Orders.Freight, Orders.OrderDate," & _
"Orders.RequiredDate, Orders.ShippedDate FROM Orders", con, _
adOpenDynamic, adLockOptimistic
Set Me.Recordset = rs
End Sub
That code however won't setup the master/detail relationship your looking for. It will still be your job to make that happen manually by loading the correct recordset anytime the user changes to a different record on the main form. You should be able to do this on the form's Current event.

Update an Access Link Table when the underlying SQL Server table's columns change

At work we've got a SQL Server database that several users connect to (read only) using Access 2003.
This was fine, except now one of the tables they look at is re-created fairly often and part of this process involves a cross-tab query.
Which means that, depending on the data, the number and and names of the columns potentially change each time the table is regenerated.
However when they look at the re-created table from Access, it still shows the column headings that were there when the table was first linked to.
Is there a way I can programmatically re-link the table each time they open the Access database?
What I ended up doing was creating a VBA function that looks something like below (needs error handling!)
Public Function ReConnectToLinkTable()
Dim db As Dao.Database
Dim tdf As Dao.TableDef
Set db = CurrentDb
Set tdf = db.CreateTableDef("local_table_name")
tdf.Connect = "ODBC;DRIVER=SQL Server;SERVER=server_name;UID=user_name;" & _
"PWD=password;APP=Microsoft Data Access Conponents;" & _
"DATABASE=database_name"
tdf.Attributes = TableDefAttributeEnum.dbAttachSavePWD
tdf.SourceTableName = "server_table_name"
db.TableDefs.Delete ("local_table_name")
db.TableDefs.Append tdf
End Function
Then I created a macro called AutoExec (the name guarantees it is called when the Access file is opened) which has an Action of RunCode, which calls the ReconnectToLinkTable() function.
ODBC linked tables break when the table or view on the server is altered. Some changes can result in them just becoming read-only, others will simply not include all the columns.
I have found that updating the connect string does not successfully fix this problem. It will usually fix missing fields, but it can still be read-only. The only reliable way to do this is to recreate the linked table on the fly.
Another alternative would be to not use a linked table at all, but use a saved QueryDef that has the appropriate connect string. This will never have to be updated, but could be a performance issue as the metadata stored in the table link helps Access figure out how to retrieve the data. Without that metadata stored in the table link, it has to retrieve that information from the server each time the query is run.
Something like this snippet is usually used. Search google for 'ms access refresh link table' and you'll find various solutions all similar to this one.

Resources