How can I create a (server-side) filtered editable SQL recordset for Microsoft Access? - sql

I am working on a very complex legacy ADP project in Microsoft Access that connects to a 2008 SQL Server back end. A common usage scenario is a form in datasheet view that contains an editable recordset.
The approach used through most of the database is to load the form, then build the SQL dynamically in VBA using form parameters, and apply the SQL as the RecordSource for the form. Many of the queries are quite complex, so this makes for really ugly code and a maintenance nightmare.
For the read-only recordsets, I have converted the dynamic sql to parameterized stored proceedures, which works great. Nice and clean, and easy to maintain going forward.
But for the editable recordsets, I am trying to determine the best approach:
1. Dynamic SQL - As mentioned above, I would really like to avoid this approach.
2. SQL View - The challenge here is that some of the tables are very large, so if I try to load the view and then filter it on the form, it has to pull the entire recordset from the SQL server, even though I only need a small number of rows. (Negative performance and IO impact.)
3. Use context_info - This sounds intriguing, but does not sound like a recommended approach based on discussion here: Create parameterized VIEW in SQL Server 2008 If I was developing against SQL Server 2016 I might look more into SESSION_CONTEXT.
4. Parameter Table with View - This is an idea that I am leaning towards. I would create a Parameters table in SQL, and set the parameter value (as a key/value pair) with the session ID. The view would then filter based on the current value in the parameter table. This would allow me to use a view as my RecordSource to support the edits, but the filtering would take place on the SQL Server side.
Is the parameter table indeed the best approach to take with this project, or is there another way that I could access a parameterized read-write recordset that is filtered on the server side?

