Programatically link SQL Server tables in a new Access 2007 database - ms-access

I manage an SQL Server 2005 Database and I would like to give read-only access to the necessary tables to a group of 20-30 networked users who are able to use the GUI in MS Access 2007 to write or modify their own queries to the database, with some help.
I would like to distribute an Access database with a single form that would create links to the necessary tables. All of these users are included in a group with read-only permissions to the SQL Server database. I can distribute a dsn file for the connection, but I haven't found a way to programatically create the links to the 50 or so tables they might need, with their network credentials from an otherwise empty Access database.
I found a line of VB code from answer to a similar question onstackoverflow (below), but I was wondering if there was any simpler way than running the modified command once for each of the 50 or so tables.
DoCmd.TransferDatabase acLink, "ODBC Database", "ODBC;DRIVER=Microsoft ODBC for Oracle;SERVER=myserver;UID=myuser;PWD=mypassword", acTable, "SCHEMA.TABLE", "TABLE", False, True

I just wrote an article last week detailing a way to quickly link all tables in an SQL Database to Access. Here are some Access methods that will help. Read the article for more instructions on using it.
You will need to reference the Microsoft ActiveX Data Objects library.
Sub LinkAllTables(Server As String, database As String, OverwriteIfExists As Boolean)
'Usage Example (link all tables in database "SQLDB" on SQL Server Instance SQO01, overwriting any existing linked tables.
'linkalltables "SQL01","SQLDB", true
'This will also update the link if the underlying table definition has been modified.
Dim rsTableList As New ADODB.Recordset
Dim sqlTableList As String
sqlTableList = "SELECT [name] as tablename FROM sysObjects WHERE (type = 'U')"
rsTableList.Open sqlTableList, BuildSQLConnectionString(Server, database)
While Not rsTableList.EOF
If LinkTable(rsTableList("tableName"), Server, database, rsTableList("tableName"), OverwriteIfExists) Then
Debug.Print "Linked: " & rsTableList("tableName")
End If
rsTableList.MoveNext
Wend
rsTableList.Close
Debug.Print "Done."
End Sub
Function LinkTable(LinkedTableAlias As String, Server As String, database As String, SourceTableName As String, OverwriteIfExists As Boolean)
'This method will also update the link if the underlying table definition has been modified.
'The overwrite parameter will cause it to re-map/refresh the link for LinktedTable Alias, but only if it was already a linked table.
' it will not overwrite an existing query or local table with the name specified in LinkedTableAlias.
'Links to a SQL Server table without the need to set up a DSN in the ODBC Console.
Dim dbsCurrent As database
Dim tdfLinked As TableDef
' Open a database to which a linked table can be appended.
Set dbsCurrent = CurrentDb()
'Check for and deal with the scenario ofthe table alias already existing
If TableNameInUse(LinkedTableAlias) Then
If (Not OverwriteIfExists) Then
Debug.Print "Can't use name '" + LinkedTableAlias + "' because it would overwrite existing table."
Exit Function
End If
'delete existing table, but only if it is a linked table
If IsLinkedTable(LinkedTableAlias) Then
dbsCurrent.TableDefs.Delete LinkedTableAlias
dbsCurrent.TableDefs.Refresh
Else
Debug.Print "Can't use name '" + LinkedTableAlias + "' because it would overwrite an existing query or local table."
Exit Function
End If
End If
'Create a linked table
Set tdfLinked = dbsCurrent.CreateTableDef(LinkedTableAlias)
tdfLinked.SourceTableName = SourceTableName
tdfLinked.Connect = "ODBC;DRIVER={SQL Server};SERVER=" & Server & ";DATABASE=" & database & ";TRUSTED_CONNECTION=yes;"
On Error Resume Next
dbsCurrent.TableDefs.Append tdfLinked
If (Err.Number = 3626) Then 'too many indexes on source table for Access
Err.Clear
On Error GoTo 0
If LinkTable(LinkedTableAlias, Server, database, "vw" & SourceTableName, OverwriteIfExists) Then
Debug.Print "Can't link directly to table '" + SourceTableName + "' because it contains too many indexes for Access to handle. Linked to view '" & "vw" & SourceTableName & "' instead."
LinkTable = True
Else
Debug.Print "Can't link table '" + SourceTableName + "' because it contains too many indexes for Access to handle. Create a view named '" & "vw" & SourceTableName & "' that selects all rows/columns from '" & SourceTableName & "' and try again to circumvent this."
LinkTable = False
End If
Exit Function
End If
On Error GoTo 0
tdfLinked.RefreshLink
LinkTable = True
End Function
Function BuildSQLConnectionString(Server As String, DBName As String) As String
BuildSQLConnectionString = "Driver={SQL Server};Server=" & Server & ";Database=" & DBName & ";TRUSTED_CONNECTION=yes;"
End Function
Function TableNameInUse(TableName As String) As Boolean
'check for local tables, linked tables and queries (they all share the same namespace)
TableNameInUse = DCount("*", "MSYSObjects", "(Type = 4 or type=1 or type=5) AND [Name]='" & TableName & "'") > 0
End Function
Function IsLinkedTable(TableName As String) As Boolean
IsLinkedTable = DCount("*", "MSYSObjects", "(Type = 4) AND [Name]='" & TableName & "'") > 0
End Function

