如何以编程方式创建链接 Table 到 SQL 服务器视图的 ODBC 并使其可编辑?
How to programmatically create an ODBC Linked Table to a SQL Server View and have it be editable?
当我使用向导创建到 SQL 服务器的 DSN 连接时,我可以 link 将其显示到视图中。在这种情况下,Access 将其识别为 editable table.
但是如果我使用 vba 代码(来自 https://support.microsoft.com/en-us/kb/892490 的方法 1)使用无 DSN 连接到视图,它会被 link 编辑为 table那不是 updatable.
我不知道为什么会有差异,但我如何才能连接到 SQL 服务器中的视图(作为 table 或 Access 中的查询)并拥有更新table?
编辑:当我使用 SQL 服务器中的 table 而不是视图建立无 DSN 连接时,它是 Access 中的 updatable。我猜想我的问题与没有唯一 ID 的视图有关,但我很困惑为什么 DSN 连接可以更新 table 而 DSN-less 不能。
这不是因为它没有 DSN,而是因为您是通过 VBA 创建的。如果您 link 通过 Access GUI 查看视图,它会要求您提供主键。
但是通过 VBA,它不知道主键,所以 linked 视图不可更新。使用 table,Access 通过 ODBC 自动获取主键,因此 table 有效。
解决方法:在通过VBA访问视图后link设置主键:
S = "CREATE INDEX PrimaryKey ON MyViewName (MyPrimaryKeyField) WITH PRIMARY"
DB.Execute S
如果您有很多视图,并定期重新link它们(例如从开发数据库到生产数据库),硬编码它们的名称和 PK 变得不切实际。我编写了一个函数来从 linked 视图中检索所有主键索引,并在 linking 之后重新创建它们。
要的话我可以挖出来
编辑:
这就是我所做的:
' 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
给link一个table或者查看第一次,我用这个(strTable是table/view的名字):
DoCmd.TransferDatabase acLink, "ODBC", ODBC_String(), acTable, strTable, strTable, False, True
table秒后,自动确定主键(PK)。对于视图,我使用访问对话框 window 来指定 PK,就像我手动 link 视图一样。
PK 信息存储在 linked 视图的 TableDef 对象中,因此我永远不必在任何地方对其进行硬编码。
为了存储所有 linked 视图的 PK 信息,我有这个 table(为简单起见,它是 Access 前端中的本地 table):
t_LinkedViewPK
ViewName Text(100)
IndexFields Text(255)
还有这个功能。所有视图(以及 仅 视图)都称为 "v_*",因此我可以按名称列出它们。
我实际上不确定您是否可以从 TableDef 对象确定它是指向 table 还是指向视图。
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
当我更改 table 或查看结构,或更改源数据库(这是通过更改 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
注:
可以使用字典对象代替 table t_LinkedViewPK
。但是在开发这个的时候,把它作为一个实际的 table.
是非常有用的
安德烈的回答是正确的。我正在使用稍微复杂一点的代码来创建索引——这只是表面上的改变:
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 时调用此命令,并且仅针对没有索引的视图。
当我使用向导创建到 SQL 服务器的 DSN 连接时,我可以 link 将其显示到视图中。在这种情况下,Access 将其识别为 editable table.
但是如果我使用 vba 代码(来自 https://support.microsoft.com/en-us/kb/892490 的方法 1)使用无 DSN 连接到视图,它会被 link 编辑为 table那不是 updatable.
我不知道为什么会有差异,但我如何才能连接到 SQL 服务器中的视图(作为 table 或 Access 中的查询)并拥有更新table?
编辑:当我使用 SQL 服务器中的 table 而不是视图建立无 DSN 连接时,它是 Access 中的 updatable。我猜想我的问题与没有唯一 ID 的视图有关,但我很困惑为什么 DSN 连接可以更新 table 而 DSN-less 不能。
这不是因为它没有 DSN,而是因为您是通过 VBA 创建的。如果您 link 通过 Access GUI 查看视图,它会要求您提供主键。
但是通过 VBA,它不知道主键,所以 linked 视图不可更新。使用 table,Access 通过 ODBC 自动获取主键,因此 table 有效。
解决方法:在通过VBA访问视图后link设置主键:
S = "CREATE INDEX PrimaryKey ON MyViewName (MyPrimaryKeyField) WITH PRIMARY"
DB.Execute S
如果您有很多视图,并定期重新link它们(例如从开发数据库到生产数据库),硬编码它们的名称和 PK 变得不切实际。我编写了一个函数来从 linked 视图中检索所有主键索引,并在 linking 之后重新创建它们。
要的话我可以挖出来
编辑:
这就是我所做的:
' 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
给link一个table或者查看第一次,我用这个(strTable是table/view的名字):
DoCmd.TransferDatabase acLink, "ODBC", ODBC_String(), acTable, strTable, strTable, False, True
table秒后,自动确定主键(PK)。对于视图,我使用访问对话框 window 来指定 PK,就像我手动 link 视图一样。
PK 信息存储在 linked 视图的 TableDef 对象中,因此我永远不必在任何地方对其进行硬编码。
为了存储所有 linked 视图的 PK 信息,我有这个 table(为简单起见,它是 Access 前端中的本地 table):
t_LinkedViewPK
ViewName Text(100)
IndexFields Text(255)
还有这个功能。所有视图(以及 仅 视图)都称为 "v_*",因此我可以按名称列出它们。
我实际上不确定您是否可以从 TableDef 对象确定它是指向 table 还是指向视图。
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
当我更改 table 或查看结构,或更改源数据库(这是通过更改 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
注:
可以使用字典对象代替 table t_LinkedViewPK
。但是在开发这个的时候,把它作为一个实际的 table.
安德烈的回答是正确的。我正在使用稍微复杂一点的代码来创建索引——这只是表面上的改变:
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 时调用此命令,并且仅针对没有索引的视图。