ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

WinCC SQL报表查询:VBScript+ActiveX控件实战指南

WinCC SQL报表查询:VBScript+ActiveX控件实战指南

简介:这是一份面向工业自动化人机界面开发者的WINCC报表查询学习文档,聚焦用VBScript配合MSFlexGrid与MS DTPicker控件完成SQL数据库查询、表格展示和报表打印,适合需要补齐WinCC全局脚本与SQL Server数据交互能力的工程人员。文档围绕数据库建库建表、WinCC变量设置与主画面及日报表画面组态展开,讲解按日期选择后查询记录、将结果读入表格控件,以及把表格内容转存Excel并打印的实现思路;内容还涉及ActiveX控件选用与注册、日期起止校验等工程细节,并对报表界面交互、历史变量查询与打印输出形成较完整的示例。附录给出写入数据库和查询记录的VBS代码示例,便于对照理解数据采集、存储、查询到报表输出的完整链路,可作为项目原型搭建的参考资料。包内共1个doc文件,压缩包约1.11MB,内容集中,适合按主题查阅。已有1682人学习下载,对WinCC报表功能开发与调试具有参考价值。

1. WinCC 里做 SQL 报表查询:老组合还能不能落地

在 WinCC 里做 SQL 报表查询,绕不开 VBScript 和 ActiveX 控件这套老组合:DTPicker 选日期,MSFlexGrid 显示结果,VBS 脚本负责把 SQL 结果集灌进表格。这份《基于 VBScript 和控件实现 WINCC SQL 报表查询功能》的 .doc,把建库、全局脚本写入、日报表画面组态、查询和打印串成了一条线,适合还在维护老 WinCC 项目、需要按天出 CPU/内存/硬盘统计报表的从业者。它不是新框架,胜在链条完整:从 SQL Server 2005 建表开始,到 MSFlexGrid 合并表头、Excel 打印预览,每一步都有脚本可抄。下面按我实际复现的顺序拆,先定表结构,再写全局脚本,最后把画面和查询按钮接上。

2. 数据库与表结构:四列定死,时间字段别留坑

2.1 为什么独立 SQL 表比 WinCC 归档更适合做日报

WinCC 自带的变量归档适合看趋势、做曲线,但日报表要的是“按日期区间取数、算平均值/最大值/最小值、再导到 Excel 打印”。这类需求用独立 SQL 表更直接:VBS 往表里插一行,查询按钮按时间范围捞一组记录,统计 SQL 一条语句就能把 avg/min/max 算完。老项目里常见做法是新建一个报表库,和 WinCC 运行库分开,日报查询再重也不至于把画面切换拖慢。

独立表的代价是维护:数据量大了要加索引,历史数据要定期清理,写入脚本要保证时间字段和数值字段类型对得上。对比一下两种方式:

方式适合场景落地成本主要风险
WinCC 变量归档趋势曲线、短期追溯组态即可,脚本少按日聚合和自定义报表不灵活
独立 SQL 表日报、班报、导出 Excel要建库建表、写 VBS表结构、连接串、权限容易出问题

我一般会把报表表结构定得很窄:时间、CPU、内存、硬盘四列,够用就不再加字段。字段一多,MSFlexGrid 列宽、Excel 导出范围、统计 SQL 都要跟着改,后面维护成本会翻倍。

2.2 表结构、字段类型和索引

时间字段用datetime,不要用char或varchar存字符串。字符串看起来省事,但查询时做BETWEEN会按字典序比较,跨月跨年容易翻车;datetime能直接比较大小,也能用CONVERT(char(19), shijian, 20)格式化成yyyy-mm-dd hh:mi:ss给表格显示。

数值字段用float。CPU、内存、硬盘占用率在 WinCC 里读出来可能是整数或浮点数,统一存float省得脚本里频繁转换。表结构建议如下:

字段类型允许空说明
shijiandatetime否采集时间,写入时用Now
cpufloat否CPU 占用率
ramfloat否内存占用率
harddiskfloat否硬盘占用率

表名统一成Report,列名统一成shijian,cpu,ram,harddisk。这一步看着无聊,但后面查询脚本、统计 SQL、MSFlexGrid 表头都依赖这套名字。数据库名建议用SQLReport,不要用带连字符的SQL-Report:带连字符在 SQL 里要加方括号,在连接串里也容易写错,老项目里这种小差异经常导致“库明明建了,脚本却连不上”。

