I am using this code to attach DSN less table.
'//Name : AttachDSNLessTable
'//Purpose : Create a linked table to SQL Server without using a DSN
'//Parameters
'// stLocalTableName: Name of the table that you are creating in the current database
'// stRemoteTableName: Name of the table that you are linking to on the SQL Server database
'// stServer: Name of the SQL Server that you are linking to
'// stDatabase: Name of the SQL Server database that you are linking to
'// stUsername: Name of the SQL Server user who can connect to SQL Server, leave blank to use a Trusted Connection
'// stPassword: SQL Server user password
Function AttachDSNLessTable(stLocalTableName As String, stRemoteTableName As String, stServer As String, stDatabase As String, Optional stUsername As String, Optional stPassword As String)
On Error GoTo AttachDSNLessTable_Err
Dim td As TableDef
Dim stConnect As String
For Each td In CurrentDb.TableDefs
If td.Name = stLocalTableName Then
CurrentDb.TableDefs.Delete stLocalTableName
End If
Next
If Len(stUsername) = 0 Then
'//Use trusted authentication if stUsername is not supplied.
stConnect = "ODBC;DRIVER=SQL Server;SERVER=" & stServer & ";DATABASE=" & stDatabase & ";Trusted_Connection=Yes"
Else
'//WARNING: This will save the username and the password with the linked table information.
stConnect = "ODBC;DRIVER=SQL Server;SERVER=" & stServer & ";DATABASE=" & stDatabase & ";UID=" & stUsername & ";PWD=" & stPassword
End If
Set td = CurrentDb.CreateTableDef(stLocalTableName, dbAttachSavePWD, stRemoteTableName, stConnect)
CurrentDb.TableDefs.Append td
AttachDSNLessTable = True
Exit Function
AttachDSNLessTable_Err:
AttachDSNLessTable = False
MsgBox "AttachDSNLessTable encountered an unexpected error: " & Err.Description
End Function
Suppose I changed a schema of a linked tables using ADO:
cn.execute "Alter table table 1 add add Name string not null"
I know this will change the table in SQL Server.
I thought that the linked tables in MS Access always reflected any changes made to the tables in SQL Server. Why do we need to delete the local table and relink it to reflect the schema changes?
The simple answer is that schema changes only are saved and gathered at link time for reasons of performance.
The linked table has no way really of knowing that the table structure has been changed until such time you tell Access to check (by simply refreshing the table).
I mean, when you are on a web page, they might add some new articles, or change the layout. The web server does not then call out to everyone on the internet and tell them that page has been updated. Seems like a silly approach.
The table structure is pulled at linking time. If every time you touched or used a table then performance would take a big hit, and lots of extra checking, chatter and communication with the server would have to occur.
If the linked table did not save information about the schema local, then you would not have this issue.
However, linking to a table not only saves connection information, but it also saves the information about the table structure.
Pulling data from a table is a spectacular and massive difference of an issue then asking sql server to send down the whole table structure. (And so what if it did – you would STILL THEN have to update the local information).
I mean, my form might have 2 fields out of 150 fields. Why would I want all that mass of information to come down the network pipe just to display two text boxes on a form? Why should 150 columns of information (name, length, data type, Auto number etc., indexing come down each time?
Have you seen the propriety sheet for each column on sql? It quite a large bit of information. In fact that information is quite a bit of data when you have 100 or more columns. The information sent down the network pipe would be MORE than the data contained in the one small record you are editing!!!
I mean, you might link to a view on sql server that only includes a few columns. Each query and use of that view will thus only pull the columns defined at view time.
In fact, even other views on sql server can have a hard time being updated when you modify a view. As a result even query on queries (views on views) EVEN when using pure sql server often need to be to “tell” that the data structure and columns you originally started out with are changed.
So linked tables in Access are not much different then saved views on sql server. If you update the base tables, then you have to execute a sp_refeshview command on sql server to update the view to know about these changes.
I mean, if you grab a word document from the server, and others start modify the copy on the server, does each work station know about the changes? Does the server reach out now?
So the information about the schema is persisted at link time, and the re-creating and building of the schema does not occur by just “using” the linked table. You have tell access to re-load the schema when changes are made.
Having to check the schema, and update the information in access for each record or each time you grab some data is a costly and time consuming process.
Since for 99.9% of your data pulling, the schema is not changing, so having all that overhead of pulling and testing the schema each time does not make much of any sense (it would be a poor idea and design).
As pointed out, even when not using Access, and using views on sql server that point to existing tables, the schema is persisted in that view until such time you refresh that view.
And this issue and problem certainly not limited to Access to Access back end, or Access to sql server back ends. Most database products simply don’t out of the blue update things like views that persist the original schema for reasons of performance.
I mean, I am on an access form, and I move to the next record. Are you telling me now that Access should start talking to the server and asking if the schema was changed? Makes no sense at all force the client to gather all that information and re-save the schema information.
I suppose perhaps on each first table use for a given session Access could query for this information, but really, what about all the users currently working? They not see the schema change anyway.
Schema changes are also rather rare. If they are frequent, then something is wrong with the developers - not the database system
Related
I have a Main table of 5000 rows, a Managers table of 51 rows and a Phase table of 16 rows. I created a query with a LEFT JOIN from the Main table to each of the other two:
SELECT [tblTrue-UpMain].Contract_Number, [tblTrue-UpMain].ProjectName, [tblTrue-UpMain].AFGroupName, [tblTrue-UpMain].AFAccountMgr, [tblTrue-UpMain].AFSubStationName, [tblTrue-UpMain].AFAssociatedContract, [tblTrue-UpMain].AFPreviousFile, [tblTrue-UpMain].AFFinancing, [tblTrue-UpMain].AFReplaceCoverage, [tblTrue-UpMain].AFBillingRate, [tblTrue-UpMain].AFProjectType, [tblTrue-UpMain].AFPreviousClients, [tblTrue-UpMain].ID, [tblTrue-UpMain].AF_VS_File_Number, [tblTrue-UpMain].Customer_Number, [tblTrue-UpMain].Contract_Execution_Date, [tblTrue-UpMain].Service_Account, [tblTrue-UpMain].Contract_Account, [tblTrue-UpMain].Contract_No_IF_Or_AF, [tblTrue-UpMain].CM, [tblTrue-UpMain].FinanceReplacementCPUC, [tblTrue-UpMain].Project_Type, [tblTrue-UpMain].Contract_Status, [tblTrue-UpMain].Contract_Phase, Local_ContractManagers.Contract_Manager, Local_Contract_Phase.PhaseName
FROM ([tblTrue-UpMain]
LEFT JOIN Local_ContractManagers ON [tblTrue-UpMain].CM = Local_ContractManagers.CMID)
LEFT JOIN Local_Contract_Phase ON [tblTrue-UpMain].Contract_Phase = Local_Contract_Phase.PhaseID
WHERE ((([tblTrue-UpMain].Contract_Number) Like "AF*"))
ORDER BY [tblTrue-UpMain].Contract_Number;
[tblTrue-UpMain].CM and [tblTrue-UpMain].Contract_Phase are both indexes. [Removing these indexes did not impact the time either way.]
Local_ContractManagers.CMID and Local_Contract_Phase.PhaseID are both Primary Keys. [INNER JOINS instead of LEFT JOINS ran for about 1 second in both cases.
When I run the query with the Managers and Phase tables as Access tables, it takes 1 second or less. If I run it with the Managers and Phase tables on SQL Server, it takes over 40 seconds. The Main table is on SQL Server for both queries.]
Running the query on SSMS (with all tables on SQL Server) runs quickly too.
Any ideas why it might be running so long?
As long as the tables are all SQL server based?
Then the query should not take that long. However, if you mix a local table and a SQL server table, then it will be slow as turtles.
However, one way to fix the issue?
Fire up SQL manager, and build the same query in SQL server.
In fact, choose create new view. A SQL "view" is really like a Access saved query, and you get to use familiar query builder (it similar to the Access one). you will note that sorting is not in general allowed, so, do that in the report, or even build a client-side query against that view.
Now, save this view, and then create a link to this view Access side.
I do this so often, I have a little helper VBA routine to add this one link, and that's a whole lot less than using the ODBC manager from the ribbon.
Now, in place of the local query you have, try using the SQL "view" (it will appear as a linked table).
The result should be a VERY high performance, and as good as when you try the query in sql studio.
Views tend to be a far better choice than say using a stored procedure, since it is much easier to use in Access and even VBA code.
In fact, I recommend giving the linked view the SAME name as what you had for the local SQL query. That way, existing forms, code or reports don't require any modifications, and you get fantastic performance.
This performance includes even opening a report with a "where" clause. So, you can still use client-side filters, or even one's created in VBA that launches the report with "where" clause. Access will ONLY pull those records that meet the criteria (and thus this approach is a network/bandwidth friendly approach).
So, anytime you encounter a slow turtle like performance? Try the view approach, it has fixed every performance issue I seen.
For a simple query based on one table? Then a view doesn't help. But, once you start introduction of joins and additional tables, then access can't do a proper join without pulling a lot of data - so a view really helps.
[tblTrue-UpMain].CM and [tblTrue-UpMain].Contract_Phase are both indexes.
We assume you talking about SQL server-side indexes. Do NOT attempt to create index's client side - they don't do anything and can't be used.
I also STRONG suggest you do NOT attempt to create index(s) server side when using a view. ALWAYS create the index(s) on the base sql server tables server side - nothing more, nothing less.
Edit: Adding a link with vba in place of odbc manager.=
So, as noted, since one often needs to add one new talbe, or one new view (as a link from SQL server), then I wrote a VBA routine to make save time.
Nothing speical about the routine. I have a boatload of such routines. I mean, once one adopts a split database, then for ease of deployment, we all over time "cobbile" together some re-link code. This holds true for just a access back end, or a sql server one.
So, my little VBA helper routine to add one link?
Say I just created a new view sql server side. say viewCustomers.
Now, I need a linked table (or view) in the client side. Too much time + pain to fire up the odbc manager and go through all those steps to link just one table.
So, I have this routine, and run it from the debug window.
So, I'll hit ctrl-g, and in the debug window type in this:
MyCreateOneLink "server table", "Local name for link"
So, the above is much quicker.
So, what does the above routine look like?
In my standard code module, I have this:
Sub MyCreateOneLink(strServerTable As String, strLocalTable As String)
Dim tdfcurrent As DAO.TableDef
Set tdfcurrent = CurrentDb.CreateTableDef(strLocalTable)
tdfcurrent.connect = dbCon("MYSERVER\SQLEXPRESS", "Customers", "MyUserName", "MyPassword", cControlPassword, "MyApp")
tdfcurrent.SourceTableName = strServerTable
CurrentDb.TableDefs.Append tdfcurrent
End Sub
And above calls a routine "dbcon()". That routine creates a valid SQL connection string from values passed.
this:
Public Function dbCon(ServerName As String, _
DataBaseName As String, _
Optional UserID As String, _
Optional USERpw As String, _
Optional APP As String = "Office 2010", _
Optional WSID As String = "Axis") As String
' returns a SQL server conneciton string
dbCon = "ODBC;DRIVER=SQL Server;" & _
"SERVER=" & ServerName & ";" & _
"DATABASE=" & DataBaseName & ";"
If UserID <> "" Then
dbCon = dbCon & "UID=" & UserID & ";" & "PWD=" & USERpw & ";"
End If
dbCon = dbCon & _
"APP=" & APP & ";" & _
"WSID=" & WSID & ";" & _
"Network=DBMSSOCN"
End Function
above will have to be changed for "native" driver, but the simple idea here is to have a quick code and thus from the access command prompt (debug window), I can type in a quick easy command to create a table link.
So, often during development, one will use the access command line (immediate) window, and we can thus call little VBA helper routines, such as one to create a table link for us.
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.
I am in the process of converting an Access application to use a SQL Server backend while still using the Access front end forms. Sounds like fun I know.
This application needs data access to 2 SQL Server databases that are on the same server. There are numerous inline sql query strings that attempt to connect to both databases at the same time on a single ADODB connection. This is failing because I am expecting records but none are returned.
What is the best way to fix this? Is there any way to use these sql strings or must it all be converted to stored procedures? Thanks for any help.
Here is some code:
Dim conn As ADODB.Connection
Set conn = New ADODB.Connection
Dim rst As ADODB.Recordset
Set rst = New ADODB.Recordset
With conn
.Provider = "sqlncli11"
.ConnectionString = "Server=[MY_SERVER];Database=[MY_DATABASE];User Id=sa; Password=password;"
.Open
End With
Dim str As String
str = "SELECT TABLE_DB1.Parent_Item_No FROM TABLE_DB1 INNER JOIN [DB2].[dbo].TABLE_DB2 ON (TABLE_DB1.Comp_Item_No = " & _
"TABLE_DB2.item_no) AND (TABLE_DB1.Loc = TABLE_DB2.loc) " & _
"GROUP BY TABLE_DB1.Parent_Item_No " & _
"HAVING (((TABLE_DB1.Parent_Item_No)='" & str_Assembly & "'));"
With rst
.Open str, conn, adOpenKeyset, adLockOptimistic ' this fails to return records
If .RecordCount > 0 Then
'Do Stuff
Else
'Do Other Stuff
End If
End With
You're only checking RecordCount. Take a look at this: slxdeveloper.com/page.aspx?action=viewarticle&articleid=33 Some recordset types don't populate the RecordCount property (adOpenKeyset should though). What happens if you use While Not .EOF and .BOF instead? What is that actual value of RecordCount in your code?
Would it at all be possible to run queries from SQL which save to an access file? I have nothing but trouble pulling data directly into access. I do have success when I set up an ODBC database and go to data -> get external data -> from other sources -> From microsoft query
Another method I myself have been successful with is using the power query add-on from microsoft. https://www.microsoft.com/en-us/download/details.aspx?id=39379
Despite this, what I still mostly end up doing is using the SQL import/export tool. I don't have screenshots or specific instructions as I'm not at work right now, but this can write directly to text files, Access databases, everything. I love it too much. Getting the correct drivers was a loop for sure. If you're on 64-bit and having issues then this is the driver you need. http://www.microsoft.com/en-us/download/details.aspx?id=13255
What I do is:
Set up my source (SQL 11 native client), choose the database you're pulling from
Specify an outfile type and location (I think it has to already exist)
When prompted to specify whether to pull data from tables and views or write a query, select write a query.
Go through the rest of the importer, you can edit the sql statement later on when viewing conversion and specify whether the transfer fails or ignores errors etc.
I personally still use the import export tool for transfers of all sizes because it's just so difficult to get all the correct drivers and get SQL to like what I want. (and without admin rights I get tired of asking my boss).
I hope one of those solutions can help you!
I've outline a more proper fix and a quick fix...
The more proper fix is the Data Layer Pattern. There is a lot to this fix and it may require some application structural changes. This is discussed in depth in another question:
Data Access Layer design patterns
A very simple fix is to use Access Linked tables. A Linked Table works like a normal Access table except the data is stored and updated on the SQL Server. Its basically a built in Data Access Layer to SQL Server. Its not an elegant solution but it gets you up and running right away. More info can be found here:
https://support.office.com/en-us/article/Import-or-link-to-SQL-Server-data-a5a3b4eb-57b9-45a0-b732-77bc6089b84e#bm2
On thing to be aware of with Linked Tables are that some Access Queries and Forms retrieve all the records before filtering and can lock the table so you can end up with some performance headaches if you have lots of data and lots of users.
Consider using the SQL server SYNONYM feature to add aliases for objects in one database to the other. Then just update all your queries to use one database.
Also, you could merge the two databases with each one, or one of them, going into a new schema to keep them separate. This could be tough if you have a lot of stored procedures, views, and functions in the database. This may be a terrible answer, but it could also be true that the two databases should never have been separate in the first place.
In the INNER JOIN, you prefixed the table name with DatabaseName.Schema.:
... FROM TABLE_DB1 INNER JOIN [DB2].[dbo].TABLE_DB2 ...
But you didn't do it in the other places where TABLE_DB2 occurs.
So you either need to change this:
ON (TABLE_DB1.Comp_Item_No = TABLE_DB2.item_no) AND (TABLE_DB1.Loc = TABLE_DB2.loc)
...to this:
ON (TABLE_DB1.Comp_Item_No = [DB2].[dbo].TABLE_DB2.item_no) AND (TABLE_DB1.Loc = [DB2].[dbo].TABLE_DB2.loc)
Or (which I prefer) you can use aliases for the table names in the FROM clause:
... FROM TABLE_DB1 t1 INNER JOIN [DB2].[dbo].TABLE_DB2 t2...
...then you use the aliases everywhere else:
str = "SELECT t1.Parent_Item_No FROM TABLE_DB1 t1 INNER JOIN [DB2].[dbo].TABLE_DB2 t2 ON (t1.Comp_Item_No = " & _
"t2.item_no) AND (t1.Loc = t2.loc) " & _
"GROUP BY t1.Parent_Item_No " & _
"HAVING (((t1.Parent_Item_No)='" & str_Assembly & "'));"
Additional background information:
If you connect to an SQL Server via ADO, you're directly connecting to exactly one database - the one in the connection string:
.ConnectionString = "Server=[MY_SERVER];Database=[MY_DATABASE];User Id=sa; Password=password;"
So in your case, the database you're connecting to is named MY_DATABASE. Any SQL you're executing via ADO goes to that database.
If you need to get data from other databases on the same server, you need to prefix the names with DatabaseName.Schema. in all places where you use them.
So let's assume we have:
a table MY_TABLE in MY_DATABASE
a table OTHER_TABLE in OTHER_DATABASE on the same server
both tables have the schema dbo (the default in SQL Server)
With the connection string from above (connecting to MY_DATABASE), you can join them as follows:
select *
from MY_TABLE
inner join OTHER_DATABASE.dbo.OTHER_TABLE
on MY_TABLE.SomeColumn = OTHER_DATABASE.dbo.OTHER_TABLE.OtherColumn
where OTHER_DATABASE.dbo.OTHER_TABLE.EvenAnotherColumn = 'foo'
See? Everywhere I used OTHER_TABLE, I prefixed it with OTHER_DATABASE.dbo..
PS:
It's bad practice to use the sa user to connect to a database with an application. The sa user has the highest permissions possible.
You should either use Windows authentication or create a dedicated SQL user for your app.
Consider storing your SQL in a pass-through query instead of VBA code. You can apply your filter using a copy of the .sql property of the pass-through query's querydef object, modifying it with the criteria they enter in your form at runtime.
I have single big table in Ms access 2k daabase and i need a way to copy this table and then populate my two already ready tables on sql server .
I can use Migration tool but is there any way we can do it from Ms access like a form which executes the stored procedure or ODBC connection.
what i am thinking is to create a form in ms access which should have a browse button for selecting my source access mdb file and then another button for processing[on clicking the table specified above by browse button should be imported into sql server]??
Is this possbile?
please give me some details
Thanks in Advance
You could set up table links to the two SQL server tables from inside MS Access.
Then you could create a query that populates each table from the source table.
I have single big table in Ms access 2k database and I need a way to copy this table and then populate my two already ready tables on SQL server .
How big is "big"? SQL Server should be able to handle any single table you would ever store in Access. I don't think a terabyte of data would be unfeasible.
You could use Microsoft SSIS. Or just dump a .csv representation of the Access database and import it into SQL Server. Or upsize Access to SQL Server.
Your question is confusing to me. Are the two "already ready" tables in addition to this one big table, with other data to be stored in them? I certainly hope that they don't have the identical layout as the "big" table in Access (violating first normal form) and encourage you to divide the data from Access between them, thinking that it's an improvement of some kind.
You could create an Access Project (.adp) to the Sql Server and then use the File>Import menu in MS Access (in the .adp) to import your table into the (.adp) and thus Sql Server.
Then just use a simple SELECT INTO stored procedures to populate your tables in Sql Server from the imported table.
The following is some code that you can use to generate a DSNless link to a table in a SQL Server database from Access. It works, at least, in Access 2003. After you have run this code, the SQL tables can be manipulated directly just like any other Access table.
'Name : AttachDSNLessTable
'Purpose : Create a linked table to SQL Server without using a DSN
'Parameters
' stLocalTableName: Name of the table that you are creating in the current database
' stRemoteTableName: Name of the table that you are linking to on the SQL Server database
' stServer: Name of the SQL Server that you are linking to
' stDatabase: Name of the SQL Server database that you are linking to
' stUsername: Name of the SQL Server user who can connect to SQL Server, leave blank to use a Trusted Connection
' stPassword: SQL Server user password
Function AttachDSNLessTable(stLocalTableName As String, stRemoteTableName As String, stServer As String, _
stDatabase As String, _
Optional stUsername As String, Optional stPassword As String)
On Error GoTo AttachDSNLessTable_Err
Dim td As TableDef
For Each td In CurrentDb.TableDefs
If td.Name = stLocalTableName Then
CurrentDb.TableDefs.Delete stLocalTableName
End If
Next
If Len(stUsername) = 0 Then
'Use trusted authentication if stUsername is not supplied.
stConnect = "ODBC;DRIVER=SQL Server;SERVER=" & stServer & ";DATABASE=" & stDatabase & ";Trusted_Connection=Yes"
Else
'WARNING: This will save the username and the password with the linked table information.
stConnect = "ODBC;DRIVER=SQL Server;SERVER=" & stServer & ";DATABASE=" & stDatabase & ";UID=" & stUsername & ";PWD=" & stPassword
End If
Set td = CurrentDb.CreateTableDef(stLocalTableName, dbAttachSavePWD, stRemoteTableName, stConnect)
CurrentDb.TableDefs.Append td
AttachDSNLessTable = True
Exit Function
AttachDSNLessTable_Err:
AttachDSNLessTable = False
MsgBox "Error while trying to link to SQL Server on " & stServer & ": " & Err.Description
End Function
I suspect that running an INSERT from Access with ODBC linked tables would be very slow, as Jet/ACE tends to break down each row into a separate INSERT statement. Jet/ACE is being a good citizen user of the mult-user server database, since this allows the server to serialize operations generated by multiple users and interleave others updates in with the massive update. But it's horridly slow for large datasets, and when you're doing something like you are, which is initializing an empty dataset, it's being too "civically responsible."
You might consider renaming the existing table, and instead exporting the Access table up to SQL Server via an ODBC DSN. If you have the DSN already defined, you just choose EXPORT from the FILE menu in Access, choose the DSN as a destination, and Jet/ACE takes care of the rest, creating the table on the server and uploading all the data. It's very efficient (i.e., won't do it one record at a time) since Jet/ACE knows it's populating a table that didn't previously exist.
Now, the result might not be exactly what you like (Jet/ACE might not get all the data types right because of the translation aspects of ODBC drivers, but the data types should be compatible, if the the exact strictest data types desired), but you will then have your full dataset in you SQL Server database, and can then append from one SQL Server table to the correctly structured one.
You would want to do a check of the data to make sure no data has been lost or incorrectly transformed (an example of this would be a text zip code field getting converted to a number -- this would actually never happen, but it's the kind of thing you'd want to check for), but I'd think for a 500MB dataset when you don't have good upsizing tools (because the Access versions can't keep up with the recent SQL Server versions), this might get the job done more efficiently with less work.
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.