☰
Intouch对接SQL Server实现历史数据落库与VBA报表
2026/10/27 19:35:52 网站建设 项目流程

简介:这份Word文档面向SCADA、HMI工程师及自动化系统集成人员,围绕WonderWare Intouch 2014R2与SQL Server 2012的数据互通,解决实时数据入库、历史数据查询和Excel报表输出等工程问题。文档以图文步骤与脚本示例组织,内容覆盖数据库身份验证与登录权限、建库建表、Intouch标记名字典中的内存整型、内存消息、内存离散变量、SQL访问管理器绑定列表的列名和类型对应、SQLConnect与SQLInsert联接写入脚本、条件触发及退出断开连接,并延伸到Excel VBA报表模板的控制面板、参数日报页面和数据库mdf、ldf文件的附加恢复,适合有一定Intouch基础、需要打通数据链路的技术人员对照项目查阅。包内共1个doc文件,约2.48MB,以单文档形式集中汇总,目录清晰,便于按数据库配置、脚本编写和报表制作几个环节检索。已有531人学习,可作为Intouch与SQL Server集成及Excel报表搭建的实操参考。

1. 凌晨两点的那句“数据还能翻出来吗”

凌晨两点,产线操作站弹出一条温度超限报警,值班的人回头问:昨天下午三点那个批次的数据,现在还能不能翻出来?画面只做了实时显示、没做落库,这问题就只能靠回忆。这套 Intouch 2014 R2 加 SQL Server 2012、再配一套 Excel 报表 的组合,解决的就是这件事:在 IDE 里把关键标记绑到库表上,按条件定时写入,再用 VBA 把库里的行拉成一张能打印的日报。组态、数据库、VBA 三样东西都不新,难的是让它们在现场那台常年不关机的操作站上稳稳跑三年。适合手上已经有一台装好 Intouch 的操作站、想补上历史数据能力、又不愿意为几台设备上一整套 historian 的人。

2. SQL Server 2012 侧:身份验证、建表与 mdf 附加恢复

顺序上先把数据库这一头弄利索,不是因为数据库更重要,而是因为 Intouch 那边的绑定列表是“照着表结构填”的。表还没定,绑定列表就是空中楼阁,填完了回头改表名、改列类型,前面所有工作作废。我在现场见过不止一次:先花半天配 Intouch 脚本,最后发现库表列名和绑定列表对不上,只能推倒重来。所以这一章的落点是三件事——让操作站能用账号密码连进来、把表结构定死、把备份和恢复的路留出来。

2.1 身份验证模式不改,后面全是白工

装完 SQL Server 2012 默认是“仅 Windows 身份验证”,这时候 Intouch 用账号密码连是连不上的,SQLConnect 会直接返回非 0。改法在 SSMS 里:对象资源管理器最顶层节点右键 → 属性 → 左侧选“安全性” → 右侧“服务器身份验证”选“SQL Server 和 Windows 身份验证模式”→ 确定 → 重启 SQL Server 服务。重启这一步不能省,改完不重启,配置不生效,你会以为是脚本写错了。

接下来建登录名。对象资源管理器 → 安全性 → 登录名 → 右键“新建登录名”,填账号、选“SQL Server 身份验证”、设密码、取消“用户在下次登录时必须更改密码”。服务器角色页只勾 public,别顺手给 sysadmin;用户映射页勾上目标库,下面勾 db_datareader 和 db_datawriter 两个角色就够读写数据了。

原始资料里图省事直接拿 sa 连,密码还是明文写在脚本里。这在封闭的产线网里能跑,但两个后患:一是 sa 权限太大,脚本一旦被人改了表名,DROP 也能执行;二是 sa 密码往往全厂统一,改一次要牵动一堆系统,最后谁都不敢改。用专用账号更省心:

-- 建一个只对业务库有读写权限的账号,别用 sa 直连 CREATE LOGIN scada_w WITH PASSWORD = N'Chg#2024_Scada', CHECK_POLICY = ON; GO USE PLANT_BB; GO CREATE USER scada_w FOR LOGIN scada_w; GO ALTER ROLE db_datareader ADD MEMBER scada_w; -- 允许 SELECT ALTER ROLE db_datawriter ADD MEMBER scada_w; -- 允许 INSERT/UPDATE/DELETE GO