在shijian上建一个非聚集索引。日报查询基本都带时间范围,没索引时几千行还能忍,几十万行就会明显变慢。索引不要建太多,写入频繁的表上索引越多,插入越慢。

2.3 建库建表与第一条验证 SQL

在 SQL Server 管理工具里连上实例,先建库再建表。下面这段可以直接在查询窗口执行,库名、表名、列名和后面的 VBS 脚本保持一致:

-- 新建报表库,避免用带连字符的库名 CREATE DATABASE SQLReport; GO USE SQLReport; GO -- 报表表:时间 + 三个占用率 CREATE TABLE Report( shijian datetime NOT NULL, cpu float NOT NULL, ram float NOT NULL, harddisk float NOT NULL ); GO -- 日报查询按时间范围过滤,时间列建索引 CREATE INDEX IX_Report_shijian ON Report(shijian); GO

这段 SQL 的逻辑很直白:CREATE DATABASE建库,USE切库,CREATE TABLE建四列,最后在shijian上建索引。参数上唯一要改的是库名和实例名;如果现场已经有报表库,不要重复建,直接建表即可。建完先手动插一条测试数据,确认表结构没问题:

-- 手动插一条,验证字段类型和格式 INSERT INTO Report(shijian, cpu, ram, harddisk) VALUES ('2024-01-01 08:00:00', 35.5, 62.1, 48.0); -- 按时间范围查,确认能取到 SELECT CONVERT(char(19), shijian, 20) AS shijian, cpu, ram, harddisk FROM Report WHERE shijian >= '2024-01-01 00:00:00' AND shijian < '2024-01-02 00:00:00' ORDER BY shijian;

第一条INSERT用来验证列名和类型,第二条SELECT用来验证查询写法。注意这里用的是>= 当天零点和< 次日零点,不是BETWEEN ... 23:59:59。datetime有毫秒级精度,23:59:59之后、23:59:59.997之前的数据可能被漏掉,日报少几条记录往往就是这里出的问题。先把 SQL 跑通,再进 WinCC 写脚本,后面排查时才能分清是数据库问题还是画面脚本问题。

3. WinCC 全局脚本写入:连接串、触发周期和一次落库验证

3.1 全局脚本触发方式与连接串

写入脚本放在 WinCC 全局脚本的 VBS 动作里,触发方式常见两种:按周期触发,比如每 1 分钟或每 5 分钟写一次;按变量变化触发,比如 CPU 变量变化超过阈值再写。日报表一般用周期触发,周期太短数据库涨得快,周期太长统计曲线会显得稀疏。我一般先设 1 分钟,跑一天看数据量,再决定要不要放宽。

连接串是第一个容易翻车的地方。下面这几个参数要逐个对上:

参数示例值说明
ProviderSQLOLEDB.1SQL Server 的 OLE DB 提供程序
Integrated SecuritySSPI用 Windows 身份验证,不写用户名密码
Initial CatalogSQLReport数据库名,必须和建库时一致
Data Source.\wincc实例名,现场不是这个就改

Data Source=.\wincc表示本机名为wincc的实例。如果现场是默认实例,可能写成.或(local);如果是其他命名实例,就要换成实际实例名。不要照抄文档里的值就运行,先在 SQL Server 管理工具里确认实例名和库名,再改连接串。

3.2 写入脚本逐段拆解

下面这段写入脚本保留了原文的核心逻辑:读 WinCC 内部变量,拼INSERT,用 ADODB 连接执行。我加了空值判断和对象释放,避免脚本跑久了连接越积越多。

Option Explicit Function action() Dim conn, strcn, sql Dim t, cpu, ram, hd ' 读取当前时间和三个 WinCC 内部变量 t = Now cpu = HMIRuntime.Tags("cpu").Read ram = HMIRuntime.Tags("ram").Read hd = HMIRuntime.Tags("hd").Read ' 变量没读到就跳过,避免往库里写空值 If IsEmpty(cpu) Or IsEmpty(ram) Or IsEmpty(hd) Then Exit Function End If ' 连接串:库名 SQLReport,实例名按现场改 strcn = "Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=False;" & _ "Initial Catalog=SQLReport;Data Source=.\wincc" Set conn = CreateObject("ADODB.Connection") conn.ConnectionString = strcn conn.Open ' 时间加单引号,数值不加引号 sql = "INSERT INTO Report(shijian,cpu,ram,harddisk) VALUES('" & t & "'," & _ cpu & "," & ram & "," & hd & ")" conn.Execute sql conn.Close Set conn = Nothing End Function

