DBSheets






4.90/5 (11投票s)
2007 年 3 月 13 日
22分钟阅读

59515

935
基于 Excel 的解决方案,

引言
重要提示:DBSheets
已与DBFuncs
合并,并已移至此处!
DBSheet
是一个基于Excel的解决方案,用于修改数据库数据,即在给定表的指定(子)字段集中插入、更新和删除行。
修改是在所谓的数据库工作表(DB-Sheets)中完成的,这些工作表通过使用指定的查询进行填充。DB-Sheet包含“正常”的表数据(直接值)和用于更新外键列的间接查找值。这些外键列的允许ID和可见查找值存储在一个隐藏的工作表中。
DB-Sheet中的工作通过上下文菜单(鼠标右键)和上下文菜单项的快捷键完成
- 添加新记录的方法有两种:一是在显示数据下方的空行中添加数据;二是在单元格上下文菜单中选择
插入行(Ctrl-I)
,然后将数据添加到插入的空行中 - 通过简单地更改单元格来更新现有记录
- 通过在单元格上下文菜单中选择
删除行(Ctrl-D)
来删除现有记录 - 当Excel工作簿保存(Ctrl-S)时,当前的DB-Sheet也会被保存
- 在单元格上下文菜单中选择
刷新数据(Ctrl-R)
将刷新数据(撤销目前所做的任何更改)
还有一个支持工具“DBSheet creation”(上下文菜单“编辑DBsheet
定义”,仅在左上角单元格中可用),用于构建和编辑DBSheets
。
在以下章节中,将介绍DBSheet
的主要功能,然后描述支持工具“DBSheet
创建”。
背景
理解本文档和使用DBSheets
的前提条件是:- 对SQL和数据库设计有基本的熟练度(关于此主题的好书有《实用SQL手册:使用结构化查询语言(第三版)》及其续作《实用SQL手册:使用SQL变体(第四版)》,可在线免费获取的课程有
- SQL 教程(德语)
- SQL 教程(英语)
- SQL 教程 - 学习 SQL(英语)
- SQL 课程(英语)
- SQL 课程 2(英语)
- 作为插件管理员,需具备Excel-VBA的基本熟练度。
使用 DBSheet
我使用随附的测试工作簿Pubs.xls作为示例来指导DBSheet
的各种可能性。
此工作簿使用 Microsoft SQL 的 pubs
数据库(如果您的数据库服务器尚未安装,可从 Microsoft 下载/安装,或在 Microsoft.com 上搜索“pubs 下载”)。对于 MySQL、Sybase、Oracle 和 DB2,我自行传输了 pubs
数据库,您可以在此处下载。
DBSheets 的特性
DBSheets
由一个包含表数据、标题和DBSheet
定义(左上角单元格中的注释)的工作表组成,一个(隐藏的)工作表包含(外表)查找,最后还有一个隐藏的工作表包含上次刷新时的表数据,用于识别更改的数据。
标题行和主键列(位于最左侧并显示为灰色)不可修改,但新行除外,因为它们尚未有主键。
必填列(不允许为空(null
)值)显示为灰色背景,数值、日期值和查找列受Excel单元格验证的限制。
编辑数据
数据通过简单地编辑单元格中的现有数据来更改。
这将标记要更改的行(在工作表末尾(Excel XP为第256列)的修改行末尾放置一个“c”)。
插入操作是通过在空行中输入数据来完成的,您可以让数据库引入一个新的主键(仅适用于单一主键),方法是将其列留空;或者您可以自己设置主键。这会标记要插入的行(在工作表末尾的插入行末尾放置一个“i”)。
可以在现有数据区域内通过使用上下文菜单“插入行”或在应插入数据的行中按Ctrl+I来创建空行。如果选中多行,则插入相同数量的空行。
删除操作是通过使用上下文菜单“删除行”或在要删除的行中按Ctrl+D来完成的。这会标记要删除的行(在工作表末尾的已删除行末尾放置一个“d”)。
要使编辑永久生效,请保存工作簿(保存按钮或 Ctrl-S)。在警告消息后,当前的DBSheet
将存储到数据库中,数据库(或触发器)在检查有效性时会产生警告/错误。
编辑时,必须遵守底层数据定义的约束,例如,外键必须使用作为可能外键值提供的受限列表输入,数值数据单元格不允许字符数据等...
有四个上下文菜单(外键查找列中有五个)
- 刷新
DBSheet
(快捷键 Ctrl+R),用于从数据库中读取当前的DBSheet
,从而丢弃任何更改 - 删除行(快捷键 Ctrl+D),用于删除当前选中单元格的行
- 插入行(快捷键 Ctrl+I),用于在当前选中单元格上方插入行。插入的行数与选中区域中的行数相同。
- 切换详细视图(快捷键 Ctrl+T),用于在列表视图和详细视图之间切换(多个窗口,每个窗口显示列的不同部分,请参见下文)
- 显示/隐藏外部记录 (Ctrl-B),用于在下方第二个窗口中显示关联的外部表,并已选中相应的记录。
您可以在出现的确认消息框中勾选“始终确定并不要为该DB工作表显示!”,以指定每个工作表是否不显示有关刷新、删除、插入和存储数据的任何警告。

通过设置Public Const enforceRefresh = True
,可以在DBsheets
之间切换(进入/激活DBsheet
)时强制刷新数据,从而在按取消时显示警告消息

当选择“切换详细视图”(快捷键Ctrl+T)时,列表视图会切换到“详细视图”,其中一行所有数据都会尝试在一个窗口中显示

这是通过在同一工作表上打开多个窗口(视图)实现的,每个视图都向右偏移一页。您可以使用Ctrl+Tab(向前)和Ctrl+Shift+Tab(向后)在这些窗口之间切换。如果DBSheet
是唯一(可见)的工作簿,那么它将真正循环显示所有详细窗口,否则其他工作簿将从后面弹出。最后选中的行显示为浅绿色,这简化了在详细窗口中的导航(当列数 > 100 或列宽非常宽时,单个详细窗口会相当窄)。
您可以再次选择“切换详细视图”以切换回列表视图。
如果定义了计算列,则该列中最顶部的公式在刷新DBSheet
时始终会保留,并在每次刷新后自动向下填充。保存时,只有那些底层(真实)数据已编辑的行才会被存储,计算更改的数据不会被存储。可以通过设置DBsheet
行为参数enforceSaveAllWhenCalcColumns
来覆盖此行为,该参数将所有行都保存为已编辑,无论它们是否真正被编辑过。请注意,这种行为会使大型表的保存非常慢……
标题行包含所有定义注释,其中A1单元格中的注释以XML格式包含以下信息
- 用于获取要编辑的主表数据的查询
- 连接ID,引用所有相应
DBSheets
的中央定义,主要包括数据库和应从中获取数据的服务器 - 外键查找。这些包括一个查找名称,即列的名称,以及一个
select
语句或一个值列表。select
语句必须恰好返回两列,首先返回查找值,然后返回要查找的ID。(主表列值集应包含在其中,以便每个列值都可以被查找)。
对于参照外键列的查询返回集中,自然应该严格避免重复,因为它们会导致歧义,从而在生成DBSheet
时导致错误 - 连接超时、主列计数和计算列的起始位置(0表示无计算列)
- 用于指定特殊
DBSheet
行为的其他几项信息- 冻结标题行:
<freezeHeader>
- 冻结主列:
<freezePrimCols>
- 当需要保存计算列时强制保存所有行:
<enforceSaveAllWhenCalcColumns>
- 禁止确认消息框:
<dontShowsave>,
<dontShowinsert>
<dontShowrefresh>
等。 - 用于参数化
DBSheets
的范围名称
- 冻结标题行:
- 工作簿中存储着一个工作簿属性信息(可在
Const globalPropertyStore = "Hyperlink base"
中自定义),用于在存储数据时禁止保存底层工作簿:<dontSaveWB>
这些信息中可由最终用户定义的部分(而非DBSheet
定义本身)可以在DBSheet
参数对话框中编辑,通过右键单击左上角单元格并选择编辑 DBSheet 参数
来调用

此对话框允许您编辑窗口窗格冻结预设,强制保存所有行(用于计算列)。此外,在编辑/刷新/保存DBsheets
期间所做的所有“不显示”设置都可以再次重置。可以启用自动格式化单元格向下复制DBsheet
第一行中设置的格式。
在左侧部分,您可以定义用于参数化DBSheet
查询的参数范围名称。命名范围的内容用于替换参数化DBSheet
查询中使用的引号,无论是带引号(字符串)还是不带引号(数值数据或准备好的部分,如in子句等)。参数按照出现的顺序进行替换。
工作簿中存储着一个工作簿属性信息(可在Const globalPropertyStore = "Hyperlink base"
中自定义),用于在存储数据时禁止保存底层工作簿:<dontSaveWB>
当多个用户编辑同一行时的冲突解决
两个人或更多人可以同时编辑一个表,最后保存数据的人必须解决任何编辑冲突(乐观行锁定)。如果发生冲突,相应行的第一个单元格会被标记为青绿色,并且对于在打开DBSheet
(或刷新)到其他人保存之间被编辑的每个单元格,都会给出以下消息:“要存储的数据(<字段>
)已被其他人编辑过(<值>
),现在的值是(<已更改的值>
)。您想保留这些编辑吗(‘否’将用您的更改覆盖它们)?”,您可以据此回答。
删除操作也同样适用,只要在即将删除的记录中检测到第一个更改的字段,就会显示:“要删除的记录已被其他人编辑(<字段>:<更改值>
)。您想保留这些编辑吗(‘否’将彻底删除该记录)?”
查找外表记录
由于查找信息有时可能不足以识别记录,并且用户可能希望在查看主表数据时更改外键记录,因此还有另一种查找外键记录的方法:在外键查找列中,使用上下文菜单“显示/隐藏外键记录”(Ctrl-B)以显示外键表,并在主表窗格下方突出显示关联的外键记录。

主记录置于原始窗口最上方,外键记录被选中并显示在已激活的外键表窗格中。在主表窗格中激活上下文菜单时,再次激活“显示/隐藏外键记录”(或按下Ctrl-B)将隐藏外键表窗格,而另一个关联的外键记录则在外键表窗格中突出显示。两个窗格中的数据都可以编辑,但在窗格之间切换时会进行刷新!
支持工具“DBSheet Creation”
以下是支持工具“DBsheet Creation”的第一个标签页。通常,您首先需要为您的DBSheet
定义或选择连接。对于新连接,您需要输入所需的连接字符串,其中包含要编辑的表所在的数据库。连接超时也可以选择。
在测试环境中运行DBSheet
的关键是接下来的两个字段,它们主要允许通过添加“Test.”来更改数据库名称或服务器名称。
接下来的两个条目用于Oracle数据库,其中工具需要切换到方案,因此需要密码。在这两个数据库特定字段之后,可以输入用于从数据库中检索所有数据库/方案的命令(对于SQL Server是sp_helpdb
,对于Oracle是select username from sys.all_users
。如果此命令的结果有多于一列(如在SQL Server中),则必须提供可以从中检索数据库的字段名称)。
之后,必须给出将数据库/架构名称与表分隔开的字符串(例如“.dbo.”或“..”或仅仅是“.”)。最后,允许编辑DBSheet
连接的Windows用户以逗号分隔列表的形式给出。
如果所有信息都填写正确,您可以存储连接定义并测试连接。如果成功连接到数据库,则“列”和“创建”这两个选项卡将可用,您可以继续在“列”选项卡上选择一个表。“从文件加载DBSheet
定义”这里只是一个快捷方式,用于直接加载已存储的定义,而无需定义/测试连接!

接下来,选择主表。应在下拉菜单“表”中创建DBsheet
,然后将表的可用字段填充到下拉菜单“列”中。一旦选择了列,连接选项卡将变为不可用,只有从列定义中清除所有列才能再次更改连接。
之后,您可以通过在下拉菜单“列”中选择它们,然后点击“添加到DBSheet
”(或按下Alt-A)将它们添加到DBSheet
列列表中,开始填充要编辑的列。添加所有可用列的快速方法是点击“添加所有字段”(或按下Alt-F)
如果列要求填充(非空),则前面会加上一个星号(也会显示在下面的列列表中,但在生成/自定义查询和查找限制时会移除星号)。第一列会自动设置为主键,任何后续应该作为主键的列可以通过勾选“是主键?”来标记。主列在DBSheet
中必须始终排在最前面,因此DBsheet
创建会阻止将“非主键”列之后的主键列。
如果所讨论的列应为外表查找,请勾选“是外键列?”复选框以启用外表下拉菜单

第一个下拉菜单“外表”允许您选择包含外键列查找信息的外表。选择外表后,可以在下拉菜单“外表键”中选择外表的键。此键用于将主表与外表连接,如果它是外连接(允许外表中缺失条目),请勾选“是外连接?”。
要完成外表查找,请选择作为外键有意义描述的查找列(通常是某个“名称”、“代码”或“描述”字段)。
通过勾选“是否为计算列?”,可以指定计算列(不从数据库读取但仅存储到数据库)。计算列在DBSheet
中必须始终位于最后,因此DBSheet
创建会阻止将“非计算”列之前的任何计算列。
您可以通过选择一行并更改下拉菜单中的值来编辑已存储在DBSheet
-Column列表中的列。更改将在离开所选行(取消选择)后反映在DBSheet
-Column列表中。编辑完成后,点击“中止列编辑”按钮再次开始添加。
您可以通过点击向上/向下箭头按钮来更改列的顺序。
您可以通过在要复制的列上按 Ctrl-C,然后在应粘贴定义的列上按 Ctrl-V,在列之间复制/粘贴定义。除列名之外的所有内容都将粘贴在那里。
通过点击“从DBSheet
中移除”可以移除列,通过点击“清除所有字段”可以清除整个DBSheet
列。
如果您想撤消所有更改,只需退出表单,然后右键单击选择“编辑DBSheet
定义”再次启动它。这将从工作表中获取当前存储的值并再次显示以供编辑。
在处理外键列查找或其他限制时,您可以通过编辑DBSheet
列列表下方的限制字段来直接编辑查找的定义

您可以在其中放入任何查询,它只需首先返回查找值,然后返回要查找的ID。此查询的返回集中应严格避免重复,因为它们会导致歧义,并在生成DBSheet
时产生错误消息。
限制字段的自定义必须遵守一些规则才能有效使用(从而不强制DBSheet
创建者进行不必要的重复工作):首先,任何对外部表本身的引用都必须使用模板占位符!T!,该占位符随后被实际的表枚举器(T2..TDBSheet
将无法将主表中的外部列与查找ID关联,从而显示以下错误消息:

查找查询和主查询之间的连接方式如下
- 查找查询
select
语句的第一个列部分被复制到主表中的相应列(因此有上述限制) - 外键查找表和查找查询所需的所有其他附加表都以与查找中定义的方式相同的方式(内/外连接)连接到主查询中,
WHERE
子句会以AND
添加到这些连接中。
您可以通过点击限制字段旁边的“测试查找查询”来测试外部查找查询。这将打开一个Excel工作表,其中插入了查找查询的结果(最多1000个,一个接一个)。此测试工作表可以通过简单地关闭它来再次关闭,或者通过再次点击相同的按钮(现在其标题已更改为“删除测试工作表”)来更快地关闭。
下图应阐明查找查询和主查询之间的连接

你甚至可以有一个查找列,而无需定义任何外表关系。这只需通过使用“是否为外键列?”复选框打开限制字段,并在该字段中定义查找即可。这里同样适用上述内容。请注意,第一列始终是查找值,第二列始终是ID(实际存储到表中的值),因此在“无关系”查找中,**两列**都包含实际值。这意味着“无关系”限制通常看起来像“select lookupCol, lookupCol from someTable…”

另请记住,查找始终检查唯一性,因此如果预期存在重复行,则附加的distinct
子句将避免由此产生的错误消息:select distinct lookupCol, lookupCol from someTable…
(此方法不适用于外键查找,因为应始终找出确切/正确的ID。相反,请尝试找到一种方法,使查找值反映其唯一性,例如通过连接/合并更多标识符,如select lookupCol+additionalLookup, lookupID…
)。
甚至不需要查找查询的查找列也是可能的,只需在限制中的值之后用“||”分隔列出可能的值,例如:是||否||可能。这里不需要ID,只需要值就足够了

DBSheet
的创建分为三个步骤
- 首先,所有(或仅单个选定的)限制都应使用外键表/查找字段信息(重新)生成
- 然后必须生成(并且如果需要,可以进一步自定义)用于检索要编辑的字段的主查询
您可以通过点击查询字段旁边的“测试”来测试查询。这会将查询结果放入一个新的临时工作表(之后可以关闭)。 - 最后,可以创建
DBSheet
,将DBSheet
创建工具收集到的信息传输到当前打开的Excel工作表(如果工作簿/工作表处于活动状态,则将DBSheet
定义添加到该工作表;否则将创建新的工作簿/工作表)。
如果勾选了这些属性,您还可以初步设置DBSheet
的特定属性(另请参阅“DBSheets
的特性”)
- 冻结标题行?:设置
<freezeHeader>
- 冻结主列?:设置
<freezePrimCols>
- 计算列时强制保存所有行?:设置
<enforceSaveAllWhenCalcColumns>

DBSheet
定义可以使用左下角的加载/保存/另存为...按钮进行保存/恢复。这会将当前包含在DBSheet
列中的信息存储在DBSheet
定义文件(扩展名为:XML)中。
您可以通过点击查询定义上方的“测试DBSheet查询”来随时测试主表查询。这将打开一个Excel工作表,其中插入了主表查询的结果(最多1000个,一个接一个)。此测试工作表可以通过简单地关闭它来再次关闭,或者通过再次点击相同的按钮(现在其标题已更改为“删除测试工作表”)来更快地关闭。
创建DBSheet
时,所有定义(标题/查找、查询等)和表的初始值都将被插入。
此外,一个用于初始化所创建的DBSheet
工作簿的特殊Workbook.open
过程被放入Windows剪贴板
Private Sub Workbook_Open()
env = ""
If InStr(1, ThisWorkbook.Name, "Test") > 0 Or _
InStr(1, ThisWorkbook.Path, "Test") > 0 Then env = "Test"
On Error Resume Next
Application.Run "'Your\Path\To\DBSheet\" & env &_
"\DBSheet.xla'!initDBSheet", ThisWorkbook.Name, ThisWorkbook.Path
If Err <> 0 Then _
Application.Run "'DBSheet.xla'!initDBSheet", ThisWorkbook.Name, _
ThisWorkbook.Path
End Sub
然后必须将其粘贴到包含该工作簿的相应DBSheet
的工作簿模块中

安装
安装方法是将Excel Addin DBSheet.xla复制到您选择的任何文件夹中。
在该文件夹中,存放着全局连接定义文件DBConns.xml。使用文本编辑器编辑您的连接(请注意不要在叶节点内引入换行符,这会干扰DBSheet
创建工具的读取)。
<DBConnections>
<c>
<id>TESTDB</id>
<default/>
<connString>
provider=SQLOLEDB;server=MULTIMEDIAPC;Trusted_Connection=Yes;database=pubs
</connString>
<timeout>15</timeout>
<dbChange>database=</dbChange>
<srvChange>server=</srvChange>
<dbneedPwd></dbneedPwd>
<dbGetAll>sp_helpdb</dbGetAll>
<dbGetAllFieldName>name</dbGetAllFieldName>
<ownerQualifier>.dbo.</ownerQualifier>
<allowedUsersEdit>roli</allowedUsersEdit>
</c>
<c>
<id>ORAPUBS</id>
<connString>
Provider=OraOLEDB.Oracle;Data Source=XE;User ID=pubs;Password=pubs12
</connString>
<timeout>15</timeout>
<dbChange>User ID=</dbChange><DBisUserscheme/>
<dbneedPwd>Password=</dbneedPwd>
<dbGetAll>select username from sys.all_users</dbGetAll>
<dbGetAllFieldName></dbGetAllFieldName>
<ownerQualifier>.</ownerQualifier>
<allowedUsersEdit>roli</allowedUsersEdit>
</c>
</DBConnections>
您可能还想在模块DBSheetMain
中自定义以下几个全局变量
#Const DEBUGME = True
:对于调试消息,可以将编译器指令设置为FALSE
以忽略LogDebug
语句(从而加快生产速度……)Const Loglevel = 3
: 写入LogFilePath
的最大日志级别(0 = ERROR, 1 = WARN, 2 = INFO, 3 = DEBUG)。包含上述信息的日志文件将写入Environ("USERPROFILE") & "\DBSheet.xla." & env & ".log"
(其中env
为"Test"
或""
,Environ("USERPROFILE")
表示用户的个人资料文件夹)。Const testHeaderColor = 45
:(橙色)测试环境的标题颜色Const prodHeaderColor = vbBlack
: 生产环境的标题颜色noErrColor = -4142
: 如果没有发生错误,标签颜色internalErrColor = 45
: 如果内部错误,标签颜色dataErrColor = 3
: 如果数据错误,标签颜色TrueFalseSelection = "WAHR,FALSCH
": 这是Excel中布尔(位)值选择的语言相关设置。例如,对于英语,这将是Const TrueFalseSelection = "TRUE,FALSE"
enforceRefresh = True
:激活DBSheets
时强制刷新,这样用户就不能取消刷新确认问题DBConnFileName = "DBConns.xml"
: 全局连接定义文件的文件名specialNonNullableChar = "*"
:这在不允许为null
的列前添加globalPropertyStore = "Hyperlink base"
:工作簿内置属性,用于存储工作簿全局用户设置(目前仅<saveWB>
)tblPlaceHolder = "!T!"
:用于在查找中替换为该行的外键表的特殊占位符(T2、T3...)maxRowsToFetch = 1000
:测试查询时,一次不要获取超过此数量的数据(会要求更多...)
然后再次启动DBSheet.xla并创建您的第一个DBSheet
!
如果您想从不同于DBSheet.xla路径的位置运行Pubs.xls工作簿,请将(手动编辑的)起始路径"'" & ThisWorkbook.Path & ...
更改为更适合的值。
测试
DBSheets
仅在Excel XP (2002) 和 Microsoft SQL Server 2000 以及 Oracle 10g 上测试过。
有一个独立的测试环境设施,这意味着如果DBSheet
工作簿的路径中某处包含“Test”,它将 a) 调用位于主文件夹下的Test文件夹中的测试插件,b) 以模块utils
中setEnvironmentConnstring
定义的方式修改连接字符串,该方式目前仅将\Test附加到连接中由srvChange
参数指示的数据库服务器实例。这当然高度依赖于您使用的数据库引擎,因此您总是需要稍微更改setEnvironmentConnstring
过程。
已知问题/限制
由于 Excel 将数据验证限制为 32K,外键值的查找值列表限制为 32767 个条目。但这并不意味着您不能在那里输入值,只是它不会显示在下拉列表中,因此您只能靠自己猜测正确——精确——的外键值查找!
在DBsheet
工作簿中,查找列名(包含指向其他表的外键值的字段)应是明确的。原因在于,列名用于包含(隐藏)查找表中有效列表的范围名称。
由于 ansi-padding 导致固定长度字符字段(对于 Microsoft SQL Server)填充,当直接更新固定长度字符字段时,您有时会错误地被询问另一个用户是否更改了该字段。这是因为 Microsoft SQL 在通过 ADO 中的 select cmd 查询时返回填充了空格的字段,因此与上次刷新数据工作表中的未填充值进行比较(有趣的是,如果您使用表作为 cmd 查询,字段会返回未填充的值)。
目前,隐藏/显示外键查找仅适用于单主列外键表。任何具有多个主列的表都会导致错误的外键记录选择。
我在 Microsoft SQL Server 中使用命名实例和 Microsoft OLEDB 驱动程序时遇到一个问题,在尝试检索架构信息时,OLEDB 驱动程序在没有明显原因的情况下关闭了连接,从而在尝试检索外表可能的数据库时产生了一个错误。
关注点
DBSheets
没什么特别之处(除了其理念),它主要是纯粹的 Excel VBA,只有一个例外:详细视图的切换(在模块DetailListView
中)使用了 WinAPI 调用SetWindowLong (Hwnd As Long, nIndex As Long, dwNewLong As Long)
,它用于隐藏/显示窗口标题栏。
此功能封装在函数TitleBar(theWin As Window, bShow As Boolean)
后面,该函数根据bShow
隐藏/显示theWin
的标题栏。
历史
- 2007年2月28日 首次发布至Codeproject
- 15/03/2007
- 错误修正
- 保存时来自数据库的错误信息更清晰
- 加载数据表定义现在使用正确的连接
- 增强功能
- 增加了参数化查询(在
where
子句中带有问号),在左上角单元格菜单中增加了“编辑DBSheet
参数”上下文菜单,允许创建范围名称,其值用于填充参数化查询。还允许最终用户编辑其“不显示”预设和其他有用设置(自动格式化、冻结标题/主列等)。 - 2007年11月2日:
DBAddin
1.0版现已在sourceforge上线