资讯动态

WINCC中基于VBScript与SQL的报表查询实现

发布时间:2026/10/30 3:00:09 来源:尧图企业网站定制
简介面向SIMATIC WinCC组态开发人员与工业自动化数据报表学习者这份文档围绕VBScript与ActiveX控件实现SQL报表查询展开覆盖从SQL Server 2005建库建表、WinCC全局脚本读写数据库到日报表画面组态、查询与打印脚本编写的完整知识链路适合具备WinCC基础、希望补齐数据库报表能力的中级读者。包内仅含1个doc文件压缩包约1.11MB以图文步骤和附录VBS代码示例组织内容便于对照工程实践查阅。目前已有1682人学习。读者可从中获取MSFlexGrid与MS DTPicker控件的注册配置要点、按日期区间查询SQL记录的脚本思路以及将表格数据转存Excel并打印的实现参考对搭建WinCC生产数据日报、理解变量入库与报表展示的衔接具有直接借鉴价值。1. 班组长要的那张日报WINCC 里的 VBS 脚本 SQL 直连能补上什么凌晨两点交接班班组长要前一天的设备占用率日报值班的人还在 WINCC 画面里一页页翻历史趋势手抄进 Excel 再排版。这份《基于 VBScript 和控件实现 WINCC SQL 报表查询功能》针对的就是这个环节用 VBS 脚本把 WINCC 内部变量按周期写进 SQL Server 的报表表画面里挂上 MSFlexGrid 和 MS DTPicker 两个 ActiveX 控件选日期、点查询、点打印数据直接落到 Excel 表格里走打印。整条链路不引额外报表服务器全局脚本负责写、画面脚本负责读改需求就是在项目里改几行代码。适合已经在维护 WINCC 项目、想自己补一块历史数据查询能力的人也适合刚接手旧项目、需要先把这套控件体系跑通的新手。下面按建库、组态、查询脚本、排错、打印的顺序拆。2. 建库与写库脚本time/cpu/ram/hd 四列宽表怎么定、全局脚本怎么落这套方案的数据底座很朴素一张宽表、四个字段。选型上稍微走岔一步后面的查询和打印都会跟着别扭。常见做法是照着概要先把表建完就收工等数据攒到几十万行、查一个月日报要转十来秒才回头补索引和字段类型那时候改起来就得停机迁数据了。2.1 表结构与字段类型先把 datetime 和浮点定准建库这一步原文写得很直白打开 SQL Server Management Studio 连上实例在 Databases 上右键 New Database名字填一个项目用的库名原文示例是SQL-Report然后在 Tables 上右键 New Table逐个填 Column Name 和 Data Type保存时给表名。字段一共四个我一般把类型定成这样列名类型说明备注timedatetime采集时刻写库时取 Now建议写成[time]避免和类型名撞cpufloatCPU 占用百分比0–100允许小数ramfloat内存占用百分比同上hdfloat硬盘占用百分比同上注意是占用率不是剩余量time这个列名在 T-SQL 里虽然能直接用但和系统类型名同名写INSERT INTO VBSReport(time,...)多数情况能过换成别的数据库或者加计算列时容易报错加方括号最省事。类型上不推荐用varchar存时间看着省事实际上在BETWEEN比较时会按字符串比2024-9-5和2024-09-05排出来的顺序不对日报直接错行。用datetime还有一个好处写脚本时不用管秒和毫秒比较运算符天然支持。建完表立刻在time上建一个非聚集索引。理由很实际——查询脚本的WHERE条件永远只落在时间区间上数据量上来之后有没有这个索引差别就是几百毫秒和几秒。索引建在 SQL 端和 WINCC 侧没关系不会影响画面响应。提示建表时把 cpu/ram/hd 的默认值设成 0 或者留 NULL 都行但写库脚本里三个变量读失败时返回的是空字符串插进去会变成 0报表上就是一串平线。读变量那一步要做空值判断别指望数据库兜底。2.2 写库全局脚本连接串、INSERT 与日期格式原文附录里那段action函数是整套东西的入口放在全局脚本里由触发器周期调用。核心就三步读变量、连库、拼 INSERT。真正需要动脑的是连接串和日期格式这两处。Option Explicit Function action Dim a, b, c, d, ts Dim strcn, cn, is_SQL a Now 采集时刻 b HMIRuntime.Tags(cpu).Read 三个内部变量同步读取 c HMIRuntime.Tags(ram).Read d HMIRuntime.Tags(hd).Read 不走格式本地化手工补零拼成 ISO 风格SQL 端一定认得 ts Year(a) - Right(0 Month(a), 2) - Right(0 Day(a), 2) _ Right(0 Hour(a), 2) : Right(0 Minute(a), 2) : Right(0 Second(a), 2) Integrated SecuritySSPI 走 Windows 身份验证不落账号密码在脚本里 strcn ProviderSQLOLEDB.1;Integrated SecuritySSPI;Persist Security InfoFalse; _ Initial CatalogSQL-Report;Data Source.\wincc Set cn CreateObject(ADODB.Connection) cn.ConnectionString strcn cn.Open is_SQL INSERT INTO VBSReport([time], cpu, ram, hd) VALUES ( ts , _ b , c , d ) cn.Execute is_SQL cn.Close Set cn Nothing End Function参数逐个说一下。ProviderSQLOLEDB.1是 SQL Server 的 OLE DB 提供程序WINCC 这种老版本环境上最稳的一档Integrated SecuritySSPI表示用当前 Windows 账户认证全局脚本跑在 WINCC 运行态的服务账户下所以这个账户要在 SQL 实例上有对应登录名和目标库的db_datawriter权限否则报“登录失败”。Data Source.\wincc里的点表示本机后面跟的是实例名——WINCC 自带安装的 SQL 实例名通常是固定的实际项目里按本机SELECT SERVERNAME的结果改。Initial Catalog就是目标库名。把时间手工补零这件事是这套脚本里最容易被忽略的地方。VBScript 的Month()、Day()返回的是不带前导零的数字直接拼出来是2024-9-5 8:3:7SQL Server 在部分语言设置下能认在另外一些设置下会当成非法日期直接抛错或者更糟——被解析成2024-09-05之外的日子。踩过一次之后我在这类脚本里一律手工补零。2.3 触发条件周期、变量触发和写库频率的取舍全局脚本写好了不会自己跑得挂触发器。WINCC 的全局脚本触发器常见有三类定时器按固定周期、变量触发某个变量变化时、事件触发。写历史数据用定时器最直接周期填多少要看两个约束。一是数据密度。日报表按天聚合一分钟一条就是 1440 行一个月四万多行如果只是看趋势五分钟一条、一天 288 行就够查询和导出的速度都会好很多。二是变量读取的代价。HMIRuntime.Tags(cpu).Read是同步调用一次读三个变量就是三次往返周期压到一秒以内脚本本身的执行时间会开始挤占运行态的时间片画面切页会肉眼可见地卡。我一般把周期放在 30 秒到 5 分钟之间看工艺节奏定。还有一个容易翻车的地方触发器配了周期但脚本函数名必须和触发配置里调用的名字一致且函数要放在“项目模块”而不是“标准模块”之外的位置时注意作用域。原文特意标了一句“注意全局脚本触发条件”说明当时就是在这上面卡过。判断脚本有没有真的跑起来最快的办法是去 SSMS 里SELECT TOP 10 * FROM VBSReport ORDER BY [time] DESC看最新一条的时间戳是不是刚刚——画面上的报表是查出来的库里有数据才有得查链路排查永远从写库这一端开始。3. 画面组态与两个 ActiveX 控件MSFlexGrid 和 MS DTPicker 怎么挂上去写库通了接下来是给人看的那一半。新建 WINCC 项目、建变量、建主画面这些是常规操作真正耗时间的是把 MSFlexGrid 和 MS DTPicker 这两个 ActiveX 控件塞进画面并且让它们听话。这两个控件都不在 WINCC 自带控件库里属于 VB6 时代的遗留物能不能用、怎么注册取决于运行这台机器的系统状态。3.1 控件注册regsvr32 与 MSFLXGRD.OCX在画面里右键 ActiveX Controls选 Add/Remove从弹出的列表里勾上Microsoft FlexGrid Control, Version 6.0和Microsoft Date and Time Picker Control 6.0 (SP4)。列表里如果没有说明这台机器还没注册。原文给的处理路径是装了 VB6 开发环境的机器可以直接选因为安装时已经把控件注册进系统没装的把MSFLXGRD.OCX拷到系统目录然后在 Windows 的“开始 → 运行”里执行注册命令regsvr32 MSFLXGRD.OCX regsvr32 /u MSFLXGRD.OCX第一条是注册第二条/u是反注册卸载或换版本的时候用。执行完会弹一个对话框成功会明确告诉你注册成功如果提示“找不到指定的模块”或者“模块加载失败”基本是两个原因——文件没放到系统目录或者放的是 64 位系统下的错误目录。WINCC 这类组态软件运行在 32 位进程里控件必须放在 32 位系统目录C:\WINDOWS\system32并对 32 位注册器执行命令别把文件丢进SysWOW64就以为完事路径对不上注册一定失败。# 32 位系统目录下执行注册成功会弹确认框 cd C:\WINDOWS\system32 regsvr32 MSFLXGRD.OCX注册完成后回到画面编辑器重新点一次 Add/Remove列表里就应该出现这两个控件了。拖进画面后在属性里改控件名——脚本里通过ScreenItems(MSFlexGrid1)和ScreenItems(Date1)拿对象引用控件名和脚本里的字符串必须严格一致改名字就得同步改脚本这是最没技术含量但也最常见的“查询没反应”。注意控件注册是机器级操作换一台运行机就得重来一遍。项目交付时把 OCX 文件、注册命令和依赖说明一起打包比口头交代一句“装一下控件”靠谱得多。3.2 DTPicker 的取值与日期区间拼装日报表画面的逻辑起点是 DTPicker用户选起始日和结束日脚本把两个控件的Value取出来拼成 SQL 能用的时间边界。原文里用Year()、Month()、Day()三段拼字符串思路是对的坑在补零和边界。Dim Date1, Date2 Dim By, Bm, Bd, Ny, Nm, Nd Dim BeginDate, EndDate Set Date1 ScreenItems(Date1) 起始日期控件 Set Date2 ScreenItems(Date2) 结束日期控件 By Year(Date1.Value) Bm Month(Date1.Value) Bd Day(Date1.Value) Ny Year(Date2.Value) Nm Month(Date2.Value) Nd Day(Date2.Value) 手工补零避免 2024-9-5 这种写法被 SQL 误解析 BeginDate By - Right(0 Bm, 2) - Right(0 Bd, 2) 00:00:00 EndDate Ny - Right(0 Nm, 2) - Right(0 Nd, 2) 23:59:59Date1.Value返回的是一个完整的日期时间值DTPicker 默认带时间部分但用户只关心日期所以起始日固定补00:00:00、结束日固定补23:59:59用闭区间把当天的数据全兜进来。这里的23:59:59有个精度边界——如果time列存的是datetime类型理论精度到 3.33 毫秒23:59:59.997这类值会落在区间外。稳妥的写法是结束边界用下一天的00:00:00配比较或者写23:59:59.997。日报表这种场景丢几毫秒不影响结论但如果哪天要做秒级对账这个边界一定会出来咬人。日期先后顺序的校验在原文里有了但那行逻辑判断的运算符优先级有隐患 原文写法And 的优先级高于 Or不加括号时判断结果可能不符合预期 If By Ny Or By Ny And Bm Nm Or By Ny And Bm Nm And Bd Nd Then MsgBox 输入的时间不正确, vbOK, 错误的起始时间 End If 我一般改成这样把每组条件用括号包起来 If (By Ny) Or (By Ny And Bm Nm) Or (By Ny And Bm Nm And Bd Nd) Then MsgBox 输入的时间不正确, vbOK, 错误的起始时间 Exit Sub End If顺带加了Exit Sub。原来的写法只弹框不中断用户看到提示之后脚本继续往下跑照样去查一个反的区间结果为空还得再弹一次“没有找到符合条件的数据”。两次弹框用户会以为是两个不同的错。3.3 MSFlexGrid 的行列、合并与列宽设置MSFlexGrid 的配置看着琐碎其实就三件事给够行数、定好列宽、把表头和数据落到对的格子里。它是 0 基索引的TextMatrix(row, col)第一个参数是行、第二个是列第 0 行留给合并后的标题第 1 行放列名第 2 行开始才是数据。Set MSFlexGrid1 ScreenItems(MSFlexGrid1) MSFlexGrid1.Clear MSFlexGrid1.Cols 5 数据行 标题行 列名行 表尾 MAX/MIN/AVG 三行 余量 MSFlexGrid1.Rows oRs.RecordCount 6 MSFlexGrid1.ColWidth(0) 900 MSFlexGrid1.ColWidth(1) 1900 MSFlexGrid1.ColWidth(2) 1000 MSFlexGrid1.ColWidth(3) 1000 MSFlexGrid1.ColWidth(4) 1100 合并第 0 行的 0~4 列做整行标题 MSFlexGrid1.MergeCells 4 MSFlexGrid1.MergeRow(0) True MSFlexGrid1.TextMatrix(1, 0) NO. MSFlexGrid1.TextMatrix(1, 1) Time MSFlexGrid1.TextMatrix(1, 2) CPU% MSFlexGrid1.TextMatrix(1, 3) RAM% MSFlexGrid1.TextMatrix(1, 4) HardDisk%行数这里6不是拍脑袋1 行标题、1 行列名、N 行数据、3 行表尾统计再加 1 行余量。少一行表尾的 AVG 就写不进去TextMatrix赋值的行号超界时会静默失败或者报错具体表现取决于版本反正不是好事。MergeCells 4是允许按行合并MergeRow(0) True指定第 0 行合并两行必须配着写只写一行不生效。列宽的单位是 twip不是像素1000 左右大概是能放下四个汉字的宽度。时间列给 1900 是因为要显示yyyy-mm-dd hh:mm:ss短了会被截断成####。想让内容自适应也可以用AllowUserResizing让操作工自己拖但报表画面一般是要打印的固定列宽反而更稳。4. 查询按钮的 VBS 脚本一条明细 SQL 加一条聚合 SQL“查询”按钮的点击脚本是整套东西里最长的一段动辄上百行。抛开变量声明逻辑其实是两条 SQL 加一次填表一条查明细一条算最大值、最小值、平均值。分开查而不是在明细里算是因为聚合结果只需要一行混在游标里循环累加既慢又容易算错。4.1 明细查询CONVERT 格式化时间与 BETWEEN 区间Dim Sql, Sql1 Sql SELECT CONVERT(char(19), [time], 20) AS shijian, cpu, ram, hd _ FROM VBSReport WHERE [time] BETWEEN BeginDate AND EndDate _ ORDER BY [time]CONVERT(char(19), [time], 20)里的样式 20 是 ODBC 标准格式yyyy-mm-dd hh:mm:ss正好 19 个字符。好处在于返回值已经是字符串VBS 侧拿到就能直接往格子里塞不用再处理日期对象的本地化显示。如果直接SELECT [time]Recordset 的字段值是日期类型转成字符串时会按运行机的区域设置走同一条数据在不同机器上显示成2024/9/5或者9/5/2024都有可能。BETWEEN ... AND ...是闭区间两头都包含配合前面拼好的00:00:00和23:59:59正好覆盖一整天。ORDER BY [time]必须加没有排序的SELECT返回顺序依赖存储引擎的物理分布同一天的数据可能乱序出现报表上看着就是时间跳来跳去。4.2 聚合查询MAX/MIN/AVG 一次取回Sql1 SELECT AVG(cpu) AS ylp, AVG(ram) AS wdp, AVG(hd) AS llp, _ MIN(cpu) AS ylx, MIN(ram) AS wdx, MIN(hd) AS llx, _ MAX(cpu) AS yld, MAX(ram) AS wdd, MAX(hd) AS lld _ FROM VBSReport WHERE [time] BETWEEN BeginDate AND EndDate 一条语句把九个聚合值全拿回来比跑三次SELECT快也少两次连接往返。别名起得短是为了后面赋值方便oRs1(ylp)这样的写法比oRs1.Fields(0).Value可读性高一些改字段顺序的时候也不会错位。原文里这段赋值写在循环内部每循环一行就把九个聚合值重写一遍。功能上没问题但属于典型的“能跑但没必要”——把聚合值的赋值挪到循环外面只写一次逻辑更清楚。填表尾的行号用oRs.RecordCount 3、4、5对应前面6的行数分配这几个数字必须联动修改动一处就得全动。4.3 空结果、RecordCount 与游标类型Set conn CreateObject(ADODB.Connection) conn.ConnectionString strcn conn.CursorLocation 3 adUseClient客户端游标才能稳定拿到 RecordCount conn.Open Set oCom CreateObject(ADODB.Command) oCom.CommandType 1 adCmdText Set oCom.ActiveConnection conn oCom.CommandText Sql Set oRs oCom.Execute n oRs.RecordCountCursorLocation 3这行很关键。默认的服务端游标adUseServer在结果集上拿RecordCount经常返回-1因为驱动不知道总数要等你一条条走完才有准数。客户端游标会把结果先拉到本地RecordCount一次就准。代价是内存报表场景一天几千行完全不是问题。拿到n之后先判断If n 0 Then弹框提示并Exit Sub不要让它继续往下填表。原文的写法是先弹框再继续执行后面那段填表代码在空结果集上跑Rows 0 6还是能设但列名和表尾统计会填进一堆空值用户看到的是一个只有表头的空壳比直接提示“没数据”更让人困惑。还有一个隐藏问题oRs.Requery原文里在填表前调了一次。Requery会重新执行一遍 SQL等于同样的查询跑两次在数据量大的时候会明显加长点击到出结果的等待。除非有“表结构可能变化”这类特殊需求这一行可以直接删掉。5. 排查清单五个在现场反复出现的坑这套链路涉及 SQL、VBS、ActiveX 三层出错的时候现象往往指向错误的地方。下面五条是这类项目里反复遇到的按现象、原因、解决三段记下来下次遇到能省不少时间。5.1 画面与控件类现象点 Add/Remove 找不到 MSFlexGrid或者拖进画面后显示成一个空白方块运行时点查询直接报“对象不支持该属性或方法”。原因控件没注册或者注册了但版本不匹配。装了 VB6 的机器上同时存在多个版本勾选时选错版本号属性名就会对不上。解决先在系统目录确认MSFLXGRD.OCX存在用regsvr32重新注册一次注册成功后关闭画面编辑器再打开。仍然不行就看控件属性里的版本号脚本里用到的MergeCells、TextMatrix这些属性在老版本上名字可能不同按实际版本改脚本别硬套。5.2 链接与数据类现象写库脚本跑着不报错但去 SSMS 里查表一条数据都没有。原因触发器没挂上或者脚本里连的库名、表名和实际建的不是同一个。原文里写库用的是VBSReport表、SQL-Report库而查询脚本连的是Example-report库、查的是report表两段代码如果直接混用写进去的数据查询端根本看不到。解决把写库和查询两段脚本里的Initial Catalog、表名、列名逐字对齐做成一个地方维护。全局脚本触发器的状态单独确认一遍最快的验证方式是手工在脚本编辑器里执行一次action看是否立刻多出一行。现象查询能出结果但数据比实际少一天或者某个时间段的记录整段缺失。原因日期边界问题。结束日拼成23:59:59丢掉了毫秒部分或者月份没补零2024-9-5在某些语言设置下被解析成了别的日期。解决结束边界改成次日00:00:00配比较或者直接写23:59:59.997拼接日期统一用Right(0 月份, 2)补零。改完拿一个已知有数据的日期做对照比对着 SSMS 里直接查的结果看行数是否一致。5.3 运行环境与导出类现象点“打印”之后 Excel 起来了表格也填进去了但关闭画面后任务管理器里残留一堆EXCEL.EXE机器越跑越慢。原因脚本里ExcelApp.Quit没执行或者执行顺序不对——先Quit再Close对象引用没释放进程就挂在后台。原文里ExcelBook.Close和ExcelApp.Quit是注释掉的调试阶段没问题上线必须打开。解决按Workbook.Close→Application.Quit→ 置Nothing的顺序收尾中间任何一步抛异常都要保证后面的清理能走到。稳妥的写法是把清理放在一段独立的错误处理里先释放对象再退出。现象脚本在工程师电脑上跑得好好的装到现场运行机上就报“未找到提供程序”或者“登录失败”。原因运行机没装对应的 OLE DB 提供程序或者 WINCC 运行态使用的 Windows 账户在 SQL 实例上没有登录名。Integrated SecuritySSPI是拿进程身份去认证的工程师用管理员账户调试运行态换成受限账户权限就断了。解决在运行机上确认提供程序存在在 SQL 实例的安全节点下给运行账户建登录名并授予目标库的读写权限。这一步最好在项目初期就确认别等交付前一天才发现。6. 打印导出把 MSFlexGrid 搬进 Excel 的收尾技巧打印按钮的脚本本质是一次表格搬运遍历 MSFlexGrid 的每个单元格Trim之后写进 Excel 对应位置再统一排版。这段代码不长但排版参数特别容易调半天下面几个是我常用的取值。Set MSFlexGrid1 ScreenItems(MSFlexGrid1) Set ExcelApp CreateObject(Excel.Application) Set ExcelBook ExcelApp.Workbooks.Add Set ExcelSheet ExcelBook.Worksheets(1) ExcelApp.Visible True 逐格搬运Trim 掉控件里的占位空格 For irow 0 To MSFlexGrid1.Rows - 1 For icol 0 To MSFlexGrid1.Cols - 1 ExcelSheet.Cells(irow 1, icol 1) Trim(MSFlexGrid1.TextMatrix(irow, icol)) Next Next z MSFlexGrid1.Rows ExcelSheet.Range(A1:E1).Merge ExcelSheet.Range(A1:E z).Borders(1).Weight 2 四周加边框 ExcelSheet.Range(A1:E z).Borders(2).Weight 2 ExcelSheet.Range(A1:E z).Borders(3).Weight 2 ExcelSheet.Range(A1:E z).Borders(4).Weight 2 ExcelSheet.Rows(1).RowHeight 0.75 / 0.035 磅换算成行高 ExcelSheet.Rows(1).Font.Name 宋体 ExcelSheet.Rows(1).Font.Bold True ExcelSheet.Rows(1).Font.Size 10 ExcelSheet.Cells.HorizontalAlignment 3 居中 ExcelSheet.Cells.EntireColumn.AutoFit ExcelSheet.PrintPreview 调试期先看分页 ExcelSheet.PrintOut 上线后换成直接打印 ExcelBook.Close ExcelApp.Quit Set ExcelSheet Nothing Set ExcelBook Nothing Set ExcelApp NothingRowHeight 0.75 / 0.035这个除法是在做单位换算把厘米换算成 Excel 的行高单位磅0.75 厘米大概是打印出来比较舒服的表头高度。AutoFit要在所有内容写完之后调否则宽度按空单元格算导出来全是窄条。调试期用PrintPreview上线再换PrintOut这个切换我一般做成脚本里注释掉一行、放开另一行交付时在说明里写清楚改哪一行。最后一次翻车是这样一个细节表格搬运时没TrimMSFlexGrid 里填充的空格被原样带进 Excel看着没内容导出后列宽被撑得很怪AutoFit也跟着算错。从那以后我每次做这类导出都强制先走一遍PrintPreview看分页和列宽确认没问题再换成直接打印多花十秒钟省掉一次现场返工。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