逻辑说明:HMIRuntime.Tags("cpu").Read读的是 WinCC 内部变量,变量名要和变量管理里完全一致,大小写不要随意改。Now取的是脚本运行时刻,写入datetime字段时由 SQL Server 隐式转换。INSERT里时间字段用单引号包住,三个数值字段直接拼,因为前面已经确认不是空值。最后conn.Close和Set conn = Nothing是必须的,全局脚本周期性运行,不释放对象迟早会出问题。

参数上要注意:如果现场变量名不是cpu/ram/hd,三个HMIRuntime.Tags都要改;如果表名不是Report,INSERT INTO后面的表名也要改。脚本改完先在全局脚本编辑器里编译,不要直接切到运行环境等它自己跑。

3.3 落库验证和权限检查

脚本编译通过后,把触发周期设成 1 分钟,等两三分钟,去 SQL Server 管理工具里查:

-- 看最新写入的 10 条,确认时间和数值都正常 SELECT TOP 10 CONVERT(char(19), shijian, 20) AS shijian, cpu, ram, harddisk FROM Report ORDER BY shijian DESC;

如果一条都没有,先看全局脚本的触发器有没有启用,再看 WinCC 运行账户有没有数据库权限。Integrated Security=SSPI用的是当前 Windows 账户,WinCC 服务账户和你在管理工具里登录的账户可能不是同一个。常见做法是给运行账户在SQLReport库里建一个登录名,至少给INSERT和SELECT权限;不要图省事给sysadmin,老项目里权限放太大,后面换人维护很麻烦。

如果查出来有时间但数值是 0,先回 WinCC 变量管理里看变量是否有实时值,再检查HMIRuntime.Tags里的变量名是不是写错。写入脚本本身不复杂,难的是触发器和权限这两头,先把这两头确认了,再谈查询画面。

4. 报表画面组态:MSFlexGrid 与 DTPicker 的注册和属性

4.1 控件注册与 ActiveX 拖放

日报表画面要用两个 ActiveX 控件:MSFlexGrid显示表格,DTPicker选日期。在 WinCC 画面编辑器里选ActiveX Controls,右键Add/Remove,从列表里找MSFlexGrid Control, Version 6.0和Date and Time Picker Control 6.0 (SP4)。如果列表里没有,通常是 OCX 没注册,不是 WinCC 坏了。

MSFlexGrid 对应MSFLXGRD.OCX,DTPicker 常见对应MSCOMCT2.OCX。注册命令要用管理员权限运行:

:: 注册 MSFlexGrid 控件,路径按实际文件位置改 regsvr32 C:\Windows\SysWOW64\MSFLXGRD.OCX :: 注册 DTPicker 控件 regsvr32 C:\Windows\SysWOW64\MSCOMCT2.OCX :: 卸载时加 /u,排查冲突时用 regsvr32 /u C:\Windows\SysWOW64\MSFLXGRD.OCX

这里最容易踩的坑是位数:老机器 32 位系统常用C:\Windows\System32,64 位系统上 32 位控件通常放C:\Windows\SysWOW64,注册时也要用对应的regsvr32。如果注册成功但 WinCC 里还是看不到控件,重启画面编辑器再试;还不行就看 WinCC 是 32 位还是 64 位,别把 64 位控件往 32 位项目里塞。

4.2 画面布局与 MSFlexGrid 属性

画面布局不用花哨:上面放两个 DTPicker,分别命名Date1、Date2;中间放 MSFlexGrid,命名MSFlexGrid1;下面放“查询”和“打印”两个按钮。DTPicker 的Format设成自定义,CustomFormat写yyyy-MM-dd,这样脚本里取到的日期格式稳定,不受系统区域设置影响。

MSFlexGrid 的属性建议在画面编辑器里先设一遍,脚本里再兜底:

属性建议值作用
Cols5序号、时间、CPU、内存、硬盘
Rows20初始行数,查询时再动态改
ColWidth(0)900序号列
ColWidth(1)1900时间列
ColWidth(2)1000CPU 列
ColWidth(3)1000内存列
ColWidth(4)1100硬盘列
MergeCells4允许合并单元格
MergeRow(0)True首行标题合并
ColAlignment4列内容居中