I assume you talking about a non adp project now.
Even if you don’t use a view, and say bind a form directly to a linked table of 1 million rows, then access will ONLY pull down the records you requests. You simply just use the forms “where” clause of the open Form command.
So you don’t even have to use dynamic sql here.
However, you don’t want to launch a form bound to large table UNLESS you set the where clause.
You can certainly open a form without a reordsource, have the user enter some parameter values into a text box, and then go:
Dim strSQL as string
strSQL = "select * from tblCustomers where InvoiceNum = " & me.MyInvoiceTextBox
Me.RecordSoruce = strSQL
However, in most cases you better to create some type of search form. Let the user enter some values, display the results like this:
So in above, the person type in "smi". You display the results
(and in above I did use the above approach of stuffing the sql directly into the forms reocrdsource
Now on the edit buttons along the side to launch + edit one record,
I simply go:
Docmd.OpenForm "frmCustomer",,,id = & me!id
Once again, EVEN if the form is bound directly to the linked SQL server table, only the ONE record will be pulled from SQL server. So no messing with sql, no messing with parameters etc. is required.
So a regular non ADP access application with linked tables DOES NOT pull the whole table.
You can also after opening a form set the forms filter – and again access will ONLY pull the reocrds in question from the linked sql table. It is a “common” myth that access pulls all records – it does not if you provide a filter, and I recommend you open a form to one record, let the user work then close the form and return back to some search screen in which you ready to do battle with the next customer etc.
so provide a search form - don't recommend having the form to edit data be all cluttered up with the ability to search records. Let the user edit, and then close the form - this also promotes the record being saved after the user done working.
edit:
For a form that has any kind of complex joins etc., then create a view, and bind the form to that view. You use the forms "where" clause, then once again access will only pull down the one record. So for complex joins etc., yes access can often mess that query up and it runs slow. So if the form is bound to one table (that is MOST cases), then bind the form directly to the linked table. If the sql is complex, then bind the form to the linked view, and as noted in either case ALWAYS provide a "where clause" to the openform command - it will in these cases ONLY pull the one record into the form. And once again, no messy parameters, no messy sql is required on the access side - you will save MASSIVE amounts of coding if you adopt this approach, and you also get stellar performance since you limiting the reocrds pulled into that form from sql server.

Related

Read/Write SQL Server views in MS Access using SCHEMABINDING

I am having to redevelop an MS Access 2003/2010 ADP project, that uses SQL Server views as the RecordSource for all its forms, into an MS Access 2016 ACCDB.
I have tried using pass through queries to get the data and this works fine for readonly columns, however when I want to change the value in one of the bound columns, it says that the RecordSet is not updateable, which is what you might expect if using a view.
But I have now read that if you define the view with SCHEMABINDING like this:
ALTER VIEW [dbo].[vwQuote_MinibusesDetails]
WITH SCHEMABINDING
AS
SELECT ...
and add a UNIQUE CLUSTERED INDEX like this:
CREATE UNIQUE CLUSTERED INDEX CIX_vwQuote_MinibusesDetails
ON vwQuote_MinibusesDetails (txtQuoteNo, txtVersion, txtVehicleNo);
and then add the view to your project as a DSN-less TableDef like this
stConnect = "ODBC;Driver=SQL Server;Server=" & SERVER_NAME & ";Database=" & APP_DATABASE & ";Trusted_Connection=Yes"
Set td = CurrentDb.CreateTableDef(stLocalTableName, dbAttachSavePWD, stRemoteTableName, stConnect)
CurrentDb.TableDefs.Append td
it becomes editable as if it were a table.
However, when I open the TableDef in Access it shows all the rows and columns as if it were editable, but if I try to edit a column, it says that there is a Write Conflict with another user's changes, when I am 100% sure that there isn't because I am the only person using it.
Any ideas? (I am using Access 2010 at the moment)
But why do all that truckloads of work? There is zero reason to do all that extra work.
Simply bind and set the forms data source to a view. It is assumed that you will simply link all of the existing views on the client side. Access will thus see all the views as simply tables, and views (as opposed to Pass through queries) are read/write.
So, there is no need to:
Have ANY connection strings in your code. If you ARE using connection strings in code, then you approach is all wrong. Just link to the views, and set the forms data source as that view.
At this point, the forms can edit data.
As for a pass-through quires to drive combo boxes? Do NOT do this. While a PT query is likely the fastest way to pull data, the access client cannot filter PT queries. So you ONLY EVER want to use a PT query for cases in which the client side does NOT have to filter the results. If you bind a combo box to a PT query, then Access needs and wants to ONLY pull the one value from that table for display. And since the access client can’t filter a PT query, then it will scan the WHOLE data source for that combo box to get the one value that is currently displayed. If you use a linked table (or even a view) to that source that drives the combo box, then the access client can filter that dataset to the ONE row. So, do NOT use a PT query for ANY case in which client side filtering is required. A linked view (or linked table) is fine in these cases.
So, just bind the forms directly to the linked table, or the linked view.
If the table has 1 million rows of data, and you say do this:
Docmd.OpenForm "frmInvoice",,,"InvoiceNum = 1234"
Access will open the form to the ONE row of data, and ONLY pull one row down the network pipe. This is despite that the form in question is bound to a table of 1 million rows. The form will load instant, and you not had to write any sql, any connection stuff, and not really do anything different then how you developed typical access applications.
Bound forms are how and why Access saves huge development dollars. If you jump to .net, then you have all kinds of tools and wizards that can help you around this issue. So in .net, you could adopt the dataset designer, or use the newer entity framework.
In Access, we don’t have this huge tool box of data designers and tools, so if you attempt to hand code, and code up the data sources for a form, then you get the worst possible outcome (you write truckloads of code, and don’t have all those cool tools for un-bound forms).
Simply link your forms to linked tables (or views) and you now have a working data bound form without any code. If you need to load the form, then use the 20+ year standard approach of the “where” clause of the open form command to open the form to the one record. The access client will ONLY pull what you put in the “where” clause.
So, to wire up a form for editing of data, ZERO code is required. The only developer efforts will then to ensure that the form does not pull un-necessary data to the client.

Choose AS400 query records directly from Excel

I've been searching the internet for hours trying to figure out if the following is even possible:
To choose the AS400 query records directly from Excel.
I haven't found any solution or description of how this could be achieved, which makes me guess that it's simply not possible. However, I haven't seen anyone confirm that it is impossible.
So my question is: Is this possible? And if it is, could you point me in the right direction in order for me to start learning how to do it?
I know its possible to run a query from Excel, and then adding parameters via SQL statements, but in my case, this presents several problems that could be avoided by choosing the records before the query is executed.
Example:
I have a query with a column (lets call it ColVal) that can hold the values 1 and/or 2. In the AS400 program under the menu "Work with queries" and then "Choose records" I can specify which records the query should contain when it has run based on the value in ColVal. This means i can get three different situations (A, B and C) when i run the query:
A) The query only contains records where the value in ColVal is 1
B) The query only contains records where the value in ColVal is 2
C) The query contains records where the value in ColVal is either 1 or 2
The goal is to be able to choose which situation I want from Excel in order to circumvent opening and using the AS400 program.
However, using situation C and then editing the query in Excel with an SQL statement to mimic situation A or B is not an option, as this means the query still contains undesired records.
This whole thing boils down to the following: Is it even possible to run the query from Excel essentially changing the data it contains and not just outputting it to excel? If this is possible, is it then possible to pass a parameter to the AS400 system and use it to create situation A, B or C?
I hope this example makes sense.
Edit - New example
Say i have different customers A and B. I can open the AS400 program and run a query in which i have specified that I only want data on customer A. I can then open Excel and use filters (as Hambone described) on the query to determine which records I want to output. However, if I want to work with data from customer B, I have to open the AS400 again and run the query with different parameters. I would like to be able to "change" my dataset from customer A to B from Excel, without having to include both in my recordset and then filter out one of them.
I imagined this is doable if you could pass a parameter to the AS400. The AS400 then runs the query using this parameter as the criteria for which records should be stored in the query. This means that if the parameter is Customer B, then there is no way to acces data from customer A, without running the query through AS400 again.
Any ideas are greatly appreciated :)
Follow up to my comment, here is a quick primer on how to run an ODBC query directly in MS Excel using Microsoft Query. This is very different than Power Query, which you referenced, in that MS Query is standard with Excel -- it's not a plug-in. This is relevant because it means everyone has it. If you are deploying a solution to others, that's an important consideration.
To start an MS Query in Excel, go to the data tab, select "From Other Sources" -> "Microsoft Query."
A list of your ODBC connections will come up. Pick the one that you want and select "OK."
It may or may not ask you for a login (depending on which ODBC connection you use and how its configured).
The next part is important. MS Query is going to try to have you use its builder to create the query. If you have the SQL, skip this part. It's horrible. Click "Cancel" on the query wizard, and then click the "SQL" button to enter your own SQL. If you can, make sure the result set is small (like use where 1 = 2 in the query).
When MS Query returns results, click the button next to the SQL Button to have it return the results to the spreadsheet. It looks like a little door.
From here, any time you want to refresh the query, you can simply right-click the data table in Excel and select "refresh." Alternatively you can go to the data tab on the ribbon and select "Refresh."
By the way if you have linked pivot tables and charts, the "Refresh All" option will refresh those as well, in the correct order.
To edit your query at any time, right-click on the table in Excel, go to Table-External Data Properties:
Then Click on the Connection Properties icon (highlighted below)
Click on the second tab (Definition) and edit the SQL Directly.
Parameters can be declared simply by inserting a bare "?" in place of your literal.
In other words, if your query looks like this:
select *
from users
where user_id = 'hambone'
Just change it to:
select *
from users
where user_id = ?
Excel will prompt you for a user id before it runs the query. From here, you also have the option of putting the parameter value in a cell within the spreadsheet and having the query read it from there. You'll see these when you right-click the table and go to the "Parameters" menu option.
Let me know if this helps or is unclear.
-- EDIT 7/23/2018 --
To follow up on your latest edit, it is possible to handle the scenario you describe, where you want to be able to filter on a value, or if none is given, then not have a filter. You see this a lot when you present multiple filter options to the user and you want a blank to mean "no filter," which is obviously counter to the way SQL works.
However, you can hack SQL to still make it work:
select * from activities
where
(activity = ? or ? is null) and
(energy = ? or ? is null)
In this example you have to declare four parameters instead of two, two for each.
You might also have to play with datatypes, depending on the RDBMS (for example for numerics you might have to say ? = 0 instead of ? is null or even ? = '' for text).
Here is a working example where a single filter was applied on the query above and you can clearly see the second one did not have an impact.
Yes it's possible. You need to use an ODBC driver to connect to the AS400 and retrieve the data. The driver and documentation are Here

