当我使用向导创建到SQL Server的DSN连接时,可以将其链接到视图。在这种情况下,Access会将其识别为可编辑表。
但是如果我使用无DSN的连接到使用vba代码的视图(https://support.microsoft.com/en-us/kb/892490中的方法1,它被链接为不可更新的表。
我不知道为什么会有区别,但是如何连接到SQL Server中的视图(作为表或Access中的查询)并使其可更新?
编辑:当我使用SQL Server中的表而不是视图进行无DSN的连接时,它在Access中是可更新的。我猜想我的问题与没有唯一ID的视图有关,但是我很困惑为什么DSN连接可以更新而没有DSN的视图不能更新。
不是因为它没有DSN,而是因为您是通过VBA创建的。如果您通过Access GUI链接视图,它将要求您提供主键。
但是通过VBA,它不知道主键,因此链接的视图不可更新。对于表,Access会通过ODBC自动获取主键,因此该表可以工作。
解决方案:通过VBA链接视图后,设置主键:
S = "CREATE INDEX PrimaryKey ON MyViewName (MyPrimaryKeyField) WITH PRIMARY"
DB.Execute S
如果您有很多视图,并定期将它们重新链接(例如,从开发人员转到生产数据库),则硬编码其名称和PK变得不切实际。我编写了一个函数,用于从链接的视图中检索所有主键索引,并在链接后重新创建它们。如果您愿意,我可以进行挖掘。
编辑:这就是我的工作:
' This function returns the full DSN-less connect string
Private Function ODBC_String() As String
' In the real world there are several constants and variable in there
ODBC_String = "ODBC;DRIVER={SQL Server};SERVER=aaa;DATABASE=bbb;UID=ccc;PWD=ccc;LANGUAGE=us_english;TRUSTED_CONNECTION=No"
End Function
要链接表或视图第一次,我用这个(strTable是表/视图的名称):
DoCmd.TransferDatabase acLink, "ODBC", ODBC_String(), acTable, strTable, strTable, False, True
对于表,主键(PK)是自动确定的。对于视图,我将获得“访问”对话框窗口以指定PK,就像我手动链接视图一样。PK信息存储在TableDef对象中的链接视图中,因此,我不必在任何地方对其进行硬编码。
要存储所有链接视图的PK信息,我有此表(为简单起见,它是Access前端中的本地表:]
t_LinkedViewPK
ViewName Text(100)
IndexFields Text(255)
以及此功能。所有视图(和only视图)都称为“ v_ *”,因此我可以按名称列出它们。我实际上不确定是否可以从TableDef对象确定它是否指向表或视图。
Private Sub StoreViewPKs()
Dim TD As TableDef
Dim idx As index
Dim FD As Field
Dim RS As Recordset
Dim S As String
' DB is a global Database object, set to CurrentDB
DB.Execute "Delete * From t_LinkedViewPK"
Set RS = DB.OpenRecordset("t_LinkedViewPK")
For Each TD In DB.TableDefs
If TD.Name Like "v_*" Then
' Views must have exactly one index. If not: panic!
If TD.Indexes.Count <> 1 Then
MsgBox "View " & TD.Name & " has " & TD.Indexes.Count & " Indizes.", vbCritical
Stop
End If
Set idx = TD.Indexes(0)
' Build field list (the index may contain multiple fields)
S = ""
For Each FD In idx.Fields
If S <> "" Then S = S & ", "
S = S & FD.Name
Next FD
RS.AddNew
RS!ViewName = TD.Name
RS!IndexFields = S
RS.Update
End If
Next TD
RS.Close
End Sub
当我更改表或视图结构或更改源数据库时(这是通过更改ODBC_String()
的输出来完成的,我调用此函数:
Public Function Sql_RefreshTables()
Dim TD As TableDef
Dim S As String
Dim IdxFlds As String
DB.TableDefs.Refresh
' save current Indizes for Views (recreated after .RefreshLink)
Call StoreViewPKs
For Each TD In DB.TableDefs
If Len(TD.Connect) > 0 Then
If Left(TD.Connect, 5) = "ODBC;" Then
Debug.Print "Updating " & TD.Name
TD.Connect = ODBC_String()
TD.RefreshLink
' View?
If TD.Name Like "v_*" Then
IdxFlds = Nz(DLookup("IndexFields", "t_LinkedViewPK", "ViewName = '" & TD.Name & "'"))
If IdxFlds = "" Then Stop
' Create PK
S = "CREATE INDEX PrimaryKey ON " & TD.Name & " (" & IdxFlds & ") WITH PRIMARY"
DB.Execute S
End If
End If
End If
Next TD
DB.TableDefs.Refresh
End Function
注意:代替表t_LinkedViewPK
,可以使用字典对象。但是在进行开发时,将其作为实际表非常有用。
安德烈的答案是正确的。我正在使用一些更复杂的代码来创建索引-这只是表面上的变化:
Public Function RefreshIndexes()
On Error Resume Next
CurrentDb.Execute "CREATE UNIQUE INDEX [__uniqueindex] ON MyViewName (MyPrimaryKeyField) WITH PRIMARY;", dbFailOnError
CurrentDb.Execute "CREATE UNIQUE INDEX [__uniqueindex] ON MyViewName2 (MyPrimaryKeyField2) WITH PRIMARY;", dbFailOnError
End Function
我在用户打开Access且仅用于没有索引的视图时调用此命令。