表头可以用脚本写,也可以在属性里预置。用脚本写的好处是查询后可以按结果行数重新排。首行合并成Computer Utilization Message(per Day)这种标题,第二行写NO.、Time、CPU%、RAM%、HardDisk%,最后三行留给MAX、MIN、AVG统计。行数按rs.RecordCount + 6预留,不够就加,别让统计行被挤掉。

4.3 查询按钮脚本:从 SQL 到 MSFlexGrid

查询按钮的OnClick脚本负责三件事:取两个 DTPicker 的日期,拼时间范围,把结果集填进 MSFlexGrid。下面这段可以直接改库名和控件名后用:

Function DateStr(d) ' 固定输出 yyyy-mm-dd,避免区域设置影响 DateStr = Year(d) & "-" & Right("0" & Month(d), 2) & "-" & Right("0" & Day(d), 2) End Function Sub OnClick(ByVal Item) Dim conn, rs, cmd, sql Dim d1, d2, beginDate, endDate Dim grid, i Set d1 = ScreenItems("Date1") Set d2 = ScreenItems("Date2") ' 起始日期不能大于结束日期 If d1.Value > d2.Value Then MsgBox "起始日期不能大于结束日期", vbOKOnly, "日期错误" Exit Sub End If ' 用次日零点做上边界,避免 23:59:59 漏数据 beginDate = DateStr(d1.Value) & " 00:00:00" endDate = DateStr(DateAdd("d", 1, d2.Value)) & " 00:00:00" Dim strcn strcn = "Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=False;" & _ "Initial Catalog=SQLReport;Data Source=.\wincc" Set conn = CreateObject("ADODB.Connection") conn.ConnectionString = strcn conn.CursorLocation = 3 conn.Open sql = "SELECT CONVERT(char(19), shijian, 20) AS shijian, cpu, ram, harddisk " & _ "FROM Report WHERE shijian >= '" & beginDate & "' AND shijian < '" & endDate & "' " & _ "ORDER BY shijian" Set rs = CreateObject("ADODB.Recordset") Set cmd = CreateObject("ADODB.Command") cmd.CommandType = 1 Set cmd.ActiveConnection = conn cmd.CommandText = sql Set rs = cmd.Execute Set grid = ScreenItems("MSFlexGrid1") grid.Clear grid.Cols = 5 grid.Rows = rs.RecordCount + 6 ' 列宽和表头 grid.ColWidth(0) = 900 grid.ColWidth(1) = 1900 grid.ColWidth(2) = 1000 grid.ColWidth(3) = 1000 grid.ColWidth(4) = 1100 grid.TextMatrix(1, 0) = "NO." grid.TextMatrix(1, 1) = "Time" grid.TextMatrix(1, 2) = "CPU%" grid.TextMatrix(1, 3) = "RAM%" grid.TextMatrix(1, 4) = "HardDisk%" If rs.RecordCount = 0 Then MsgBox "没有符合条件的数据", vbOKOnly, "查询结果" rs.Close conn.Close Exit Sub End If ' 逐行填充,第一列写序号 rs.MoveFirst i = 0 Do While Not rs.EOF i = i + 1 grid.TextMatrix(i + 1, 0) = i grid.TextMatrix(i + 1, 1) = rs.Fields("shijian").Value grid.TextMatrix(i + 1, 2) = rs.Fields("cpu").Value grid.TextMatrix(i + 1, 3) = rs.Fields("ram").Value grid.TextMatrix(i + 1, 4) = rs.Fields("harddisk").Value rs.MoveNext Loop rs.Close conn.Close Set rs = Nothing Set cmd = Nothing Set conn = Nothing End Sub

逻辑说明:DateStr负责把日期拼成yyyy-mm-dd,DateAdd("d", 1, d2.Value)取结束日期的次日,查询条件用>= beginDate和< endDate。conn.CursorLocation = 3让RecordCount可用,否则有些游标类型下RecordCount返回 -1,表格行数会算错。grid.Rows = rs.RecordCount + 6预留了标题行、表头行和三行统计。TextMatrix(i + 1, 0) = i从 1 开始编号,和表头行错开。

参数上要改的是Initial Catalog、Data Source、ScreenItems("Date1")、ScreenItems("Date2")、ScreenItems("MSFlexGrid1")这几处。如果现场控件命名不是这个,脚本里全部同步改。查询 SQL 里的表名和列名也要和建表时一致,不要一边写Report,一边写VBSReport。