这段执行完,scada_w只能碰PLANT_BB这一个库,别的库连看都看不到。CHECK_POLICY = ON会强制密码满足复杂度,现场机器常年不联网的话也可以关掉,但我不建议——密码复杂度是唯一挡在“脚本被随手复制走”前面的东西。

2.2 建库建表:列名就是和 Intouch 的契约

建库的位置要提前想好。数据文件和日志文件默认落在 SQL Server 安装目录,C 盘。SCADA 的历史数据是持续增长的,一年下来几个 G 很常见,把 C 盘写满导致操作站蓝屏,属于典型的血泪经验。建库时直接指定到数据盘:

-- 库文件放到数据盘,日志和数据的增长量分开估 CREATE DATABASE PLANT_BB ON PRIMARY ( NAME = N'PLANT_BB', FILENAME = N'E:\DataBase\PLANT_BB.mdf', SIZE = 512MB, FILEGROWTH = 256MB ) LOG ON ( NAME = N'PLANT_BB_log', FILENAME = N'E:\DataBase\PLANT_BB_log.ldf', SIZE = 128MB, FILEGROWTH = 128MB ); GO USE PLANT_BB; GO -- 日报表:一行就是某台操作站在某个时刻写下的一条汇总 CREATE TABLE dbo.TDays ( RecID BIGINT IDENTITY(1,1) NOT NULL, RecDate DATETIME NOT NULL CONSTRAINT DF_TDays_RecDate DEFAULT (GETDATE()), NodeName NVARCHAR(32) NOT NULL, -- 操作站计算机名 BatchNo NVARCHAR(32) NULL, -- 批次号,对应 Message 标记 ProdQty INT NULL, -- 产量计数 AvgTemp FLOAT NULL, -- 平均温度,对应 Real 标记 CONSTRAINT PK_TDays PRIMARY KEY CLUSTERED (RecID) ); GO

RecID用自增列做主键,是为了避免“同一时刻两条记录主键冲突”这种在现场很难复现的问题。RecDate给了GETDATE()默认值,好处是 Intouch 绑定列表里不放这个字段也能写入,SQL Server 自己补当前时间;如果放了这个字段,就用 Intouch 传上来的值覆盖默认值。两种做法都行,但不要一半放一半不放。

这里最关键的一点:表里的列名,就是 Intouch 绑定列表要填的列名,必须逐字一致。AvgTemp和Avg_Temp在 SQL 里是两个东西,在绑定列表里也是两个东西,对不上就是 0 行受影响或者直接报错。我一般会把建表脚本存成.sql文件跟工程放一起,改表就改脚本、重跑,不靠手点 SSMS。

2.3 类型映射:Intouch 的 Real 和 SQL 的 float 不是随便对

绑定列表除了列名要对,列的数据类型也得接得住。Intouch 标记名字典里的类型就那么几种,落到 SQL Server 2012 上的对应关系我整理成下面这张表,照着填基本不会翻车:

Intouch 标记类型典型用途SQL Server 类型备注
Memory Integer计数、状态码、连接号INT连接号是 InTouch 分配的,别放进表里
Memory Real温度、压力、流量FLOATReal 是双精度,用 REAL 会丢精度
Memory Message批次号、班次、字符串NVARCHAR(131)Message 标记最长 131 字符,别超
Memory Discrete开关量、写入标记BIT 或 INT写入去重标记不要入库
$Date + $Time 组合时间戳DATETIME也可直接用 GETDATE() 默认值

Memory Real对应FLOAT这一条值得多说一句。有人图省空间把列建成REAL(单精度),温度这种量级看着没区别,但平均值累计几千条之后再算,末位误差就出来了。报表上差 0.1 度也许没人管,可一旦用来做批次判定就是事故。

Memory Message的 131 字符上限也是硬约束。表列如果建成NVARCHAR(32),而 Intouch 那边批次号标记实际填了 40 个字符,写入时要么截断要么报错,取决于驱动版本,这类问题排查起来极其别扭。

2.4 mdf/ldf 的备份与附加恢复

历史数据服务器要一份副本的时候,最土也最可靠的办法是停库拷贝物理文件。库停了之后,.mdf(数据文件)和.ldf(日志文件)两个一起拷走,少一个都附不上去。拷贝目标目录建议就用原路径结构,比如统一放E:\DataBase\,这样恢复脚本不用改路径。