In addition to what David proposed, you could have a local (client side) table listing the list of tables available through the SQL connection. You could then write a piece of VBA code that will browse this table to establish all corresponding connections:
Dim rsTable as DAO.recordset
set rsTable = currentDb.openRecordset("Tbl_Tables")
if rsTable.EOF and rsTable.BOF then
else
rsTable.moveFirst
Do while not rsTable.EOF
DoCmd.openDatabase .... 'enumerate here all needed paarmeters with rsTable.fields("tableName") in the string'
rsTable.moveNext
Loop
Endif
rsTable.close
set rsTable = Nothing
This piece of code was written on the fly, so I cannot garantee it will work 'as is'. This code could for example be launched at startup (through the autoexec macro) so that your users will have their links ready when they open their app.
The 'view-only' thing can be easily managed by listing corresponding users (or, if you have a Domain, the corresponding group of users) as 'data readers' on your SQL server.

Is there a special reason why you want to re-create the links every time?
It would be much simpler to create once the mdb with the linked tables, and distribute that mdb to your users.
You might also want to link SQL Server Views (instead of tables) to Access tables, in order to make sure it's read only, maybe pre-join some tables, and eliminate some fields they do not require.

Why not use an Active Data Project in Access?
Linked tables are really only useful if you also need local (unlinked) tables. If you can keep all the tables and views on SQL Server and leave the forms in Access, an ADP will work fine and won't require "linking" any tables manually or via scripting.
In response to Patrick below, if you don't want them mucking around creating queries in your real SQL Server store, create a second SQL Server database that they have rights to create and update queries in, and create VIEWs like the following:
CREATE VIEW mytable AS SELECT * FROM [real database].dbo.mytable
Thus, when you change your master data tables, you only have to make a change to the VIEW in their shared SQL Server database, not a change to each Access database.
Side advantage #1: the users can see each other's queries, thus giving a social aspect of easily sharing good queries.
Side advantage #2: since they are all in one place, you can use SQL Server to detect which user queries will break if you make a change to one of the read-only tables (by searching the view definitions created by Access).

If your SQL Server uses Windows security instead of SQL Server security, then you don't have to supply a username/password in your connect string.
Here's the standard way to do this:
on your development machine, create a DSN for your SQL Server database.
use FILE | GET EXTERNAL DATA | LINK TABLES to link to the tables via ODBC.
get Doug Steele's code to convert to DSN-less connect strings.
then just distribute the front end as is to your users.
The key to this scenario is using Windows security rather than SQL Server security -- Access silently passes the credentials when it requests the connection via ODBC. This is one reason I'd never use SQL Server security -- too much trouble!

Related

Need to Change Linked Tables in MS Access DB with Username, Password and System DB