Get column name & table name from value

Actually I have a new client & their Database has no standard naming conventions & the application is in classic asp.I have a form in which a form there are many values in the different textboxes, it it very difficult to trace the value come from which table.& also there is no erd.
I need a query from which I can get the table name with column name by giving Value.
Let's suppose I have a value having label name abc#= '6599912268'
& the new project has no ERD no standard of naming conventions... I need a fast way to know the abc# ='6599912268' is taking from which table & which column name.... like this the UI has many values which is time taken to trace manually
Is there any way to trace it?
The simple answer is no. There is no way to trace table/column it comes from by mere inspection of the value.
I suggest the following.
Find out what type of db your product is using. Where it is situatede, do you have access to it.
If you have access to the database, get to know the db structure. What each table is meant to store, the relationships etc. Speak to the db administrator or the business analayst to increase your knowledge on the product domain.
Once you have the db structure, try and compare the table to the page. Eg. The user details will most like be stored in a db table named 'Users' or 'Membership' Catch my drift?
Then have a look at the web sites source code. Look at the specific page you are at. Is the sql code embedded in the source code (asp page) or does it call a COM server or something similar? If you are "lucky" (and I say lucky for on the purpose of your problem that you are having) you fill find the sql code in the asp page.
If it calls a COM object or something similar, then you will have to dig up the source code for that, and that is most likely where you sql will reside.
There is no easy way to do this, you have to use a stored procedure to loop over all the tables in the database and search for the value, and it will probably take a while.
There's a stored procedure and examples here: Search all columns in all the tables in a database for a specific value. You'll see there are stored procedures for finding dates, strings, numbers.
Not possible, and If you search the column with the value, there is a possible chance that you get multiple columns with the same value, so how would you differentiate them and the same case is for the table.

