简介:这份文档面向SCADA系统工程师与Intouch初学者,聚焦WonderWare Intouch 2014R2平台与SQL Server 2012数据库的集成应用,解决工业现场实时数据存储与报表输出的实际问题。内容涵盖在Intouch中配置数据库连接、设置身份验证与登录权限、通过标记名字典和SQL访问管理器完成变量绑定,并借助SQLConnect、SQLInsert等函数实现数据写入,同时讲解如何用Excel搭建报表系统读取数据库数据,以及SQL数据库文件的备份与恢复思路。资源包为单个doc文档,约2.48MB,结构按章节展开,便于对照操作。目前已有531人学习,适合需要打通Intouch与数据库链路、构建实时监控与报表方案的工程人员参考,可帮助读者理解从数据库配置到报表呈现的完整流程,并掌握常见连接与写入脚本的编写方法。
1. 从 Intouch 到 Excel:一条被低估的数据链路
很多做 SCADA 的同行都有过这种经历:Intouch 画面上实时曲线跑得好好的,历史报警也能查,但一到月底要交生产报表,还是得靠人拿着 U 盘去工控机上拷数据,再手动往 Excel 里贴。问题出在 Intouch 自带的历史趋势和数据归档,本质是给操作员看的,不是给管理层做分析的。它不擅长做跨班次、跨设备的聚合统计,更没法直接生成带格式的报表文件。
标题里说的「在 Intouch 中添加数据库及基于 Excel 报表系统的相关操作」,拆开看其实是两件事:第一,把 Intouch 的实时和历史数据落到一个关系型数据库里,通常是 SQL Server;第二,用 Excel 作为报表前端,通过 VBA 和 SQLConnect 这类接口去读库、算指标、出报表。这套组合在中小型项目里非常实用,因为它不需要额外买 BI 授权,Excel 人人会用,SQL Server Express 版本也够用。适合谁?适合那些手上有 Intouch 项目、又被报表需求追着跑的自控工程师。下面我把这条链路从建库到出表完整走一遍。
2. 建库与 Intouch 侧配置:把数据落到 SQL Server
2.1 为什么选 SQL Server 而不是 Access 或 CSV
Intouch 的历史数据归档格式是专有的,直接解析很麻烦。常见做法是通过 SQLConnect 或者第三方 Historian 接口,把数据写到关系型数据库。选 SQL Server 的理由很直接:它和 Windows 生态集成好,Intouch 的 SQLConnect 组件原生支持,Express 版本免费且单库 10GB 对大多数产线够用。Access 虽然轻,但并发一上来就容易锁库,而且 VBA 通过 ADO 连 Access 和连 SQL Server 的代码几乎一样,迁移成本低。CSV 就更不用说了,没有索引,查一个月的数据要全表扫,报表一跑就卡死。
安装 SQL Server 时有个坑要注意:混合验证模式一定要开。Intouch 的 SQLConnect 默认走 SQL 账号认证,如果你只开了 Windows 认证,后面连接字符串会一直报登录失败。安装完成后,在 SSMS 里新建一个数据库,比如叫IntouchData,然后建两张核心表:一张实时表RT_Data,一张历史表His_Data。
-- 实时数据表,按标签名存当前值 CREATE TABLE RT_Data ( TagName NVARCHAR(64) PRIMARY KEY, TagValue FLOAT, UpdateTime DATETIME DEFAULT GETDATE() ); -- 历史数据表,按时间戳存归档值 CREATE TABLE His_Data ( ID BIGINT IDENTITY(1,1) PRIMARY KEY, TagName NVARCHAR(64), TagValue FLOAT, LogTime DATETIME, Quality INT ); CREATE INDEX IX_His_TagTime ON His_Data (TagName, LogTime);逻辑说明:RT_Data用标签名做主键,保证每个标签只有一条当前记录,Intouch 侧可以用 UPDATE 或 MERGE 写入。His_Data用自增 ID 做主键,标签名和时间戳建联合索引,因为报表查询几乎都是「某个标签在某段时间内的值」。Quality字段存数据质量码,Intouch 里对应的是质量戳,报表里可以过滤掉坏值。参数上,TagValue用 FLOAT 而不是 DECIMAL,因为 Intouch 的模拟量本身就是浮点,DECIMAL 反而要额外转换。
2.2 Intouch 侧 SQLConnect 的配置步骤
Intouch 这边需要装 SQLConnect 组件,安装后在 WindowMaker 的「特别」菜单里能找到「SQL 访问管理器」。配置流程分三步:先建一个绑定,把 Intouch 的标签和数据库字段对应起来;再建一个连接,填 SQL Server 的地址、库名、账号密码;最后在脚本里调用。
绑定配置里有个细节:标签名和数据库字段的映射是大小写敏感的。Intouch 标签习惯用Tank1_Level这种写法,数据库字段如果建成了tank1_level,绑定就会失败。我一般建议数据库字段名直接复制 Intouch 标签名,省得后面排查。
连接配置里,连接字符串的写法很关键:
-- 在 SQLConnect 的连接配置里填 Provider=SQLOLEDB;Data Source=192.168.1.100;Initial Catalog=IntouchData;User ID=sa;Password=YourPassword;这里Data Source填 IP 而不是主机名,因为工控机的 DNS 解析经常不稳定。User ID用sa只是为了调试方便,正式项目应该建一个只有读写权限的专用账号。密码里如果带分号或单引号,连接字符串会解析出错,这种玄学问题我遇到过两次,后来统一改成纯字母数字密码。
脚本调用部分,Intouch 的脚本编辑器里可以用SQLConnect函数。比如在「数据改变」脚本里写:
' Intouch 脚本,当 Tank1_Level 变化时写入实时表 SQLConnect("MyConnection"); SQLSetStatement("UPDATE RT_Data SET TagValue = :TagValue, UpdateTime = GETDATE() WHERE TagName = 'Tank1_Level'"); SQLSetParam("TagValue", Tank1_Level); SQLExecute(); SQLDisconnect();逻辑说明:SQLConnect的参数是连接配置的名称,不是连接字符串本身。SQLSetParam把 Intouch 标签的值绑到 SQL 语句的参数上,避免拼接字符串带来的注入风险和类型转换问题。SQLExecute执行后要SQLDisconnect,否则连接池会耗尽。参数上,如果写入频率很高,比如每秒一次,建议改成批量提交,每 10 秒攒一批再写,否则 SQL Server 的日志会涨得很快。
3. Excel 报表端:用 VBA 和 SQLConnect 把数据拉出来
3.1 Excel 连接 SQL Server 的两种方式对比
Excel 端连 SQL Server,常见做法有两种:一种是用「数据」选项卡里的「从其他源」建 ODBC 连接,另一种是用 VBA 里的 ADO 对象。ODBC 方式的好处是不用写代码,刷新一下就能出数据,但缺点是灵活性差,没法做复杂的参数化查询,而且每次刷新都要重新输密码。VBA + ADO 的方式代码量不大,但可控性强,能根据用户选的日期范围动态拼 SQL,还能在拉完数据后直接做计算和格式化。
我一般推荐 VBA + ADO,因为报表需求往往不是「把整张表拉出来」这么简单。比如要算某台设备当班的累计产量,需要按时间范围过滤、按班次分组、再求和。这些用 ODBC 的图形界面很难做,用 SQL 就是一句GROUP BY的事。
3.2 VBA 里用 ADO 查询 SQL Server 的完整代码
下面这段代码放在 Excel 的模块里,功能是连接 SQL Server,按日期范围查历史数据,写到当前工作表。
Sub QueryHisData() Dim conn As Object Dim rs As Object Dim strConn As String Dim strSQL As String Dim startDate As String Dim endDate As String ' 从单元格读取日期范围 startDate = Format(Sheet1.Range("B1").Value, "yyyy-mm-dd hh:mm:ss") endDate = Format(Sheet1.Range("B2").Value, "yyyy-mm-dd hh:mm:ss") ' 连接字符串,注意 Provider 用 SQLOLEDB strConn = "Provider=SQLOLEDB;Data Source=192.168.1.100;" & _ "Initial Catalog=IntouchData;User ID=sa;Password=YourPassword;" ' 参数化查询,避免 SQL 注入和日期格式问题 strSQL = "SELECT TagName, TagValue, LogTime FROM His_Data " & _ "WHERE LogTime BETWEEN ? AND ? AND Quality = 192 " & _ "ORDER BY LogTime" Set conn = CreateObject("ADODB.Connection") Set rs = CreateObject("ADODB.Recordset") conn.Open strConn ' 用 Command 对象传参数 Dim cmd As Object Set cmd = CreateObject("ADODB.Command") cmd.ActiveConnection = conn cmd.CommandText = strSQL cmd.CommandType = 1 ' adCmdText cmd.Parameters.Append cmd.CreateParameter("startDate", 135, 1, , startDate) ' adDBTimeStamp cmd.Parameters.Append cmd.CreateParameter("endDate", 135, 1, , endDate) Set rs = cmd.Execute ' 把结果写到工作表,从 A5 开始 If Not rs.EOF Then Sheet1.Range("A5").CopyFromRecordset rs Else MsgBox "该时间段内没有数据" End If rs.Close conn.Close Set rs = Nothing Set conn = Nothing End Sub逻辑说明:CreateParameter的第三个参数1表示输入参数,第四个参数省略表示不指定长度,第五个参数是实际值。135是adDBTimeStamp的类型码,对应 SQL Server 的DATETIME。Quality = 192是 Intouch 里好值的质量码,过滤掉坏值能避免报表里出现莫名其妙的尖峰。CopyFromRecordset一次把整个结果集写进去,比逐行循环快得多,几千行数据几乎瞬间完成。
参数上,startDate和endDate用Format函数转成字符串再传,是因为 ADO 的adDBTimeStamp参数在某些驱动版本下对Date类型支持不好,转成标准格式的字符串反而稳定。如果查询数据量很大,比如超过 10 万行,建议在 SQL 里先做聚合,别把原始数据全拉到 Excel,否则内存会爆。
3.3 用 VBA 做班次统计和报表格式化
拉完数据只是第一步,报表要的是统计结果。下面这段代码在查询结果的基础上,按班次汇总产量。
Sub CalcShiftOutput() Dim lastRow As Long Dim i As Long Dim shift As String Dim total As Double lastRow = Sheet1.Cells(Sheet1.Rows.Count, "A").End(xlUp).Row ' 在 D 列写班次,E 列写累计值 Sheet1.Range("D5").Value = "班次" Sheet1.Range("E5").Value = "累计产量" Dim dict As Object Set dict = CreateObject("Scripting.Dictionary") For i = 6 To lastRow Dim logTime As Date logTime = Sheet1.Cells(i, 3).Value ' 按小时判断班次:8-16 为早班,16-24 为中班,0-8 为夜班 Select Case Hour(logTime) Case 8 To 15 shift = "早班" Case 16 To 23 shift = "中班" Case Else shift = "夜班" End Select ' 累加产量,假设 B 列是产量值 If dict.Exists(shift) Then dict(shift) = dict(shift) + Sheet1.Cells(i, 2).Value Else dict.Add shift, Sheet1.Cells(i, 2).Value End If Next i ' 输出统计结果 Dim r As Long r = 6 Dim key As Variant For Each key In dict.Keys Sheet1.Cells(r, 4).Value = key Sheet1.Cells(r, 5).Value = dict(key) r = r + 1 Next key ' 格式化表头 With Sheet1.Range("D5:E5") .Font.Bold = True .Interior.Color = RGB(200, 200, 200) End With End Sub逻辑说明:用Scripting.Dictionary做分组累加,比用 Excel 公式或透视表更灵活,因为班次规则可以随时改。Hour函数取小时数,Select Case判断班次,这里的分界点按实际排班调整。dict.Keys遍历输出,顺序不保证,如果需要固定顺序,可以在输出前手动排序。参数上,lastRow用End(xlUp)动态获取,避免写死行号。如果数据量超过几万行,字典累加会有点慢,可以考虑先把数据读进数组再处理,速度能快一个数量级。
4. 避坑与排查:这条链路上最容易翻车的五个地方
4.1 连接字符串报「未找到提供程序」
现象:VBA 运行到conn.Open时报错「未找到提供程序。该程序可能未正确安装」。
原因:Excel 是 64 位的,而 SQL Server 的 OLE DB 驱动只装了 32 位版本,或者反过来。Windows 上 64 位和 32 位的驱动是分开注册的,不通用。
解决:确认 Office 的位数,然后装对应位数的 SQL Server OLE DB 驱动。如果不想折腾驱动,可以把Provider=SQLOLEDB改成Provider=MSOLEDBSQL,后者对新版 SQL Server 支持更好,但同样要注意位数匹配。
4.2 日期范围查询返回空结果
现象:SQL 里明明有数据,VBA 查出来却是空的。
原因:Intouch 写入数据库的时间是 UTC 时间,而 Excel 里用户输入的是本地时间,两者差 8 小时。或者LogTime字段存的是字符串而不是DATETIME,比较时按字符串比,2024-1-1和2024-01-01不相等。
解决:先直接在 SSMS 里跑同样的查询,确认数据存在。如果 SSMS 能查到而 VBA 查不到,检查参数传递的格式。我一般会在 VBA 里把拼好的 SQL 用Debug.Print输出到立即窗口,复制到 SSMS 里跑一遍,这样能快速定位是 SQL 问题还是参数问题。
4.3 Excel 加载项被禁用导致按钮失效
现象:报表文件发给同事,对方打开后按钮点不动,提示「此工作簿中的宏已被禁用」。
原因:Excel 的宏安全设置默认是「禁用所有宏,并发出通知」,而且从网络下载的文件会被标记为「来自互联网」,即使启用宏也会被阻止。
解决:在「文件」→「选项」→「信任中心」→「信任中心设置」→「宏设置」里选「启用所有宏」,同时把文件所在目录加到「受信任位置」。如果文件要分发,最好用自签名证书给 VBA 工程签名,这样对方只需要信任证书就行,不用改全局宏设置。
4.4 SQL Server 密码到期导致连接中断
现象:系统跑了几个月一直正常,突然某天所有报表都连不上数据库,报「登录失败」。
原因:SQL Server 的sa账号或者专用账号设置了密码过期策略,到期后账号被锁定。
解决:在 SSMS 里把该账号的「强制实施密码过期策略」取消勾选。如果是sa账号,还要确认「登录」属性里没有被禁用。正式项目里建议建一个专用账号,密码永不过期,权限只给db_datareader和db_datawriter。
4.5 大批量写入导致数据库日志暴涨
现象:Intouch 侧写入频率高的时候,SQL Server 的日志文件几分钟就涨到几个 GB,磁盘告警。
原因:数据库的恢复模式是「完整」,每次写入都记日志,而日志又没做定期备份,导致日志文件一直增长。
解决:把数据库恢复模式改成「简单」,日志会在检查点后自动截断。如果业务要求能恢复到某个时间点,那就保留「完整」模式,但必须建一个定期备份日志的作业。我一般对报表库直接用「简单」模式,因为报表数据丢了可以重新从 Intouch 归档里补,不值得为它做事务日志备份。
5. 进阶技巧:用 VBA 数组和参数化查询把报表速度提上来
前面说的CopyFromRecordset已经比逐行循环快很多,但如果数据量到了几十万行,或者要在 VBA 里做复杂的逐行计算,直接操作单元格还是会卡。这时候可以把记录集先读进 VBA 数组,在内存里算完再一次性写回工作表。
Sub FastReport() Dim conn As Object, rs As Object Dim arr As Variant Dim i As Long, j As Long Dim strConn As String, strSQL As String strConn = "Provider=SQLOLEDB;Data Source=192.168.1.100;" & _ "Initial Catalog=IntouchData;User ID=sa;Password=YourPassword;" strSQL = "SELECT TagName, TagValue, LogTime FROM His_Data " & _ "WHERE LogTime >= ? ORDER BY LogTime" Set conn = CreateObject("ADODB.Connection") Set rs = CreateObject("ADODB.Recordset") conn.Open strConn Dim cmd As Object Set cmd = CreateObject("ADODB.Command") cmd.ActiveConnection = conn cmd.CommandText = strSQL cmd.Parameters.Append cmd.CreateParameter("start", 135, 1, , "2024-01-01 00:00:00") Set rs = cmd.Execute ' 把记录集转成二维数组 If Not rs.EOF Then arr = rs.GetRows End If ' arr 是 (列, 行) 的二维数组,转置后写入 Dim outArr() As Variant ReDim outArr(1 To UBound(arr, 2) + 1, 1 To UBound(arr, 1) + 1) For i = 0 To UBound(arr, 2) For j = 0 To UBound(arr, 1) outArr(i + 1, j + 1) = arr(j, i) Next j Next i ' 一次性写入工作表 Sheet1.Range("A5").Resize(UBound(outArr, 1), UBound(outArr, 2)).Value = outArr rs.Close conn.Close Set rs = Nothing Set conn = Nothing End Sub逻辑说明:rs.GetRows把整个记录集读进一个二维数组,数组的第一维是列,第二维是行,和 Excel 的行列方向相反,所以需要转置。转置用循环做,虽然多了一层,但比直接操作单元格快得多。ReDim重新定义输出数组的大小,Resize一次性写入,避免了逐单元格赋值的开销。参数上,GetRows默认返回所有行,如果数据量特别大,可以加第二个参数限制行数,分批处理。
还有一个技巧是用Command对象的Parameters做参数化查询,而不是拼字符串。参数化查询不仅防注入,还能让 SQL Server 复用执行计划,查询速度更稳定。我见过有人为了省事直接拼 SQL,结果标签名里带单引号,整个查询就崩了,这种血泪经验一次就够。
最后说一个验证方法:在 SSMS 里用SET STATISTICS TIME ON打开执行时间统计,对比参数化查询和拼接查询的 CPU 时间。参数化查询在重复执行时优势明显,因为执行计划被缓存了。这个习惯我保持了多年,每次优化查询前先看统计信息,比凭感觉调快得多。
希望帮到你。
本文还有配套的精品资源,点击获取