SQL简单实用工具






4.68/5 (12投票s)
该项目提供了SQL Server的实用工具,例如执行SQL脚本列表、将数据导出为SQL脚本以及显示记录之间的关系。
引言
该项目提供了三个SQL Server的实用工具
- 执行SQL脚本:用户可以选择一个包含SQL脚本的文件夹,或者一个包含SQL脚本列表的文件。可以指定脚本的执行顺序,并执行SQL脚本。
- 导出为SQL脚本:用户可以指定要导出的表列表。基于表关系(外键),该工具会计算导出顺序(父表先于子表)。然后,通过保留关系,将表的数据导出为SQL脚本。
- 显示引用:用户可以指定数据库中的一条记录。基于表关系(外键),该工具会在一定距离内查找所有相关记录,并使用svg显示图表。
该项目还演示了如何处理MDI应用程序中的工具栏、如何在WebBrowser
控件中显示svg、如何使WebBrowser
控件可缩放、如何通过异步调用数据库来填充DataSet
、如何使用通用表表达式(CTE)编写递归SQL查询、如何编写深度优先图搜索算法以及如何显示图表。
背景
执行SQL脚本
通常需要一个接一个地执行多个SQL脚本。通常的做法是将SQL脚本逐个加载到SQL Server Management Studio中执行。这很繁琐、耗时且容易出错,因为很容易遗漏某个SQL脚本。下面的图像中所示的工具提供了按顺序执行多个SQL脚本的功能。
该窗体提供两个选项卡
- 执行:显示SQL脚本列表和执行进度。
- Connection:
提供参数以指定到SQL Server数据库的连接。
通过按“获取”按钮,SQL数据库列表将加载到组合框中。
工具栏按钮 | ![]() |
- 新建:清空列表。
- 打开:提示用户选择一个包含SQL脚本列表的文件。选择文件后,窗体的列表将填充所选文件中列出的文件名。
- 导入:提示用户选择一个包含SQL脚本的文件夹。选择文件夹后,窗体的列表将按字母顺序填充所选文件夹及其子文件夹中*.sql文件的文件名。也可以将文件拖放到窗体的列表中。
- 保存:窗体的列表可以保存到文件。之后可以通过“打开”按钮打开。
- 运行:开始执行SQL脚本。
- 暂停:暂停执行。按“运行”时,将从暂停的脚本处恢复执行。
- 取消:取消执行。按“运行”时,将从头开始执行。
- 上移:将列表中选定的文件名向上移动一个位置。
- 下移:将列表中选定的文件名向下移动一个位置。
示例
- 创建一个文件夹,例如D:\temp\SQLscripts,并在其中放入一些SQL脚本。
- 按“导入”按钮,然后选择该文件夹。列表将填充.sql文件。
- 使用上移和下移按钮重新排序文件。
- 按“运行”按钮开始执行。
- 如果出现错误消息,这些消息将写在该窗体底部的文本框中。
- 按“保存”按钮,将文件列表保存到例如D:\temp\List1.txt。
导出为SQL脚本
数据库表中的参数化数据经常需要从开发环境复制到UAT和生产数据库。这通常通过编写相应的SQL脚本来完成。该工具将选定数据库表中的数据导出为SQL脚本。难点在于找到导出表的正确顺序,因为表之间可能存在外键。外键代表了一个图。通过遍历图来找到导出表的顺序,因为父表先于子表导出。可以通过一个唯一列来指定与父表的关系。
该窗体提供两个选项卡
- 执行:显示表列表、外键和导出进度。
- 连接:如上所述,提供参数以指定到SQL Server数据库的连接。
工具栏按钮 | ![]() |
- 准备:查找选定表的外键。如果源数据库和目标数据库中的ID不同,可以在外键网格中指定一个唯一列,通过该列可以检索父表ID。如果在仅外键图中找到循环链,则会以灰色背景标记,并且必须删除。在上面的例子中,在表
HumanResources.Employee
上找到了一条循环链,该表通过ManagerID
列引用自身。将其删除,以便可以开始导出。如果没有找到循环链,则计算表的顺序。 - 运行:开始将数据导出为SQL脚本。必须在“SQL脚本”文本框中指定文件名。
- 暂停:暂停执行。按“运行”时,将从暂停的表处恢复执行。
- 取消:取消执行。按“运行”时,将从头开始执行。
示例
- 在“连接”选项卡中,将连接设置为
AdventureWorks
数据库。 - 在“执行”选项卡中,选择
HumanResources
表,然后按“准备”按钮:网格将填充外键。
将显示以下消息框。
循环链是网格中的第一行,背景为灰色。表Employees
具有对自身的引用。 - 该工具不允许循环引用。选择第一行,然后右键单击删除它。这不会从数据库中删除外键。
ManagerID
值将按原样用于SQL脚本。 - 再次按“准备”按钮:网格将填充外键,并且表的顺序将在右侧的列表中显示。
将显示以下消息框。
引用未导出的表的两个外键背景为灰色。 - 在最后一个外键中,将“唯一”列中选定的字段更改为
EmailAddress
,如图所示。这意味着ContactId
字段将通过搜索唯一字段EmailAddress
从Person.Contact
表中检索。 - 将“SQL脚本”文本框设置为有效的文件名。这是将写入脚本的文件。
- 按“运行”按钮。表导出开始。
- 完成后,可以通过双击“SQL脚本”文本框来打开脚本。
显示引用
查看数据库中的记录时,很难看到它与哪些记录相关。该记录可能引用其他记录,或者其他记录可能引用给定的记录。要查找第一种情况的记录,必须检索以给定记录的表为父表的外键。必须从该记录中检索这些外键的父列。要查找第二种情况的记录,必须检索以给定记录为被引用表的表外键。对于这些外键中的每一个,必须检索引用给定记录的相应外键的被引用表中的记录。所有这些相邻记录都与给定记录的距离为1。这些记录可能还与其他记录相关,这些记录与给定记录的距离为2。相同的过程可以运行到指定的距离。该工具以图表的形式显示给定记录 up to a given distance 的所有相关记录。
该窗体提供两个选项卡
- 执行:提供控件以指定表名、主键值和距离,并显示记录及其关系的图表。
- 表名:记录所在的表名。
- 主键值:记录主键值的逗号分隔列表。
- 距离:检索相关记录的最大距离。
- 表过滤器:SQL
WHERE
子句,用于排除某些表。例如,可以在此处排除许多表引用的某些基本表,如货币、国家、用户。
- 连接:如上所述,提供参数以指定到SQL Server数据库的连接。
工具栏按钮 | ![]() |
- 运行:检索数据库的所有主键和外键,但排除“表过滤器”指定的表。对于给定的记录,会构建一个图,其中包含 up to a given distance 的所有相关记录。图表将被写入可执行文件夹中的文件Graph1.dot和Graph1.svg。如果文件小于200KB,则将在窗体的
WebBrowser
控件中显示,否则将在默认Webbrowser
中打开。 - 取消:取消执行。未完成的图将按上述方式写入文件并在上述规定中显示。
示例
- 在“连接”选项卡中,将连接设置为
AdventureWorks
数据库。 - 在“执行”选项卡中,选择
Sales.Customer
表,将“主键值”设置为30040
,将“距离”设置为1
,然后按“运行”按钮:窗体将显示与Sales.Customer
表中CustomerID=30040
的记录直接相关的所有记录。
在每个顶点中,显示四个元素- 级别:一个“L”后面跟着顶点的级别。父节点的级别高于其所有子节点的级别。级别代表创建顺序。较低级别的记录比高级别的记录先创建。因此,高级别记录必须先于低级别记录删除。相同级别的记录可以同时删除。
- 距离:一个“D”后面跟着与起始记录的距离。与起始记录直接相关的所有记录(直接父节点或直接子节点)的距离为1。与距离为1的记录直接相关的所有记录的距离为2,依此类推。
- 表名:顶点所代表的记录所属的表名。
- 主键值:记录主键值的逗号分隔列表。
- 最大距离:起始记录与相关记录之间的最大距离。
- 顶点数:找到的顶点数。
- 边数:找到的边数。
- 文件:生成的svg文件的路径。在同一位置也会创建一个相应的.dot文件。
- 长度:svg文件的长度(以字节为单位)。
- 将“距离”设置为
2
。这将需要更长的时间(通常为4分钟),并产生约10000个顶点。这是因为Sales.Customer
表引用了Sales.Territory
表。一个Sales.Territory
记录被许多Sales.Customer
记录引用,如图所示。 - 将“表过滤器”设置为
WHERE a.name != 'SalesTerritory'
。图将变得小得多,只有19个顶点。 - 将“距离”设置为10,并将“表过滤器”设置为
WHERE a.name NOT IN ('AddressType','ContactType','CountryRegion','CurrencyRate','PhoneNumberType','Product','SalesPerson','SalesStore','SalesTerritory','ShipMethod','SpecialOffer','SpecialOfferProduct','StateProvince')
,以排除所有基本表,然后按“运行”。完成后,状态栏显示Max distance = 9
。因为最大距离设置为10
,这意味着给定的记录直接或间接相关的所有记录都已检索。与给定记录相关的距离大于9的记录(除了“表过滤器”中指定的表)没有。
Using the Code
执行SQL脚本
FormExecScripts.vb实现了SQL脚本的执行。SQL脚本可能包含GO
语句,因此无法使用SqlCommand.ExecuteNonQuery
执行这些脚本。方法ModSql.ExecuteSql
在GO
语句处分割SQL脚本。GO
语句位于行的开头,同一行没有其他语句。然后,使用SqlCommand.ExecuteNonQuery
逐个执行生成的SQL脚本。
导出为SQL脚本
FormExportToScript.vb实现了导出到SQL脚本的功能。导出逻辑在ExportByTable
类中。方法FKsComputeLevel
计算外键的级别(即顺序)。算法在方法的注释中进行了描述。SQL代码由方法DataTableExport
生成。
显示引用
FormShowReferences.vb实现了“显示引用”功能。所有处理都在BackgroundWorker1_DoWork
方法中完成。执行以下语句:
FillSqlStatements
:此方法检索所有表的主键和外键(不包括“表过滤器”中指定的表),并填充DataTables mDataTableIXs
和mDataTableFKs
。mDataTableIXs
包含主键,并具有以下列。TableName
:此索引所属的表名。PKColumn
:属于此索引的列名的逗号分隔列表。
mDataTableFKs
包含外键,并具有以下列。FKName
:外键名称。ParentTable
:外键的被引用表。ChildTable
:外键的父表。ParentColumn
:外键被引用列的逗号分隔列表。ChildColumn
:外键父列的逗号分隔列表。
sys.foreign_keys
表中的列命名有些令人困惑。在此项目中,父表将是外键的被引用表,子表将是外键的父表。
DataTables
的两个查询都使用了CTE(通用表表达式)的递归查询。
对于每个表,此方法构造要执行的SQL语句,用于检索记录的邻居。例如,表Sales.Customer
有3个父表和1个子表。
要获取父记录,必须执行以下SQL脚本。SELECT PersonID,TerritoryID,StoreID FROM Sales.Customer _ WHERE CustomerID=@Sales.Customer_CustomerID@
要获取子记录,必须检索子表中引用给定记录的记录,如以下SQL脚本所示。SELECT SalesOrderID FROM Sales.SalesOrderHeader WHERE CustomerID=@Sales.Customer_CustomerID@
每个表的SQL语句保存在DataTable DataTableSQLStatements
中。- 创建一个新图:
mGraph = New AdjacencyGraph(New DbVertexProvider(), New DbEdgeProvider(), False)
。AdjacencyGraph
位于命名空间QuickGraph.Representations
中,该命名空间定义在QuickGraph.Algorithms.dll中。AdjacencyGraph
表示一个带有顶点和边的图。 - 创建初始顶点:
Dim vertex = GetStartVertex(mGraph)
。检索UI中指定的记录,创建顶点并将其添加到图中。数据库记录的对应顶点由表名和主键值定义。如果两个顶点的表名和主键值相等,则它们代表相同的记录。 - 通过调用方法
FillNeighbours
检索起始顶点的邻居。此方法采用深度优先搜索(DFS)算法来构建图。- 如果距离小于最大距离且顶点尚未被访问。
- 通过将顶点的主键值填充到先前生成的SQL语句中来构建SQL语句。通过调用
GetDataset
方法检索相关记录。GetDataSet
方法使用异步方法SqlAdapter.BeginExecuteReader
来填充DataSet
。 - 对于子表是顶点表的每一个外键。
- 通过调用
FillParent
方法填充父记录。FillParent
方法获取子顶点并创建或检索父顶点。- 创建父顶点。
- 如果这样的顶点已经存在。
- 子顶点的级别设置为子顶点级别与现有父顶点级别加1的最大值。
- 在现有父顶点和子顶点之间添加一条边。
- Else
- 父节点的级别设置为子节点级别减1。
- 将父顶点添加到图中。
- 在新创建的父顶点和子顶点之间添加一条边。
- 通过递归调用方法
FillNeighbours
来填充新创建的父顶点及其所有邻居。
- 通过调用
- 对于父表是顶点表的每一个外键。
- 对于子表的每一行。
- 通过调用
FillChild
方法填充子记录。FillChild
方法获取父顶点并创建或检索子顶点。- 创建子顶点。
- 如果这样的顶点已经存在。
- 父顶点的级别设置为父顶点级别与现有子顶点级别减1的最小值。
- 在父顶点和现有子顶点之间添加一条边。
- Else
- 子节点的级别设置为父节点级别加1。
- 将子顶点添加到图中。
- 在父顶点和新创建的子顶点之间添加一条边。
- 通过递归调用方法
FillNeighbours
来检索新创建的子顶点及其所有邻居。
- 通过调用
- 对于子表的每一行。
- 通过将顶点的主键值填充到先前生成的SQL语句中来构建SQL语句。通过调用
- 如果距离小于最大距离且顶点尚未被访问。
- 在
FillGraphViz
方法中,将生成的图写入两个文件。- 创建一个
GraphvizAlgorithm
对象:Dim svg = New GraphvizAlgorithm(graph, ".\", GraphvizImageType.Svg)
。 - 创建Graph1.svg文件:
mFileName = svg.Write("Graph1")
。 - 创建Graph1.dot文件:
File.WriteAllText("Graph1.dot", svg.Output.ToString())
。
- 创建一个
方法BackgroundWorker1_RunWorkerCompleted
检索文件Graph1.svg,如果它小于200kB,则在WebBrowser
控件中显示它,否则,它在默认的Webbrowser
中打开该文件。
此项目使用了在 Dependency Visualizer项目中提供的QuickGraph
库。不幸的是,我找不到这些库的文档。我尝试使用官方QuickGraph库,但文档非常简陋,也没有提供示例。
历史
- 2013年5月21日:首次发布
- 2016年9月22日:添加了FormReferences.vb以显示数据库记录的关系。