Access Database

I need help in creating an query interface with access database.
In brief, with this query interface I want to see calculated future dates for different steps of a process based on the date the process actually started.
The future dates will always be at a fixed number of days after the start date. I hope I am able to explain this in an understandable manner.
I was thinking of using access forms? Please help me in this. I am not sure of how to proceed with this.
If your data is already in MS Access, then using Access Forms would be your easiest method of displaying that data. If the data is elsewhere, such as in Sql Server, you may be better suited in the long run using a different display technology.
That being said, to select a number of dates, as you would do in Access, you can use the Date Add function. If you had a table Processes with a column StartDate, you could use the query
SELECT *, DateAdd("d",5,StartDate) as "5 Days", DateAdd("d",36,StartDate) as "36 Days"
FROM Processes
to generate a record set to bind your form to. Binding that query to a new form is easy. You just need to change the record source by:
Right click anywhere blank in your new form
Select Properties
Change to the data tab
Click the button next to the text box labeled "Record Source"
Build your query using the built in editor (or, to paste the given SQL, right click in the designer view and select SQL view, then paste)
Close the query building dialog and use your new fields. You can drag them from the field list onto the design surface.

How does Access 2007's moveNext/moveFirst/, etc., feature work?

I'm not an Access expert, but am an SQL expert. I inherited an Access front-end referencing a SQL 2005 database that worked OK for about 5000 records, but is failing miserably for 800k records...
Behind the scenes in the SQL profiler & activity manager I see some kind of Access query like:
SELECT "MS1"."id" FROM "dbo"."customer" "MS1" ORDER BY "MS1"."id"
The MS prefix doesn't appear in any Access code I can see. I'm suspicious of the built-in Access navigation code:
DoCmd.GoToRecord , , acNext
The GoToRecord has AcRecord constant, which includes things like acFirst, acLast, acNext, acPrevious and acGoTo.
What does it mean in a database context to move to the "next" record? This particular table uses an identity column as the PK, so is it internally grabbing all the IDs and then moving to the one that is the next highest???
If so, how would it work if a table was comprised of three different fields for the PK?
Or am I on the wrong track, and something else in Access is calling that statement? Unfortunately I see a ton of prepared statements in the profiler.
THanks!
First is literally the first row in the Recordset. In general, Access accesses data via the equivalent of cursors. So, Next and Previous are moving forward and backwards in the Recordset one row at a time just as you can with SQL Server's cursors. Be careful about depending on the sequence of the rows without an ORDER BY statement in the construction of the Recordset. Although Access is an ISAM, you should not rely on the rows coming in any particular order. Depending on the cursor type, Access will not pull the entire table down but will generally ask for one record at a time. That said, I have seen Access pull entire tables for whatever reason.
You have to distinguish between automating Access objects and working with recordsets in code.
In a form, this command has meaning:
DoCmd.GoToRecord , , acNext
It is nonspecific, and it is not predictable what record it will go to unless you know the ordering of the underlying recordset in the form and the starting record. It navigates you through the recordset stored in the form's edit buffer (which is loaded in the OnOpen event of the form). The command would be used, for instance, in the code behind a command button whose purpose is to navigate records loaded into the form that currentlyl has the focus. I would never leave out the optional arguments if I were to use that command (I almost never do). Instead, I'd identify the specific form I wanted it to apply to:
DoCmd.GoToRecord acForm, "MyForm", acNext
In traversing a DAO recordset, .MoveNext likewise has no predefined meaning except if you know the ordering and starting record. When you are walking a recordset (something you shouldn't do very often, since it's pretty inefficient; but it depends on the task you need to perform) and need to hit each record, you'd certainly call .MoveNext as part of your loop:
With rs
.MoveFirst ' technically not required, as it's the default starting point
Do Until .EOF
[do something]
.MoveNext
Loop
End With
Nothing mysterious there. It's most likely going to be used in code with small numbers of records (large recordsets really oughtn't be navigated sequentially).
In answer to your specific question:
What does it mean in a database
context to move to the "next" record?
This particular table uses an identity
column as the PK, so is it internally
grabbing all the IDs and then moving
to the one that is the next highest???
...as I said, the next record is determined by the ordering of the recordset being traversed and the starting position. In the case of the form, it's the edit buffer that's being traversed, and as the current record bookmark changes in the edit buffer, the form is updated to load the data for that record. The dynaset is bound to the underlying data table, and when the form's edit buffer is saved, the edited data is written back to the server. While it's being edited, locks may or may not be maintained on the record on the server, but Access/Jet/ACE does keep track of the state of the existing record on the server and the record in the edit buffer and will inform you at save time in Access if the record on the server has been changed since it was loaded into the form's edit buffer.
Now, in a comment, you say the form is bound to the whole table. This is a terrible design no matter whether your data is stored in a Jet/ACE back end data file or in a server database like SQL Server. The only reason Access can get away with it is because it and Jet are rather efficient about pulling data from the data source.
I properly-designed client/server Access front end will not load full tables in forms, but instead ask what filtered recordset you want to load, either 1 or more records at a time. This is only marginally more complicated than binding to a whole table.
As to knowing what cursor types are being used, you shouldn't be worrying about it. By default, Access forms use what Access/Jet/ACE calls dynasets. Each form has a RecordsetType property, and it's set to dynaset by default (read the help file on the meaning of the different recordset types). If you want more control of that, you can (but likely shouldn't) create your recordsets in code and assign them to the form's .Recordset property. This is useful in a few circumstances, such as when you'd like to bind a form to a disconnected recordset, but the point of Access is leveraging its capabilities working with bound data. Assigning your own recordsets still gets you bound controls, and the form events, but is more work than is usually necessary.
Basically, change your forms to load only the subset of records the user needs to work with (that may be one record at a time), and then let everything else get done with Access's default behaviors. If something causes a bottleneck, then troubleshoot that and replace the default behavior with something more efficient.
In other words, avoid premature optimization -- let Access be Access.
And don't worry about what Access is doing behind the scenes unless/until Access does something inappropriate.