附加在 SSMS 里的操作路径是:对象资源管理器 → 数据库 → 右键“附加”→ 添加 → 选中.mdf→ 确定。图形界面点两下就完事,但真正部署到另一台机器上时,用 T-SQL 更可控:

-- 把备份出来的物理文件附加成一个新库名,避免覆盖线上库 USE master; GO CREATE DATABASE PLANT_BB_202401 ON (FILENAME = N'E:\DataBase\PLANT_BB.mdf'), (FILENAME = N'E:\DataBase\PLANT_BB_log.ldf') FOR ATTACH; GO

FOR ATTACH表示直接挂载已有物理文件,不是新建库。新库名加日期后缀,是为了在同一条线上保留多份历史快照。

这一步最常见的报错是“操作系统错误 5(拒绝访问)”。原因几乎都一样:SQL Server 服务跑在NT Service\MSSQLSERVER这个虚拟账户下,它对E:\DataBase目录没有读写权限。解决办法是给这个账户加目录权限,而不是把整个盘设成 Everyone 完全控制。如果.ldf文件丢了或者损坏,也可以只附.mdf,加FOR ATTACH_REBUILD_LOG让 SQL Server 重建日志,但会丢掉未提交事务,只适合拿来做只读分析。

3. Intouch 侧:标记名字典、绑定列表与三段脚本

数据库那头定死了,Intouch 这头就是照着契约填空。整章要落地的其实只有四类东西:几个辅助用的内存标记、一份绑定列表、应用程序脚本里的连接与断开、条件脚本里的定时写入。顺序不能乱,得先有标记,绑定列表才有的可选;先有绑定列表,SQLInsert 才有第三个参数可用。

3.1 动手前先改系统日期时间格式

这一步看着像玄学,但确实能让后面少折腾。操作站控制面板 → 区域和语言 → 日期和时间格式,把短日期改成yyyy-MM-dd,时间改成HH:mm:ss。原因是 Intouch 的$Date、$Time这些系统标记转成字符串时,格式跟着操作系统区域设置走。如果机器是dd/MM/yyyy,写进DATETIME列时 SQL Server 在个别语言环境下会按MM/dd/yyyy解析,13 号以后的日子直接报转换失败。改成 ISO 格式,等于把这个不确定性提前掐掉。

改完重启一次 Intouch IDE,让系统标记重新初始化。这一步不做,你会以为改了没用。

3.2 三个脚手架标记:连接号、机器名、写入状态

打开 IDE,左侧应用树里找到标记名字典,新建下面四个内存标记。它们不参与任何画面显示,纯粹是脚本的“脚手架”:

标记名类型作用
ConnID1Memory Integer接收 SQLConnect 返回的连接号
NodeNameMemory Message存本机计算机名,用于多操作站区分
WSQLSMemory Discrete写入去重标记,真表示本轮已写过
GetDateTimeSMemory Message存拼接好的时间字符串,调试用

ConnID1必须是整型,因为SQLConnect会把分配到的连接号写回这个标记,后面的SQLInsert、SQLDisconnect都靠它找连接。WSQLS用离散型而不是整型,是因为它只有“写过/没写过”两种状态,离散型的语义更清楚,脚本里写IF WSQLS == 0 THEN也比比大小更直观。

GetDateTimeS在正式运行时其实用不上,但调试阶段很值钱:把$Date和$Time拼进去,用一个 Message 显示控件打在画面上,一眼就能看出格式对不对,不用去翻数据库。

3.3 绑定列表:把标记“贴”到表列上

IDE 左侧找 SQL 访问管理器,里面有两样东西——表定义和绑定列表。表已经在 SSMS 建好了,这里直接新建绑定列表,名字取TDays,和数据库表同名,后面脚本里两个参数看起来一样也不会混。

绑定列表的配置逻辑是:先选目标表,InTouch 会读回表的列清单,然后把左侧的 Intouch 标记逐个拖到对应列上。拖的时候三个对齐必须同时满足——列名一致、类型兼容、长度够用。少一条,SQLInsert就是返回非 0 或者静默写空值。