I have a front end DB, which needs to link to different back end DBs. To give you perspective, it relates to stand alone MDB files. The software in question builds a DB per company.
At the moment I am writing code within one of these MDB files.
For scalability I am now creating a new DB which will link to each MDB via code, and therefore my questions are as follows
How do I change the linked table location via code / VB so that the user can select the company / DB they want to work on
How do I do this with passing a username and password which is the same for all of the companies / DBs
And as per below we need to verify the username and password via the systemDB for it to open successfully.
As an FYI, this is how we open the DB on a standalone basis-
"C:\Program Files (x86)\Microsoft Office\root\Office16\MSACCESS.EXE" "C:\temp\SAMPLE.mdb" /WRKGRP "C:\ProgramData\SOFTWARE\SYSTEM.mdw" /user:username /pwd:password
This is not a problem at all and is absolutely able to be accomplished given the clarification that you are using a single MDW file.
To clarify Microsoft Access Workgroup Security is essentially a "session" security model that applies directly to the front-end MDB file as you open it.
Your example command line means that Microsoft Access will open the SAMPLE.MDB front-end file using the workgroup file you specified.
Once Microsoft Access has opened SAMPLE.MDB under that workgroup file, you cannot change to another workgroup file within that "session" without closing Microsoft Access and re-opening under the new workgroup file.
FYI - it IS possible to open via code, a table in another MDB using another workgroup file within that connection, but in this manner, the table is only usable in code as a RecordSet (for example), you can't make it a linked table.
Anyway, back to your real issue. How to link a different back-end set of tables for each Company.
My recommendation would be to add a few fields to your Company table that defines the filename and location of each back-end file. For example:
Notice that the location can be a UNC path, or a mapped drive path. Or maybe you don't need to define a location in the table explicitly. Maybe all of the back-ends are in the same folder, or in a definable dynamic location like \Dallas\Dallas.mdb, \NewYork\NewYork.mdb, etc. As long as you can determine the location of each back-end in some manner, then you are fine.
Now, since you will likely have "global" front-end tables, maybe also some "global" linked back-end tables i.e. Common.mdb, and your company-specific back-end tables, I would recommend having a front-end table that defines the name of each of the tables that is involved only in the company-specific files. That way, we can easily loop through just those table names and make the link changes.
For the linking code, let's say that you have prompted the User for which Company they want, and you pass the CompanyID to a re-linking function:
Public Function ChangeCompanyLinks(CompanyID As Long) As Boolean
Dim db As DAO.Database
Dim ldb As DAO.Database
Dim tdf As DAO.TableDef
Dim rstCompany As DAO.Recordset
Dim rstTables As DAO.Recordset
Dim mssql As String
Dim dbFullPath As String
Dim retVal As Boolean
Set db = CurrentDb()
retVal = False
mssql = "SELECT * FROM [tblCompany] WHERE [CompanyID] = " & CompanyID
Set rstCompany = db.OpenRecordset(mssql, dbOpenSnapshot)
If Not rstCompany.BOF Then
dbFullPath = rstCompany("DBLocation") & "\" & rstCompany("DBName")
If Dir(dbFullPath) = rstCompany("DBName") Then
'NOTE: By opening a temporary constant link to the back-end during
' relinking, the relinking runs faster
Set ldb = OpenDatabase(dbFullPath)
mssql = "SELECT * FROM [tblLinkedTables] WHERE [FileType] = ""Company"""
Set rstTables = db.OpenRecordset(mssql, dbOpenSnapshot)
Do While Not rstTables.EOF
Set tdf = db.TableDefs(rstTables("TableName"))
tdf.Connect = ";DATABASE=" & dbFullPath
tdf.RefreshLink
rstTables.MoveNext
Loop
rstTables.Close
ldb.Close
retVal = True
Else
MsgBox "Unable to Locate Company File"
End If
End If
rstCompany.Close
ChangeCompanyLinks = retVal
Set rstCompany = Nothing
Set rstTables = Nothing
Set ldb = Nothing
Set tdf = Nothing
db.Close
Set db = Nothing
End Function
Obviously you will want to add error handling, and customize this a bit to fit your situation, but this code will re-link the specified tables to a new back-end.
Please note that if you eventually change to have your back-end tables in SQL Server (which I highly recommend), the re-linking code would need to be modified a bit. See this answer for more details on that.

List Box in Access with linked MySQL table

