简介:这份文档面向使用 WonderWare Intouch 2014 R2 搭建 SCADA 人机界面的工程人员与自动化学习者,围绕 Intouch 与 SQL Server 2012 的数据存取及 Excel 报表制作,梳理从数据库配置到报表呈现的完整实现思路。包内仅含 1 个 doc 文件,约 2.48MB,以图文步骤形式组织,适合按章节查阅和现场对照。已有 533 人学习下载,具备一定的参考与复用价值。内容涉及 SQL Server 身份验证与登录名设置、数据库及表单新建、Intouch 标记名字典和绑定列表配置,以及 SQLConnect、SQLInsert、SQLDisconnect 等脚本的编写;后半部分还讲到 Excel 报表模板、控制面板与参数日报页面、VBA 引用设置,并说明数据库 mdf/ldf 文件的恢复操作,便于读者理解实时监控数据写入数据库并生成报表的流程。
1. 从夜班一张空白报表说起:Intouch 2014 R2 写进 SQL Server 2012 的那条链路
夜班交接,班长点开 Excel 那份“参数日报”,日期选昨天,刷新,表格一片空白,电话直接打到值班室问是不是报表程序坏了。远程连上去看,Excel 本身没毛病,SQL Server 2012 里的表也有数据,但最后一条记录停在三天前。再往下翻,Intouch 2014 R2 侧那个连接号还是个负数——数据库连接从那次工控机重启之后就没成功过,只是没人发现。在 Intouch 2014 R2 这类 HMI 平台做 SQL Server 2012 落库、再用 Excel 出报表,绝大多数故障不是出在脚本写得多复杂,而是出在连接串、绑定列表、时间条件这些看着最不起眼的地方。这套组合的价值也很直接:现场实时数据进库、历史可查、班报日报用 Excel 出,不额外上报表组件。适合做 SCADA 组态、要把数据落进关系库、又得给生产端交付报表的开发者。
2. 工程结构与运行环境:Intouch 工程、SQL 库、Excel 模板三者的角色划分
先把这套系统的“骨架”摆清楚,后面看脚本才不容易迷路。整套东西其实是三段式:Intouch 负责采集和触发,SQL Server 负责存,Excel 负责读和呈现。三段之间只有两个契约——一个是绑定列表到表列的字段契约,一个是连接串到库权限的账号契约。这两处只要有一处对不上,表现出来都是“报表没数据”,但根因完全不在同一层。
我一般接手这类现场,第一件事不是看脚本,而是把这三段的边界画出来:哪台机器跑 Intouch,哪台机器跑库,Excel 模板放在谁的电脑上、由谁打开。因为这三者可能分布在不同的机器上,网络、账号、防火墙、区域设置全都不一样,任何一个不一致都能让链路断掉。
2.1 三个组件的分工与数据流向
按最小可跑通的配置,一台操作站(运行 Intouch 2014 R2 的 IDE 和运行时)同时承担采集和写库;一台数据库服务器跑 SQL Server 2012;报表端可以是任意一台装了 Excel 的办公机或者工程师站,通过局域网连库读数据。
数据流向是单向的:Intouch 标记名 → 绑定列表 → SQLInsert → 数据库表 → Excel 查询 → 单元格。反方向没有回写,这一点很重要,意味着报表端可以给一个只读账号,不需要写权限,权限收敛之后现场安全性会好很多。
工程目录上,Intouch 这一侧的核心资产就三块:标记名字典(定义所有变量)、绑定列表(定义变量到表列的映射)、应用程序脚本与条件脚本(定义连接、插入、断开的时机)。Excel 那一侧的核心资产是模板文件本身,里面包含三个工作表页和一份 VBA 工程。数据库那一侧是库文件、表结构和账号。
常见做法是把这些资产分开管理:Intouch 工程目录定期整体备份,数据库的 .mdf / .ldf 单独放在固定盘符的数据目录下,Excel 模板放在共享盘的模板目录里,每次发布新版本用版本号后缀区分,不要覆盖原文件。
2.2 数据库侧的准备:身份验证模式、登录名与库表建立
第一步是让库“能连”。进 SQL Server Management Studio,在实例上右键属性,左侧选“安全性”,右侧服务器身份验证选“SQL Server 和 Windows 身份验证模式”。这一步改完必须重启数据库服务才生效,很多人改完就直接去连,发现还是连不上,白折腾半小时。
接着建登录名。在“安全性 → 登录名”上右键新建,给一个专用账号,比如scada_w给 Intouch 写库用,scada_r给 Excel 读库用。不要图省事直接用sa,一是权限过大,二是密码一旦变更所有连接串全要改,三是审计上说不清楚。给scada_w只授db_datawriter,给scada_r只授db_datareader,这是最小权限的底线。
库和表建好之后,表结构要和绑定列表严格对齐:
-- 报表主表:一行对应一个采样时刻的一批工艺参数 CREATE TABLE dbo.TDays ( RecID INT IDENTITY(1,1) PRIMARY KEY, -- 自增主键,避免重复行无主键 RecTime DATETIME NOT NULL, -- 采样时刻,由 Intouch 侧写入 NodeName NVARCHAR(32) NOT NULL, -- 采集站计算机名,多站共库时用于区分 P01 FLOAT NULL, -- 1# 泵出口压力 T01 FLOAT NULL, -- 1# 罐温度 F01 FLOAT NULL, -- 主流量 BatchNo NVARCHAR(16) NULL -- 班次号,报表按班次分组用 ); -- 报表几乎全是按时间范围查,这个索引不建,数据量一上来查询会明显变慢 CREATE INDEX IX_TDays_RecTime ON dbo.TDays(RecTime);RecID用自增主键是为了让每一行可唯一定位,后面 Excel 端要按行号回查或者做增量拉取时有用。RecTime建索引是因为所有报表查询都带时间范围条件,几千行看不出差别,几十万行的时候差别就是几秒和几十秒。字段类型上,Intouch 的实型对应FLOAT,内存消息(字符串)对应NVARCHAR,时间建议直接用DATETIME而不是字符串,否则后面 Excel 里做时间聚合会很别扭。
提示:建表时把列名和顺序记下来,绑定列表里的变量顺序要和这里完全一致,不是“差不多”就行。
2.3 标记名字典规划:Connid1、WSQLS、NodeName 各管什么
标记名字典是整个 Intouch 工程的变量总账。这套落库逻辑最少需要四个辅助标记,都是内存型,不占 I/O 点:
| 标记名 | 类型 | 作用 | 初值 |
|---|---|---|---|
| Connid1 | 内存整型 | 保存 SQLConnect 返回的连接号,负值代表连接失败 | 0 |
| WSQLS | 内存离散 | 写入完成标志,防止同一周期重复写库 | 0 |
| NodeName | 内存消息 | 存放本机计算机名,多站共库时用来区分数据来源 | 空 |
| GetDateTimeS | 内存消息 | 存放本次落库的时间戳字符串,便于调试对账 | 空 |
Connid1是关键,它不是一个“配置项”,而是一个运行时状态量。脚本里所有 SQLInsert、SQLDisconnect 的第一个参数都是它,一旦它是负值,后面所有操作都不会生效,而且 Intouch 不会主动报错弹窗,数据就是静默地不写。所以常见做法是在画面上做一个小的连接状态指示灯,颜色绑到Connid1 >= 0,让操作员一眼能看出来。
WSQLS是防重复写的闸门。它的语义是“本周期已经写过了”:写之前判断为 0,写完立刻置 1,进入下一个周期或者离开写入窗口时复位。这个标记如果不建,条件脚本每个触发周期都会插一行,一天下来表里能多出几万条重复记录,报表里全是脏数据。
NodeName在单站项目里看起来多余,但只要现场有两台以上的操作站写同一个库,它就是唯一的区分手段。GetNodeName(NodeName, 131)里的 131 是取计算机名的功能码,这个参数不要随手改。
3. Intouch 侧数据落库链路:从绑定列表到 SQLInsert 的脚本拆解
这一段是整个项目的核心。Intouch 侧要做的事就四件:连上库、把变量绑到表列、在正确的时机插一行、退出时断开。四件事分别对应四个地方:应用程序脚本、SQL 访问管理器的绑定列表、条件脚本、应用程序的“关闭时”脚本。搞混了执行顺序,就会出现“连上了但没数据”或者“数据重复写入”这类玄学问题。
3.1 绑定列表与数据库表列的对应关系
绑定列表在左侧工具视图的 SQL 访问管理器里,选“绑定列表”。它的作用是告诉 Intouch:这一组变量,对应数据库里的哪张表、哪些列。规则很死板——变量名要和列名一一对应,变量数据类型要和列的数据类型一一对应,顺序也要对得上。
比如前面建的TDays表,绑定列表里就按RecTime、NodeName、P01、T01、F01、BatchNo的顺序放六个标记名。少放一个,SQLInsert 会报错;多放一个,同样报错;顺序错了但类型巧合能对上,数据就会错位写进别的列,这种最恶心,因为表面上一切正常,等发现的时候历史数据已经污染了。
绑定列表本身不产生任何动作,它只是一个映射声明。真正触发写入的是SQLInsert(Connid1, "TDays", "TDays"),第一个字符串是数据库里的表名,第二个字符串是绑定列表的名字。这两个名字我一般刻意设成一样,减少现场维护时的心智负担,但要知道它们在概念上是两回事——一个是库里的物理表,一个是 Intouch 工程里的逻辑组。
注意:改了表结构(加列、改类型、调顺序)之后,绑定列表必须同步改,而且改完要重新编译工程、重启运行时,只保存不生效。
3.2 应用程序脚本:SQLConnect 连接串参数逐个拆
连接放在应用程序脚本的“启动时”里。我习惯先断一次再连,防止上一次异常退出(比如直接断电)留下悬挂的会话句柄:
{ 应用程序脚本 - 启动时 } SQLDisconnect(Connid1); { 先断一次,清掉可能残留的连接 } Connid1 = SQLConnect("provider=sqloledb;Data Source=OP01;Initial Catalog=PlantDB;UserID=scada_w;Password=YourPass;"); IF Connid1 < 0 THEN LogMessage("SQL Connect Failed, Code=" + Text(Connid1, "#")); ENDIF;SQLConnect的返回值就是连接号,成功是大于等于 0 的整数,失败是负值。很多人写脚本不接收返回值,直接用常量当连接号,结果库一改密码就全军覆没还查不出原因。接收返回值并且判断,是这个脚本里唯一不能省的两行。
连接串是分号分隔的键值对,五个片段各有各的坑:
| 片段 | 含义 | 现场怎么填 |
|---|---|---|
| provider | 数据访问提供程序 | SQL Server 用sqloledb,不要写成sqloledb.1之外的变体 |
| Data Source | 数据库所在计算机名或实例名 | 有命名实例要写主机名\实例名,只写主机名连不上 |
| Initial Catalog | 默认连接的库名 | 写库名,不要写 .mdf 文件路径 |
| User ID | 登录名 | 用专用写账号,不要用 sa |
| Password | 密码 | 明文写在这里,所以工程目录权限必须收紧 |
Data Source是最容易翻车的一项。现场改了计算机名、或者换了网段、或者用了 SQL Server 的命名实例,这里不跟着改,连接号就一直是负的。脚本里那个LogMessage是给后人留的黑匣子,出问题时去 Intouch 的日志查看器里翻这条记录,比猜快得多。
退出时的断开放在应用程序脚本的“关闭时”里:
{ 应用程序脚本 - 关闭时 } SQLDisconnect(Connid1); Connid1 = -1; { 置为无效值,下次启动时状态明确 }不写SQLDisconnect的后果不是立刻可见的,而是数据库那边会话数慢慢堆积,几天后连接数打满,所有新连接全部失败。这种问题排查起来特别费劲,因为症状是“突然就连不上了”,而根因在几天前。
3.3 条件脚本与 WSQLS 标志位:一次写入为什么只写一次
写入动作放在条件脚本里。Intouch 的条件脚本由“条件表达式 + 条件类型”驱动,条件类型选“为真时”,意思是表达式从假变真的那一瞬间执行一次脚本体。
{ 条件脚本:$Second == 3,条件类型"为真时" } GetNodeName(NodeName, 131); { 取本机计算机名,用于多站区分 } IF NodeName == "OP01" THEN { 只有指定的采集站才写库,避免多站重复写 } IF ($Minute >= 58) AND (WSQLS == 0) THEN GetDateTimeS = Text($Date, "yyyy-MM-dd") + " " + Text($Time, "HH:mm:ss"); SQLInsert(Connid1, "TDays", "TDays"); { 按绑定列表整组写入一行 } WSQLS = 1; { 置位:本小时已写过 } ENDIF; IF $Minute < 58 THEN WSQLS = 0; { 离开写入窗口后复位,为下一个小时做准备 } ENDIF; ENDIF;这段逻辑的核心是把“触发时机”和“写入窗口”拆成两层。$Second == 3只负责让脚本每分钟被唤醒一次,真正决定写不写的是$Minute >= 58这个窗口条件。也就是说,脚本每分钟都在跑,但只有整点前那两分钟、且本小时还没写过的时候,才真正插一行。
WSQLS的复位位置是这段脚本里最见功力的地方。如果复位写在SQLInsert后面紧跟着,那同一分钟内只要触发两次就会插两行;写在$Minute < 58的分支里,则是等整点过了、进入下一个小时的普通时段才复位,逻辑上“一小时一行”就被严格保证了。我在现场见过因为复位位置写错,一张日报表里同一个小数出现七八行的案例,查了两天才定位到这两行代码。
GetNodeName放在最外层每周期执行,是为了防止有人中途改了计算机名而脚本还拿着旧值。这个调用开销很小,没必要省。
SQLInsert的参数前面已经说过,这里再强调一次执行前提:调用它之前必须已经用SQLConnect拿到有效连接号并存进Connid1,否则函数直接返回失败,而且失败是静默的。所以工程上线前,我一般会手动在画面里点一次“测试写入”按钮,看表里有没有新行,这是最笨也最有效的验证。
4. Excel 报表端:模板页设计、VBA 引用与 ADO 取数
Excel 这一侧很多人以为就是“连上库、拉个透视表”,实际做下来会发现坑一点不比 Intouch 少。核心原因是 Excel 是给办公场景设计的,它的日期格式、宏安全策略、ADO 版本都和工控现场的习惯对不上。
4.1 三个页面的职责划分:控制面板、参数日报、数据区
模板我一般拆成三个工作表页,职责严格分开:
| 页面 | 职责 | 谁用 |
|---|---|---|
| 控制面板 | 选起止日期、选班次、点“生成报表”、显示查询状态 | 操作员/班长 |
| 参数日报 | 最终呈现的报表页,固定表头、固定打印区域 | 打印/归档 |
| 数据区 | 承接查询结果,隐藏或半隐藏,供报表页引用 | 不直接使用 |
“控制面板”上只放最少的输入控件:两个日期选择器、一个班次下拉、一个按钮、一个状态标签。状态标签用来显示“查询中 / 共 N 行 / 连接失败”,这是给现场人员的唯一反馈,没有它,按钮点下去没反应就会被当成程序坏了。
“数据区”单独一页的意义在于解耦。查询结果先落到数据区,报表页用公式引用数据区,这样即使查询结果行数变化,报表页的格式、打印区域、汇总公式都不用动。把查询结果直接写到报表页,行数一变格式全乱,这是血泪经验。
4.2 VBA 工程引用与 ADO 连接
模板建好之后,在“参数日报”页签上右键 →“查看代码”,进入 VBA 编辑器。第一步不是写代码,是勾引用:工具 → 引用,至少勾上Microsoft ActiveX Data Objects 2.8 Library(或者更高版本)、Microsoft Excel 16.0 Object Library、Microsoft Office 16.0 Object Library。少勾 ADO 那一项,代码写到CreateObject("ADODB.Connection")那行就会报“要求对象”,新手很容易在这里卡半天。
连接封装成一个函数,方便所有查询复用:
Option Explicit Private Function OpenConn() As Object Dim conn As Object Set conn = CreateObject("ADODB.Connection") ' 用只读账号连库,与 Intouch 侧写账号分开 conn.ConnectionString = "Provider=SQLOLEDB;Data Source=OP01;Initial Catalog=PlantDB;" & _ "User ID=scada_r;Password=YourPass;" conn.CommandTimeout = 30 ' 数据量大时给足超时,默认 30 秒有时不够 conn.Open Set OpenConn = conn End FunctionCommandTimeout这一行是我加上去的。数据量上万行之后,默认超时会让查询中断,报一个很模糊的“一般性网络错误”,实际只是超时。设成 30 秒之后这类假故障基本消失。
报表端用只读账号,不只是权限规范,还有个实际好处:万一 VBA 里写错了 SQL 语句,也不会误伤生产数据。
4.3 按时间段查询与刷新:SQL 拼接与日期格式
查询逻辑写在按钮的点击事件里,核心是把两个日期拼成 WHERE 条件:
Public Sub QueryDaily(ByVal dStart As Date, ByVal dEnd As Date) Dim conn As Object, rs As Object, sql As String Dim r As Long, c As Long Set conn = OpenConn() ' 日期统一用 ISO 格式拼,避免区域设置把 2024/1/5 解析成 5 月 1 日 sql = "SELECT RecTime, NodeName, P01, T01, F01, BatchNo FROM dbo.TDays " & _ "WHERE RecTime >= '" & Format(dStart, "yyyy-mm-dd hh:nn:ss") & "' " & _ "AND RecTime < '" & Format(dEnd, "yyyy-mm-dd hh:nn:ss") & "' " & _ "ORDER BY RecTime" Set rs = conn.Execute(sql) ' 结果落到数据区,从第 2 行开始写,第 1 行留表头 ThisWorkbook.Worksheets("数据区").Cells.ClearContents For c = 0 To rs.Fields.Count - 1 ThisWorkbook.Worksheets("数据区").Cells(1, c + 1).Value = rs.Fields(c).Name Next c r = 2 Do While Not rs.EOF For c = 0 To rs.Fields.Count - 1 ThisWorkbook.Worksheets("数据区").Cells(r, c + 1).Value = rs.Fields(c).Value Next c r = r + 1 rs.MoveNext Loop rs.Close: Set rs = Nothing conn.Close: Set conn = Nothing ThisWorkbook.Worksheets("控制面板").Range("B2").Value = "共 " & (r - 2) & " 行" End Sub这里有两个非直觉点。一是Format的分钟占位符是nn不是mm,mm在 VBA 里是月份,写成hh:mm:ss会把分钟挤成月份,拼出来的时间是错的,查询结果自然不对。二是时间范围用>=和<而不是BETWEEN,因为BETWEEN是闭区间,结束时间那一秒的数据会被重复计入下一天,边界日的数据对不上账。
写完数据区之后,报表页用引用公式(比如INDEX/MATCH,或者直接引用区域)来拼报表,这部分用 Excel 自身的函数能力就够了,不需要再写 VBA。查询和呈现分开,改报表格式的时候不用碰代码,这是我最满意的一处设计。
5. 避坑与排查:这套落库链路最常翻车的 5 个点
现场跑过的项目里,真正因为“脚本写不出来”失败的极少,绝大多数问题集中在下面这几处。每一条我都按现象、原因、解决三段记,照着对号入座比翻文档快。
第一坑:条件脚本被当成定时器,写入频率完全不对。
现象是表里的记录密度和预期差一个数量级,本来想做小时数据,结果一分钟一条。原因是把$Second == 5理解成了“每 5 秒执行一次”。实际上$Second是 0 到 59 的秒值,$Second == 5只会在每分钟的第 5 秒为真,也就是每分钟执行一次。解决办法是先想清楚要什么频率:每分钟一次就写$Second == 5;每 5 秒一次就写($Second MOD 5) == 0,同时配合WSQLS这类标志位把单次动作锁住,避免同一个秒值内被多次触发。
第二坑:日期时间格式不匹配,写入的 RecTime 变成乱值或者直接失败。
现象是表里RecTime列出现1900-01-01或者 NULL。原因是操作系统的日期时间格式是yyyy/M/d之类的区域设置,Intouch 侧用Text($Date, ...)转字符串时格式跟着系统走,而 SQL Server 期望的是明确的 ISO 格式,两者对不上就解析失败或者解析成错误日期。解决办法是在操作系统“日期和时间”设置里把短日期改成yyyy-MM-dd,长日期和短时间也统一;更稳妥的做法是在脚本里显式用格式化函数输出yyyy-MM-dd HH:mm:ss,不依赖系统默认格式。
第三坑:绑定列表和表列对不上,SQLInsert 静默失败。
现象是脚本执行了、连接号也正常,但表里一行都没有。原因通常是绑定列表里少放或多放了一个变量,或者变量类型和列类型不兼容(比如把实型变量绑到NVARCHAR列上)。SQLInsert失败不抛异常,只是返回一个失败码,而很多人不接收返回值。解决办法是给SQLInsert的返回值赋一个临时标记,在画面或日志里显示出来;同时在改表结构之后,逐列核对绑定列表,顺序、名称、类型三项一起对。
第四坑:连接号有效但连接实际已断,写库静默停止。
现象是白天一切正常,某次网络抖动或者数据库重启之后,数据就不写了,界面上也没有任何提示。原因是Connid1保存的是建立连接时的返回值,链路断了它不会自动变成负数,脚本依然认为自己是“已连接”状态。解决办法是加一个心跳逻辑:定时用SQLSelect做一次极轻量的查询,失败就置Connid1 = -1并尝试重连;同时在画面上做连接状态灯,让操作员能看见。
第五坑:Excel 模板换了机器就打不开宏,报表一片空白。
现象是模板在工程师电脑上好好的,拷到操作员电脑上按钮点不动。原因是 Excel 的宏安全策略,来自网络位置的 .xlsm 文件会被静默禁用宏,或者被标记为“受保护的视图”。解决办法是把模板放到一个受信任位置(Excel 选项 → 信任中心 → 受信任位置里加共享模板目录),并且把文件属性里的“解除锁定”勾上;发布时统一用受信任位置分发,不要让大家从邮件附件里点开。另外提醒一句,改完 VBA 之后一定要在目标机器上实测一次完整流程,工程师站能跑不等于现场机能跑。
6. 二次开发:把一次性交付改造成可复制模板
做过两三个同类项目之后,最省时间的做法不是把脚本写得更巧,而是把它拆成“可配置的部分”和“不变的部分”。这套东西里不变的部分是:连接串的五段结构、绑定列表的映射规则、WSQLS的防重写逻辑、Excel 的三个页签职责划分。可变的部分是:库地址、库名、账号、表名、采集站名、采样频率、报表列。把可变项全部提到一处,后面每上一个新现场,改的只是那一处。
我一般的做法是在 Intouch 里建一组“配置标记”,用内存消息类型,值写在初始化脚本里或者从一个文本文件读进来:
{ 应用程序脚本 - 启动时:所有可变项集中在这里 } CfgDbHost = "OP01"; { 库服务器计算机名 } CfgDbName = "PlantDB"; { 库名 } CfgDbUser = "scada_w"; { 写库账号 } CfgDbPass = "YourPass"; { 密码,工程目录权限必须收紧 } CfgStation = "OP01"; { 本站计算机名,用于写入判断 } Connid1 = SQLConnect("provider=sqloledb;Data Source=" + CfgDbHost + _ ";Initial Catalog=" + CfgDbName + _ ";UserID=" + CfgDbUser + _ ";Password=" + CfgDbPass + ";");这样一来,换现场只改这五行,SQLConnect那一长串不用每次重新拼,也就不容易拼错分号或者漏掉provider。同理,条件脚本里的NodeName == "OP01"换成NodeName == CfgStation,多站部署时只改配置不改逻辑。
Excel 端做同样的事:把库地址、库名、账号、表名放到“控制面板”页的一组单元格里,VBA 从单元格读值拼连接串和 SQL。这样交付时运维自己就能改,不用每次都找开发。
| 可变项 | Intouch 侧位置 | Excel 侧位置 |
|---|---|---|
| 库服务器 | CfgDbHost | 控制面板 B5 |
| 库名 | CfgDbName | 控制面板 B6 |
| 账号/密码 | CfgDbUser / CfgDbPass | 控制面板 B7 / B8 |
| 表名 | SQLInsert 第二参数 | 控制面板 B9 |
| 采样频率 | 条件脚本表达式 | 不涉及 |
验证方法上,我习惯在交付前跑一遍“三查”:一查连接,在 Intouch 画面点测试按钮,看Connid1是否为正、日志里有没有失败记录;二查数据,手动触发一次写入,直接去 SSMS 里SELECT TOP 10 * FROM TDays ORDER BY RecID DESC,看时间、站名、各参数是否对得上;三查报表,在目标操作员机器上打开 Excel 模板,选一个已知有数据的日期区间,确认行数、汇总值和库里一致。这三步走完,七八成的现场问题能在交付前拦住。
还有一个技巧值得单独说:给数据库表加一个“来源标记”列,值由脚本写入(比如SRC='INT'),Excel 端每次查询前先跑一句SELECT COUNT(*) FROM TDays WHERE SRC <> 'INT',如果结果不为 0,说明有人手动往表里插过数据,报表口径可能被污染。这行检查几乎零成本,但能在数据对不上账的时候第一时间排除掉“人为干预”这个方向,省下的时间远超写它的成本。
从那以后我每次交付这类 Intouch 落库项目,都会强制自己走一遍“断开连接再重连”的演练——把网线拔了再插上、把数据库服务重启一次,看脚本能不能自己恢复。凡是没做这个演练就上线的,基本都在半夜被叫起来过。希望帮到你。
本文还有配套的精品资源,点击获取