有个细节容易被忽略:自增列不要绑。RecID是IDENTITY,如果绑定列表里出现了它,生成的 INSERT 语句会带上这个列,SQL Server 直接报“当 IDENTITY_INSERT 为 OFF 时,不能向表插入显式值”。绑列表里只放业务字段,主键交给数据库自己生成。

绑完之后建议在 SQL 访问管理器里手动触发一次写入测试,或者先在画面上做一个“手动写一条”的按钮。等脚本全写完再一起调,出问题时分不清是绑定错了还是脚本错了。

3.4 SQLConnect:写在应用程序脚本的“启动时”

IDE 左侧脚本节点下有应用程序脚本,它分启动时、关闭时、运行时三段。连接数据库的代码放启动时,只执行一次,不会随画面切换反复重连:

' 应用程序脚本 - 启动时 ' 参数1:接收连接号的整型标记;参数2:OLE DB 连接串 SQLConnect( ConnID1, "provider=SQLOLEDB.1;Data Source=SCADA-DB01;Initial Catalog=PLANT_BB;" + "User ID=scada_w;Password=Chg#2024_Scada" );

连接串是分号分隔的键值对,五个部分各有讲究:

  • Provider:SQL Server 用SQLOLEDB.1,不要写成MSDASQL,后者走 ODBC 桥接,慢且不稳定。
  • Data Source:数据库所在计算机名。命名实例要写成SCADA-DB01\SCADA这种形式,并且目标机上 SQL Server Browser 服务得开着,否则解析不到实例。
  • Initial Catalog:库名,这里是PLANT_BB。
  • User ID/Password:2.3 节建的那个专用账号。

SQLConnect的返回值会写进ConnID1。返回 0 表示成功,非 0 就是失败,具体错误码可以在 Intouch 的日志或者函数返回值里看。我一般会在启动脚本里加一句判断,失败时往一个报警标记里写值,让画面能看见:

SQLConnect( ConnID1, "provider=SQLOLEDB.1;Data Source=SCADA-DB01;" + "Initial Catalog=PLANT_BB;User ID=scada_w;Password=Chg#2024_Scada" ); IF ConnID1 <= 0 THEN ConnFail = 1; ' 离散标记,画面上做个红色指示灯 ENDIF;

3.5 SQLInsert:条件脚本加上去重标记

写入放在条件脚本里,条件填$Second == 5,条件类型选“为真时”。意思是系统秒数等于 5 的那一秒内,脚本执行。这里有个必须理解的点:Intouch 的扫描周期通常是 250ms 到 500ms,$Second == 5在那一秒内会成立 2 到 4 个扫描周期,也就是这段脚本会在同一秒里被触发好几次。原始资料里的WSQLS标记就是干这个的。

' 条件脚本,条件:$Second == 5,类型:为真时 GetNodeName( NodeName, 131 ); ' ① 取本机计算机名 IF NodeName == "OPS-01" THEN ' ② 只让主机写,备机不重复写 IF $Minute >= 58 THEN ' ③ 整点前两分钟才落库 IF WSQLS == 0 THEN ' ④ 本轮还没写过 SQLInsert( ConnID1, "TDays", "TDays" ); ' ⑤ 写一条 WSQLS = 1; ' ⑥ 置位,挡住同一秒的重复触发 ENDIF; ELSE WSQLS = 0; ' ⑦ 过了整点就复位,为下一轮准备 ENDIF; ENDIF;

逐行说清楚。①GetNodeName的第二个参数 131 是取本机计算机名的模式值,不同版本枚举可能有差异,第一次用建议先写到 Message 标记里显示出来确认。② 多操作站场景下必须做主机判断,否则两台机器同时往一张表写,数据成对出现,报表统计直接翻倍。③$Minute >= 58让写入发生在每小时最后两分钟,符合“整点归档”的习惯,也避开交接班时段的高频操作。④ 到 ⑦ 是标准的自锁与复位:第一次进入时WSQLS为 0,写入并置 1,同一秒内后续几个扫描周期被挡住;等到分钟数掉回 58 以下(也就是下一个小时),ELSE分支把它清 0,下一轮重新开始。

SQLInsert三个参数分别是连接号、表名、绑定列表名。表名建议带 schema 前缀写成dbo.TDays,避免登录名的默认 schema 不是 dbo 时找不到表。绑定列表名和表名相同只是习惯,两者本来就允许不同名。