统计行可以再跑一条聚合 SQL,把AVG/MIN/MAX填到最后三行。常见做法是把统计 SQL 也拼成WHERE shijian >= ... AND shijian < ...,和明细查询用同一个时间边界。这样明细和统计对得上,不会出现明细 100 条、统计按 99 条算的情况。

4.4 打印按钮脚本:MSFlexGrid 转 Excel 预览

打印按钮不直接调打印机,先把 MSFlexGrid 的内容写进 Excel,再调PrintPreview。这样用户可以先看排版,确认没问题再打印。下面这段是精简后的导出脚本:

Sub OnClick(ByVal Item) Dim xlApp, xlBook, xlSheet, grid Dim r, c, rowCount Set grid = ScreenItems("MSFlexGrid1") rowCount = grid.Rows Set xlApp = CreateObject("Excel.Application") xlApp.Visible = True Set xlBook = xlApp.Workbooks.Add Set xlSheet = xlBook.Worksheets(1) ' 把 MSFlexGrid 逐格写进 Excel For r = 0 To grid.Rows - 1 For c = 0 To grid.Cols - 1 xlSheet.Cells(r + 1, c + 1) = Trim(grid.TextMatrix(r, c)) Next Next ' 边框、表头加粗、列宽自适应 xlSheet.Range("A1:E" & rowCount).Borders.Weight = 2 xlSheet.Rows(1).Font.Bold = True xlSheet.Columns.AutoFit xlSheet.PrintPreview ' 不立即 Quit,让用户看完预览自己关;要自动关就打开下面两行 ' xlBook.Close False ' xlApp.Quit Set xlSheet = Nothing Set xlBook = Nothing Set xlApp = Nothing End Sub

逻辑说明:外层循环行,内层循环列,TextMatrix(r, c)取单元格文本,Trim去掉首尾空格。Borders.Weight = 2给导出区域加边框,Rows(1).Font.Bold = True加粗首行,Columns.AutoFit让列宽跟着内容走。PrintPreview只预览不打印,用户确认后可以在 Excel 里点打印。

这里有一个取舍:xlApp.Visible = True时用户能看到 Excel 窗口,但脚本不Quit的话,每次点打印都会留一个 Excel 进程。如果改成Visible = False再自动Quit,用户看不到预览,打印按钮就变成“盲打”。我一般保留可见,让用户在预览窗口里自己关;如果现场要求自动打印,就把PrintPreview换成PrintOut,并在后面补xlBook.Close False和xlApp.Quit,同时加错误处理,避免异常时进程残留。

5. 避坑与排查:控件、库名、时间边界和 Excel 残留

5.1 控件拖不出来,ActiveX 列表里没有 MSFlexGrid

现象:在 WinCC 画面里打开ActiveX Controls的Add/Remove,列表里找不到MSFlexGrid Control, Version 6.0,或者拖到画面上提示控件未注册。

原因:MSFLXGRD.OCX没有注册,或者注册到了错误的位数目录。64 位系统上把 32 位控件放到System32再注册,WinCC 里可能还是认不到;也有情况是注册命令没用管理员权限执行,返回成功但实际没写进注册表。

解决:先确认 OCX 文件存在,32 位项目优先用C:\Windows\SysWOW64下的副本,管理员权限运行regsvr32,注册完重启画面编辑器。还不行就用regsvr32 /u卸载再重注册,别在同一个进程里反复试。DTPicker 同理,常见文件是MSCOMCT2.OCX,不要只注册 MSFlexGrid 就以为两个控件都能用。

5.2 写入有数据,查询却为空:库名、表名、列名不一致

现象:SQL Server 里能看到Report表有新记录,但画面点查询提示“没有符合条件的数据”,或者查询脚本执行时报“对象名无效”。

原因:文档附录里的写入脚本和查询脚本不是同一套命名:写入用VBSReport表、time/cpu/ram/hd列,查询用report表、shijian/cpu/ram/harddisk列,连接串里的库名也可能一个写SQL-Report、一个写Example-report。这种前后不一致在老项目里很常见,复制脚本时只改了一半。

解决:先定一套命名,建表、写入、查询、统计全部用同一套。查的时候先在 SQL Server 管理工具里跑SELECT TOP 10 * FROM Report ORDER BY shijian DESC,确认表里有数据;再把查询脚本里的FROM和WHERE列名逐个对一遍。连接串里的Initial Catalog也要和实际库名一致,带连字符的库名建议改掉,避免方括号和转义问题。

5.3 日期边界漏数据:23:59:59不是一天结束

现象:日报明明有数据,查询

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进