在 VBA 中列出服务器的数据库和表
List databases and tables of a server in VBA
我正在尝试列出服务器的数据库和表。
我试过使用 OpenSchema
like in this example 但它 returns 为空:
Set Recordset1 = Connection.OpenSchema(Schema:=ADODB.SchemaEnum.adSchemaSchemata)
MsgBox Prompt:=VBA.IsNull(Expression:=Recordset1.Fields.Item(Index:=0))
我在下面有一些适用于 MySQL 服务器的代码,但我有兴趣了解一种更标准且不特定于某个 DBMS 的方法。
' Reference:
' Microsoft ActiveX Data Objects 6.1 Library
Private Sub Macro()
Dim Connection As ADODB.Connection
Dim Recordset1 As ADODB.Recordset
Dim Recordset2 As ADODB.Recordset
Set Connection = New ADODB.Connection
Connection.ConnectionString = "Driver={MySQL ODBC 5.1 Driver};Server=127.0.0.1;Port=3306"
Connection.ConnectionTimeout = 0
Connection.Open UserId:="root", Password:=""
Set Recordset1 = New ADODB.Recordset
Recordset1.ActiveConnection = Connection
Recordset1.CursorLocation = ADODB.CursorLocationEnum.adUseClient
Recordset1.Source = "SHOW DATABASES"
Recordset1.Open
Set Recordset2 = New ADODB.Recordset
Recordset2.ActiveConnection = Connection
Recordset2.CursorLocation = ADODB.CursorLocationEnum.adUseClient
Do While Not Recordset1.EOF
Connection.Execute "USE " & Recordset1.Fields.Item(Index:="Database").Value
Recordset2.Source = "SHOW TABLES"
Recordset2.Open
Do While Not Recordset2.EOF
Debug.Print Recordset1.Fields.Item(Index:=0).Value & " - " & Recordset2.Fields.Item(Index:=0).Value
Recordset2.MoveNext
Loop
Recordset2.Close
Recordset1.MoveNext
Loop
Set Recordset2 = Nothing
Recordset1.Close
Set Recordset1 = Nothing
Connection.Close
Set Connection = Nothing
End Sub
每个 RDBMS 都有自己的 SQL 风格。 SQL服务器支持table-valued函数和常用table表达式(CTE),MySQL没有,但有一个简洁的 group_concat
功能; Oracle 具有不同的语法,Access 有其自身的局限性和特殊性,DB2 也是如此 - 如果您可以编写适用于所有 RDBMS 的 SQL 查询,那您就太幸运了。
使用 程序化 方法,您需要一种方法让您的代码知道要使用哪种风格的 SQL。您可以使用 Enum
:
Public Enum RDBMS
NotSupported
SqlServer
MySql
Oracle
End Enum
然后您将拥有切换逻辑以获得您需要执行的正确 SQL 语法:
Private Function GetTableInfoQuery(ByVal db As RDBMS) As String
Select Case db
Case SqlServer
GetTableInfoQuery = "SELECT * FROM sys.Tables;"
Case MySql
GetTableInfoQuery = "MySQL-specific query;"
Case Oracle
GetTableInfoQuery = "Oracle-specific query;"
Case Else
Err.Raise 5, TypeName(Me), "Specified RDBMS is not supported"
End Select
End Function
这会起作用,但很快就会变得烦人冗长,你现在有多个函数包含不同风格的 SQL 语法(以及更多用于不同的连接字符串),并且维护它最终成为一场噩梦 - 让独自调试。
问题不在于支持多个 RDBMS,问题在于过程方法。 Object-Oriented 编程 (OOP) 带来了更清洁的东西。在任何典型的 C# 或 Java data-access 代码中,RDBMS 使用 接口 从使用代码中抽象出来 - "pattern" 被称为 Repository,它的主要优点是使用代码根本不在乎它使用的是什么 RDBMS。
定义一个接口来公开您需要的功能:
'@Interface "ITableInfoRepository"
Option Explicit
Public Function GetTableInfo() As Object
End Function
通常更多 general-purpose 存储库界面可能如下所示:
Public Function GetAll() As Object
End Function
Public Function GetById(ByVal id As Long) As Object
End Function
Public Sub Delete(ByVal id As Long)
End Sub
Public Sub Save(ByVal entity As Object)
End Sub
在支持 generics 的语言中,您不需要 As Object
并且您可以保留 type-safety - 但由于 VBA 不需要t 支持泛型(例如 VB.NET 中的 List(Of Something)
),Object
就足够了。
所以我的想法是针对 IRepository
接口进行编码:
Public Sub DoSomething(ByVal repository As ITableInfoRepository)
Dim infos As Object
Set infos = repository.GetTableInfo
'...do stuff...
End Sub
请注意 DoSomething
如何只知道 ITableInfoRepository
抽象接口,而不关心它是否实际访问 SQL 服务器、MySQL 或 Oracle。在 code/macro 的入口点附近(或 ),您将创建实际具体类型的实例:
Public Sub Macro1()
Dim repository As ITableInfoRepository
Set repository = New SqlTableInfoRepository
DoSomething repository
'...
End Sub
如果Macro1
突然需要处理MySqlTableInfoRepository
,那么在新的MySqlTableInfoRepository
class模块上实现ITableInfoRepository
接口,并实例化它class 相反:DoSomething
眼睛都不会眨一下。
您使用 Implements
关键字 实现 接口。例如 SqlTableInfoRepository
实现可能如下所示:
Option Explicit
Implements ITableInfoRepository
Private Function ITableInfoRepository_GetTableInfo() As Object
Const sql = "SELECT * FROM sys.Tables"
Dim conn As ADODB.Connection
'...
End Function
您可以将 GetTableInfo
return 设置为 ADODB.Recordset
,但是不同实现之间的列名可能不同,您不希望这样。因此,要么确保所有实现 SELECT
相同的列,要么将查询结果抽象为 entity object 以形式化 "table info" 记录看起来像,return collection 个 entities:
'TableInfoEntity (class)
Option Explicit
Public DatabaseName As String
Public TableName As String 'SQL Server: includes 'dbo' schema
Public ColumnName As String
Public DataType As String
'...
类似地,如果你有一个 SqlOrderHeaderRepository
,你可能有一个 OrderHeaderEntity
class 模块,所以所有 IRepository
处理 的实现order headers 与 order header 的概念相同:
Option Explicit
Public OrderDate As Date
Public CustomerID As Long
Public SalesRepID As Long
Public Description As String
'...
那样 DoSomething
(或使用 IRepository
接口的任何代码)甚至不需要了解 ADODB,并且可以使用 OrderHeaderEntity
、OrderDetailEntity
、CustomerEntity
、SalesRepEntity
以及构成您的 域.
的任何其他 classes
是的,OOP 是更多的工作,更多的模块。但结果是您现在正在查看具有 clearly-defined 职责的模块,一个地方有 SQL 服务器语法,另一个地方有 MySQL 语法,另一个模块有 Oracle 语法——没有任何随处切换逻辑。要扩展它并支持新的 RDBMS,您甚至不需要触及现有代码 - 您只需实现所需的 classes,以便代码可以与新的 RDBMS 一起工作,然后将它们连接起来在入口点:一旦完成,交换 RDBMS 所需要做的就是将要实例化的具体 classes 和 parameter-injecting 更改为入口点的消费代码。
我正在尝试列出服务器的数据库和表。
我试过使用 OpenSchema
like in this example 但它 returns 为空:
Set Recordset1 = Connection.OpenSchema(Schema:=ADODB.SchemaEnum.adSchemaSchemata)
MsgBox Prompt:=VBA.IsNull(Expression:=Recordset1.Fields.Item(Index:=0))
我在下面有一些适用于 MySQL 服务器的代码,但我有兴趣了解一种更标准且不特定于某个 DBMS 的方法。
' Reference:
' Microsoft ActiveX Data Objects 6.1 Library
Private Sub Macro()
Dim Connection As ADODB.Connection
Dim Recordset1 As ADODB.Recordset
Dim Recordset2 As ADODB.Recordset
Set Connection = New ADODB.Connection
Connection.ConnectionString = "Driver={MySQL ODBC 5.1 Driver};Server=127.0.0.1;Port=3306"
Connection.ConnectionTimeout = 0
Connection.Open UserId:="root", Password:=""
Set Recordset1 = New ADODB.Recordset
Recordset1.ActiveConnection = Connection
Recordset1.CursorLocation = ADODB.CursorLocationEnum.adUseClient
Recordset1.Source = "SHOW DATABASES"
Recordset1.Open
Set Recordset2 = New ADODB.Recordset
Recordset2.ActiveConnection = Connection
Recordset2.CursorLocation = ADODB.CursorLocationEnum.adUseClient
Do While Not Recordset1.EOF
Connection.Execute "USE " & Recordset1.Fields.Item(Index:="Database").Value
Recordset2.Source = "SHOW TABLES"
Recordset2.Open
Do While Not Recordset2.EOF
Debug.Print Recordset1.Fields.Item(Index:=0).Value & " - " & Recordset2.Fields.Item(Index:=0).Value
Recordset2.MoveNext
Loop
Recordset2.Close
Recordset1.MoveNext
Loop
Set Recordset2 = Nothing
Recordset1.Close
Set Recordset1 = Nothing
Connection.Close
Set Connection = Nothing
End Sub
每个 RDBMS 都有自己的 SQL 风格。 SQL服务器支持table-valued函数和常用table表达式(CTE),MySQL没有,但有一个简洁的 group_concat
功能; Oracle 具有不同的语法,Access 有其自身的局限性和特殊性,DB2 也是如此 - 如果您可以编写适用于所有 RDBMS 的 SQL 查询,那您就太幸运了。
使用 程序化 方法,您需要一种方法让您的代码知道要使用哪种风格的 SQL。您可以使用 Enum
:
Public Enum RDBMS
NotSupported
SqlServer
MySql
Oracle
End Enum
然后您将拥有切换逻辑以获得您需要执行的正确 SQL 语法:
Private Function GetTableInfoQuery(ByVal db As RDBMS) As String
Select Case db
Case SqlServer
GetTableInfoQuery = "SELECT * FROM sys.Tables;"
Case MySql
GetTableInfoQuery = "MySQL-specific query;"
Case Oracle
GetTableInfoQuery = "Oracle-specific query;"
Case Else
Err.Raise 5, TypeName(Me), "Specified RDBMS is not supported"
End Select
End Function
这会起作用,但很快就会变得烦人冗长,你现在有多个函数包含不同风格的 SQL 语法(以及更多用于不同的连接字符串),并且维护它最终成为一场噩梦 - 让独自调试。
问题不在于支持多个 RDBMS,问题在于过程方法。 Object-Oriented 编程 (OOP) 带来了更清洁的东西。在任何典型的 C# 或 Java data-access 代码中,RDBMS 使用 接口 从使用代码中抽象出来 - "pattern" 被称为 Repository,它的主要优点是使用代码根本不在乎它使用的是什么 RDBMS。
定义一个接口来公开您需要的功能:
'@Interface "ITableInfoRepository"
Option Explicit
Public Function GetTableInfo() As Object
End Function
通常更多 general-purpose 存储库界面可能如下所示:
Public Function GetAll() As Object
End Function
Public Function GetById(ByVal id As Long) As Object
End Function
Public Sub Delete(ByVal id As Long)
End Sub
Public Sub Save(ByVal entity As Object)
End Sub
在支持 generics 的语言中,您不需要 As Object
并且您可以保留 type-safety - 但由于 VBA 不需要t 支持泛型(例如 VB.NET 中的 List(Of Something)
),Object
就足够了。
所以我的想法是针对 IRepository
接口进行编码:
Public Sub DoSomething(ByVal repository As ITableInfoRepository)
Dim infos As Object
Set infos = repository.GetTableInfo
'...do stuff...
End Sub
请注意 DoSomething
如何只知道 ITableInfoRepository
抽象接口,而不关心它是否实际访问 SQL 服务器、MySQL 或 Oracle。在 code/macro 的入口点附近(或 ),您将创建实际具体类型的实例:
Public Sub Macro1()
Dim repository As ITableInfoRepository
Set repository = New SqlTableInfoRepository
DoSomething repository
'...
End Sub
如果Macro1
突然需要处理MySqlTableInfoRepository
,那么在新的MySqlTableInfoRepository
class模块上实现ITableInfoRepository
接口,并实例化它class 相反:DoSomething
眼睛都不会眨一下。
您使用 Implements
关键字 实现 接口。例如 SqlTableInfoRepository
实现可能如下所示:
Option Explicit
Implements ITableInfoRepository
Private Function ITableInfoRepository_GetTableInfo() As Object
Const sql = "SELECT * FROM sys.Tables"
Dim conn As ADODB.Connection
'...
End Function
您可以将 GetTableInfo
return 设置为 ADODB.Recordset
,但是不同实现之间的列名可能不同,您不希望这样。因此,要么确保所有实现 SELECT
相同的列,要么将查询结果抽象为 entity object 以形式化 "table info" 记录看起来像,return collection 个 entities:
'TableInfoEntity (class)
Option Explicit
Public DatabaseName As String
Public TableName As String 'SQL Server: includes 'dbo' schema
Public ColumnName As String
Public DataType As String
'...
类似地,如果你有一个 SqlOrderHeaderRepository
,你可能有一个 OrderHeaderEntity
class 模块,所以所有 IRepository
处理 的实现order headers 与 order header 的概念相同:
Option Explicit
Public OrderDate As Date
Public CustomerID As Long
Public SalesRepID As Long
Public Description As String
'...
那样 DoSomething
(或使用 IRepository
接口的任何代码)甚至不需要了解 ADODB,并且可以使用 OrderHeaderEntity
、OrderDetailEntity
、CustomerEntity
、SalesRepEntity
以及构成您的 域.
是的,OOP 是更多的工作,更多的模块。但结果是您现在正在查看具有 clearly-defined 职责的模块,一个地方有 SQL 服务器语法,另一个地方有 MySQL 语法,另一个模块有 Oracle 语法——没有任何随处切换逻辑。要扩展它并支持新的 RDBMS,您甚至不需要触及现有代码 - 您只需实现所需的 classes,以便代码可以与新的 RDBMS 一起工作,然后将它们连接起来在入口点:一旦完成,交换 RDBMS 所需要做的就是将要实例化的具体 classes 和 parameter-injecting 更改为入口点的消费代码。