3.6 SQLDisconnect:关闭时一定要还回去

连接是有限资源,程序退出不释放,下次启动再连就可能拿到无效句柄。应用程序脚本的“关闭时”加一行就够:

' 应用程序脚本 - 关闭时 SQLDisconnect( ConnID1 );

别看只有一行,漏了它的后果是操作站重启几次之后,SQL Server 那边堆一堆睡眠会话,最后连接数打满,新连接全部失败,现象是“早上还好好的,下午就连不上了”。这类问题排查起来非常费劲,因为报错点离根因隔了好几层。

4. Excel 报表系统:VBA 模板与 ADO 查询

数据库里躺着的行,对值班的人来说等于不存在,得有一张能看、能打印、能发给班组长的东西。用 Excel 做这层壳是最省事的选择:现场每台办公电脑都装了,格式随便调,不用另写客户端。核心工作量全在 VBA 里——连库、查数、写单元格、自动刷新。

4.1 模板分两页:控制面板 + 参数日报

工作簿我一般拆成两个 Sheet。控制面板放查询条件:起始日期、结束日期、站点下拉(数据验证引用一列站点名)、一个“刷新”按钮。参数日报放展示区:第 1 到 4 行是标题和表头(日期、批次号、产量、平均温度),第 5 行往下是数据区。

两边靠命名区域连接,不要用Cells(5,1)这种硬编码。控制面板的日期单元格命名为rngDateFrom,站点下拉命名为rngNode,数据区起始单元格命名为rngDataStart。命名区域的好处是以后插入行、挪位置,代码一行都不用改,这是我在被改版需求折腾过几次之后养成的习惯。

4.2 VBA 引用:至少勾三项

打开 VBA 编辑器(Alt + F11),工具 → 引用,至少勾选这三项:

  • Microsoft ActiveX Data Objects 2.8 Library:ADO,连库和取记录集全靠它。
  • Microsoft Excel 16.0 Object Library:操作当前工作簿和单元格。
  • Microsoft Office 16.0 Object Library:部分对象模型的依赖。

版本号跟着 Office 走,装的是 2013 就是 15.0。少勾 ADO 的典型表现是编译时报“用户定义类型未定义”,指向ADODB.Connection那一行。这个报错看着像代码问题,其实一行代码没写错,纯粹是引用没勾。现场部署时最容易在这一点上卡住——开发机上勾过,拷到值班电脑上忘了勾,报错信息又不提示原因。

4.3 连接与查询:日期用 ISO 格式拼

连接串和 Intouch 那边的思路一致,只是账号换成只读的:

Option Explicit Private Const DB_SERVER As String = "SCADA-DB01" Private Const DB_NAME As String = "PLANT_BB" ' 拼连接串:只读账号,超时设短一点,库不可用时快速失败 Private Function BuildConnStr() As String BuildConnStr = "Provider=SQLOLEDB.1;Persist Security Info=True;" & _ "User ID=scada_r;Password=Chg#2024_Read;" & _ "Initial Catalog=" & DB_NAME & ";" & _ "Data Source=" & DB_SERVER & ";Connect Timeout=5" End Function

Connect Timeout=5是给现场网络留的余量。默认 15 秒,库一旦不可达,Excel 会像死机一样卡十几秒,用户以为程序崩了就开始狂点按钮,最后开了七八个连接。设成 5 秒,配合后面的错误处理,体验会好很多。

查询过程本身没什么花活,重点在日期参数的拼法:

