Intouch 数据入 SQL Server 并用 Excel VBA 构建报表系统
简介这份文档面向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 Server2.1 为什么选 SQL Server 而不是 Access 或 CSVIntouch 的历史数据归档格式是专有的直接解析很麻烦。常见做法是通过 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 的连接配置里填 ProviderSQLOLEDB;Data Source192.168.1.100;Initial CatalogIntouchData;User IDsa;PasswordYourPassword;这里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 ProviderSQLOLEDB;Data Source192.168.1.100; _ Initial CatalogIntouchData;User IDsa;PasswordYourPassword; 参数化查询避免 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 驱动。如果不想折腾驱动可以把ProviderSQLOLEDB改成ProviderMSOLEDBSQL后者对新版 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 ProviderSQLOLEDB;Data Source192.168.1.100; _ Initial CatalogIntouchData;User IDsa;PasswordYourPassword; 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 时间。参数化查询在重复执行时优势明显因为执行计划被缓存了。这个习惯我保持了多年每次优化查询前先看统计信息比凭感觉调快得多。希望帮到你。本文还有配套的精品资源点击获取