I have an MSaccess database with 2 tables (process and data_type). Each process can contain 0-many types of data_type. Within access, I have a form that displays a process with a list-box, (see pic), that has all of the data_types. The user can then check the data_types that each process has.
We are in the process of migrating the data from access to MySQL. As part of that I've created a process2data table that links the process and data_type table. Unfortunately, I don't know how to create a form in Access (we are still, for now, using that as our display engine) that accomplishes the same thing with the online data.
You can download what I'm trying to do here:
(For simplicity, I've put copies of the online data tables that are in mySQL as offline tables in Access.)
The multi-valued fields works only on MS-Access databases, to emulate we need to add VBA code and a table to store the values.
Create intermediate table, in this case I have created [ProcessList_DataType], the [checked] field will be used to include the data type, see picture:
Create a form for ProcessList and a subform for [Datatypes] and [ProcessList_DataType] combined tables. Don't forget configure the link master fields.
Add code to check the referenced values are present on intermediate table:
Private Sub Form_Current()
On Error GoTo ErrExit
If Not IsNull(Me!ProcessID) Then
sql = "INSERT INTO ProcessList_DataType(ProcessList,DataType) " & _
"SELECT " & Me!ProcessID & ",datatype_id " & _
"FROM DataTypes WHERE datatype_id not in " & _
"(SELECT datatype_id from FilterQuery " & _
" WHERE ProcessList = " & Me!ProcessID & " )"
Set db = CurrentDb()
db.Execute sql
cnt = db.RecordsAffected
If cnt > 0 Then
NeedRefresh = True
End If
End If
ErrExit:
End Sub
Enjoy!

Editing a query in another DB

Is it possible to open a second Access database from within an Access database, and edit a query in that second DB? I know you can open one Access DB from another, but I'm just not sure whether or not you can edit a query that way.
If it's possible, can anyone show me some sample code to do this?
Use OpenDatabase to return a DAO.Database reference to your remote database. Then you can access a saved query via its QueryDefs collection.
Here is an example from the Immediate window:
set db = OpenDatabase("C:\share\Access\Database1.mdb")
Debug.Print db.QueryDefs("Query1").SQL
SELECT dbo_foo.bar, TypeName(bar) AS TypeOfBar
FROM dbo_foo;
db.QueryDefs("Query1").SQL = "SELECT d.bar, TypeName(d.bar) AS TypeOfBar" & vbcrlf & _
"FROM dbo_foo AS d;"
Debug.Print db.QueryDefs("Query1").SQL
SELECT d.bar, TypeName(d.bar) AS TypeOfBar
FROM dbo_foo AS d;
db.close

Is there a way to tell if another Access db is linked to mine?

I have an old Access db with a plethora of lookup tables. Supposedly it was a warehouse of sorts for a bunch of other dept-made access apps to link to. We want to kill it. But, id there a way to find out if any app is currently linked to it?
You need the full path and filename of all the Access apps; this may not be possible.
For those you can, loop through all the files:
connect to each database to test for link.
Loop through all the tables in TestForLinkDatabase.TableDefs
Check to see if there is a .SourceTableName and the .Connect = YourLookupTableWarehouse for each table. I think the SourceTableName is an empty string for local tables.
Keep track of #3. You can optionally stop checking the rest of the tables if you find a single instance in the other file.
Again, it is not foolproof, but would be a good exercise to get a grip on all the Access apps floating around your company.
*Code does not exclude system tables.
Private Sub CheckToSeeIfLinked()
Dim Dbs As DAO.Database
Dim Tdf As DAO.TableDef
Dim Tdfs As TableDefs
Dim wrk As DAO.Workspace
Set wrk = DBEngine.Workspaces(0)
Dim TestDatabaseForLinks As String
TestDatabaseForLinks = "C:\FileNameToCheck.mdb"
Set Dbs = wrk.OpenDatabase(TestDatabaseForLinks)
Set Tdfs = Dbs.TableDefs
For Each Tdf In Tdfs
If Tdf.Connect <> "" Then
Debug.Print "Table: " & Tdf.Name & " - Is Linked To: " & Tdf.Connect
Else
Debug.Print "Table: " & Tdf.Name & " is not linked"
End If
Next
If Not (Dbs Is Nothing) Then
Dbs.Close
Set Dbs = Nothing
Set Tdfs = Nothing
End If
End Sub
Move it to another directory. Linked tables have hard-coded paths.
Not while the database isn't in use. When it is in use you should see an LDB/LACCDB file. You can open it with Notepad to see the workstation name.
If you are using Access security you will also see the Access userid. Otherwise you will see "Admin"
Opening the .ldb/.laccdb file using notepad will show you both who's currently in the database and some of the workstations which were in the database. When a person exits Access their workstation name and Access login id, Admin unless you are using Access security, are left in a "slot" or record in the ldb file. This slot or record may get overwritten the next time someone enters the MDB depending on what slot or record is available previous to it in the ldb file.
Determining the workstation which caused the Microsoft Access MDB corruption

Adding linked table to Access 2003 while keeping ODBC connection info in MDB

I have an Access 2003 database MDB where all of the tables exist as linked tables within SQL Server 2005. The MDB file contains all of the ODBC information that points to the correct SQL Server and log-on credentials (trusted connection).
What I would like to do is add a new linked table to the MDB file however I am not sure how to go about specifying the ODBC connection information. When I try to add a new linked table I keep getting prompted to locate or create a DSN file. I don't want to have to create a new DSN entry on every machine, rather I would like all that information stored within the Access MDB file itself.
In the existing database I can "hover" over the table names and see the ODBC connection info as a tool-tip. All I need to do is add another linked table using the same connection information.
I do have access to the SQL Server where the tables are linked to,. I have already created the new table I wanted to add. I just need to find a way to link to it.
For what it's worth, I'm lazy. I keep a DSN on my development machine, and use it to create new linked tables. I then run Doug Steele's code to convert the links to dsnless connections before distribution of the front end to the end users.
You can use the connection string from an existing table, or you can do something like:
''This is a basic connection string, you may need to consider password and so forth
cn = "ODBC;DSN=TheDSNName;Trusted_Connection=Yes;APP=Microsoft Office 2010;DATABASE=TheDatabaseName;"
There are a few was to connect a table:
sLocalName = "TABLE_SCHEMA" & "_" & "TABLE_NAME"
With CurrentDb
If DLookup("Name", "MSysObjects", "Name='" & sLocalName & "'") <> vbNullString Then
If .TableDefs(sLocalName).Connect <> cn Then
.TableDefs(sLocalName).Connect = cn
.TableDefs(sLocalName).RefreshLink
End If
Else
''If the table does not have a unique index, you will neded to create one
''if you wish to update.
Set tdf = .CreateTableDef(sLocalName)
tdf.Connect = cn
tdf.SourceTableName = "TABLE_NAME"
.TableDefs.Append tdf
.TableDefs.Refresh
End If
End With
This will produce a message box if the table does not have a unique index
DoCmd.TransferDatabase acLink, "ODBC Database", cn, acTable, "TABLE_NAME", sLocalName
I was able to add the table successfully and wanted to detail the steps here.
I added the new table by clicking "New" within Access and chose "Link Table"
When prompted with the Link file dialog I chose ODBC from the file type list box
I created a new DSN item (only used for initial linking to table)
Proceed with creating DSN and you will follow process of linking to the table created in SQL Server
Table should show up in Access
I then created the following sub routine in the code module. It essentially loops through all your tables in Access that have ODBC connections and sets the proper ODBC connection info into the Table definitions. You can delete the DSN you created previously as it is no longer needed.
Public Sub RefreshODBCLinks()
Dim connString As String
connString = "ODBC;DRIVER=SQL Server Native Client 10.0;" & _
"SERVER=<SQL SERVER NAME>;UID=<USER NAME>;" & _
"Trusted_Connection=Yes;" & _
"APP=<APP NAME>;DATABASE=<DATABASE NAME>"
Dim db As DAO.Database
Dim tb As DAO.TableDef
Set db = CurrentDb
For Each tb In db.TableDefs
If Left(tb.Connect, 4) = "ODBC" Then
tb.Connect = connString
tb.RefreshLink
Debug.Print "Refreshed ODBC table " & tb.Name
End If
Next tb
Set db = Nothing
End Sub
NOTE ... To execute the above SubRoutine I just typed in it's name into the "immediate" windows within the Access code module. You could also create a macro that executes this routine and then whenever you create a new table you could just run the macro.
Thanks to "Remou" for his answer and assistance!
P.S. If you are interested in APP=<APP NAME> in the connection string and what it is for check out this article. http://johnnycoder.com/blog/2006/10/24/take-advantage-of-application-name/