Public Sub RefreshDailyReport() Dim conn As ADODB.Connection Dim rs As ADODB.Recordset Dim sql As String Dim dFrom As Date, dTo As Date Dim r As Long dFrom = Range("rngDateFrom").Value dTo = Range("rngDateTo").Value Set conn = New ADODB.Connection conn.Open BuildConnStr ' 日期统一用 yyyy-mm-dd,绕开区域设置差异;单引号包住的是字符串字面量 sql = "SELECT RecDate, BatchNo, ProdQty, AvgTemp " & _ "FROM dbo.TDays " & _ "WHERE RecDate >= '" & Format$(dFrom, "yyyy-mm-dd") & "' " & _ " AND RecDate < '" & Format$(DateAdd("d", 1, dTo), "yyyy-mm-dd") & "' " & _ " AND NodeName = '" & Range("rngNode").Value & "' " & _ "ORDER BY RecDate" Set rs = conn.Execute(sql) ' 先清空旧数据区,再逐行写入 Range("rngDataStart", Range("rngDataStart").End(xlDown)).ClearContents r = Range("rngDataStart").Row Do While Not rs.EOF Cells(r, 1).Value = rs!RecDate Cells(r, 2).Value = rs!BatchNo & "" Cells(r, 3).Value = rs!ProdQty Cells(r, 4).Value = rs!AvgTemp rs.MoveNext r = r + 1 Loop rs.Close: Set rs = Nothing conn.Close: Set conn = Nothing End Sub

几个参数值得解释。Format$(dFrom, "yyyy-mm-dd")输出的日期字符串不含时间,结束日期要DateAdd("d", 1, dTo)再加一天,用<比较,这样当天的 23:59:59 也能覆盖进来——写成<= dTo会漏掉当天绝大部分记录,这是最典型的“报表少了一天”的原因。rs!BatchNo & ""是为了把数据库里的 NULL 转成空字符串,直接赋值 NULL 到单元格会显示成 0,看着像批次号是 0。

4.4 打开工作簿自动刷新,并兜住异常

手动点刷新按钮不够,报表是要发给不看代码的人的。在ThisWorkbook里挂Workbook_Open,打开就刷新一次:

Private Sub Workbook_Open() On Error GoTo Failed Application.StatusBar = "正在查询数据库..." RefreshDailyReport Application.StatusBar = False Exit Sub Failed: Application.StatusBar = False MsgBox "数据刷新失败:" & Err.Description & vbCrLf & _ "请检查网络或联系维护人员。", vbExclamation End Sub

On Error GoTo Failed加StatusBar复位,这两件事必须成对出现。少了Application.StatusBar = False,程序报错后状态栏会一直挂着“正在查询数据库”,用户以为还在跑,实际早就断了。错误信息里带上Err.Description,至少能看出是超时还是登录失败,比一句“刷新失败”有用得多。

5. 避坑与排查:产线上才会暴露的 5 个问题

下面这几条都是配置完看着能跑、跑上几天才冒出来的问题。我把现象、原因、处理方式按现场记录的方式列出来,对着排查能省不少时间。

5.1 早上能连,下午连不上

现象:操作站刚开机时数据写入正常,运行六七个小时后SQLConnect返回非 0,画面上的连接指示灯变红,重启 Intouch 又好了。

原因:应用程序脚本的“关闭时”没写SQLDisconnect,或者写了但程序是被任务管理器强杀的,没走到关闭脚本。每次重启留一批睡眠会话,SQL Server 的默认连接数被占满。

解决:补上关闭时脚本;同时在 SSMS 里查sys.dm_exec_sessions,把login_name = 'scada_w'且status = 'sleeping'的会话找出来,确认是残留后清理。长期做法是给这个账号设一个连接超时策略,或者干脆在启动脚本里先SQLDisconnect再SQLConnect,保证拿到的是干净连接。

5.2 表里数据成对出现,产量翻倍

现象:日报里的产量统计是实际值的两倍,明细表里同一时刻有两条内容几乎相同的记录,只差几毫秒。

原因:$Second == 5在一秒内会触发多个扫描周期,而WSQLS的复位位置写错了——放到了IF外面,或者每次扫描都清 0,自锁失效。

解决:WSQLS = 1必须紧跟在SQLInsert成功之后,WSQLS = 0必须放在“分钟数不再满足条件”的分支里,不能无条件执行。改完在测试环境盯十分钟,用SELECT COUNT(*) ... GROUP BY RecDate确认同一秒只有一条。

5.3 附加数据库报“操作系统错误 5(拒绝访问)”

现象:把.mdf和.ldf拷到历史数据服务器的E:\DataBase\下,SSMS 里附加,弹“无法打开物理文件,操作系统错误 5”。

原因:SQL Server 服务账户对目标目录没有读写权限。拷贝文件用的是登录账号,权限正常,但 SQL Server 是以服务账户身份访问文件的,两者不是一回事。

解决:右键目录 → 属性 → 安全 → 编辑 → 添加 SQL Server 服务账户(NT Service\MSSQLSERVER或对应实例名),给读写权限。不要图省事把整个盘设成 Everyone 完全控制。如果只是拿来分析不想改数据,也可以附加成只读库。

5.4 Excel 报“未找到提供程序”或“用户定义类型未定义”

现象:开发机上报表跑得好好的,拷到值班电脑上打开,编译直接报错,或者运行到conn.Open时提示找不到提供程序。

原因:两种情况。一是 VBA 的引用没勾 ADO 库,ADODB.Connection这个类型压根不认识;二是 Office 的位数和 OLE DB 驱动位数不匹配,32 位 Office 配 64 位驱动就会报找不到提供程序。

解决:先查引用(工具 → 引用),缺Microsoft ActiveX Data Objects 2.8 Library就勾上。再查位数,SQLOLEDB.1是随 Windows 自带的,32/64 位一般都有;如果换成MSOLEDBSQL这类新驱动,就必须位数对齐。现场统一部署时,把 Office 位数和驱动位数写进装机规范,比每次出问题再查省事。

5.5 日期列写入失败或查询结果为空

现象:13 号以后的数据写不进库,报“将 varchar 转换为 datetime 时超出范围”;或者报表查某一天返回空,但库里明明有数据。

原因:区域设置是dd/MM/yyyy,$Date转字符串得到13/05/2024,SQL Server 按MM/dd/yyyy解析时 13 月非法。查询侧同理,Format$用了本地格式而不是 ISO。

解决:3.1 节改系统短日期格式为yyyy-MM-dd,所有拼接日期的地方统一Format$(d, "yyyy-mm-dd")。这条不只是 Intouch 的问题,任何跨语言传日期的场合都适用——能用 ISO 格式就别用本地格式。

6. 二次开发:把硬编码拆成配置,把单站改成多站

原始资料里的脚本是典型的“一次成型”写法:计算机名、库名、账号密码、写入条件全写死在脚本和 VBA 里。单条产线单台操作站够用,一旦要复制到第二条线,就得改十几处字符串,改漏一处就是几个小时排查。改造方向很明确——把变化的部分抽出去。

最省事的做法是建一张配置表,把站点相关的参数全放进去:

CREATE TABLE dbo.SysConfig ( ConfigKey NVARCHAR(64) NOT NULL PRIMARY KEY, ConfigValue NVARCHAR(256) NOT NULL ); INSERT INTO dbo.SysConfig VALUES ('DbServer', 'SCADA-DB01'), ('DbName', 'PLANT_BB'), ('WriteNode', 'OPS-01'), ('WriteMinute', '58');

Intouch 侧启动时先SQLSelect把这几行读进内存标记,再用字符串拼接组出连接串。这样换线只改数据,不动脚本,版本管理也简单。VBA 那边同理,把DB_SERVER、DB_NAME从常量改成读配置表或者读同目录下的一个文本文件。

第二个改造点是写入条件。$Minute >= 58这种写死的逻辑,换一个班次制度就不适用。把它改成从配置表读WriteMinute和WriteInterval,脚本里用IF $Minute >= WriteMinute THEN,现场调整不用重新下发脚本。

第三个点容易被忽略:验证方法要先于改造落地。改完之后别直接上线,先在测试库上跑一整天,用一条对账 SQL 卡住结果:

-- 按小时统计写入条数,理论上每小时只该有 1 条 SELECT CONVERT(varchar(13), RecDate, 120) AS HourSlot, COUNT(*) AS Cnt FROM dbo.TDays WHERE RecDate >= DATEADD(day, -1, GETDATE()) GROUP BY CONVERT(varchar(13), RecDate, 120) HAVING COUNT(*) <> 1;

这条查询返回空集才算通过。它同时能抓出重复写入和漏写两种问题,比肉眼看报表靠谱得多。我习惯把它做成一个每天定时跑的作业,出问题往前翻日志就能定位到具体是哪一小时开始异常的。

从那以后,我每接一个带落库的组态项目,都会先把标记清单、库表清单、绑定列表清单这三份东西打印出来对一遍,再动手写第一行脚本——图上看着都对、跑起来全不对的场面,见过一次就够了。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询