简介:这份文档资料聚焦CitectSCADA Reports内嵌历史数据库,面向自动化、SCADA系统集成与工业数据管理方向的工程师及技术人员,帮助理解如何借助冗余SCADA连接器与MS SQL Server 2005实现高可靠的历史数据存储与保护。资源包共1个doc文件,约1.42MB,内容围绕历史数据采集、存储、安全与接口展开,适合作为选型参考或技术培训材料。文档详细说明了逢变则存机制、100纳秒时间戳与OPC质量标识、每秒10万点变化记录能力,以及死区设置、磁盘空间计算器和性能计数器等存储管理手段;同时覆盖SQL Server安全机制、标准SQL审计、主动ETL数据交换,并列出对CitectSCADA、InTouch、Fix32、IFix及MS SQL、Oracle等系统的兼容支持。目前已有311人学习,可帮助读者快速掌握该历史库的架构特点、数据接口与报表统计能力,为工业现场数据集成与商务系统对接提供参考。
1. CITECT数据库说明.doc 到底在讲什么:从一份组态软件的数据库文档说起
如果你在工控现场干过几年,大概率见过一个场景:上位机跑着 CITECT,画面刷新正常,报警也响,但历史趋势查不到数据,或者报表导出来全是空值。这时候老师傅会翻出一个叫「CITECT数据库说明.doc」的文件,让你照着查。这份文档本质上不是 CITECT 的安装手册,也不是 SQL Server 的教程,它讲的是 CITECT 这套 SCADA 软件在运行过程中,到底把数据写到了哪里、用什么方式写、表结构长什么样、外部程序怎么读到这些数据。
CITECT 是施耐德旗下的一款老牌 SCADA 组态软件,在国内电力、水处理、冶金、市政等行业存量极大。它的数据库体系分两层:一层是 CITECT 自己的内部历史库,通常叫 Trend 或 History,存在项目目录下的 DAT 文件或专用格式里;另一层是对外开放的 SQL 接口,通过 ODBC 或直接连接 SQL Server,把实时数据、报警记录、操作日志写到关系型数据库里。这份「CITECT数据库说明.doc」大概率就是某个项目交付时,集成商写给业主或二次开发人员的交接文档,核心内容包括:数据库选型(常见是 SQL Server 2008 R2 到 2019)、表结构定义、CITECT 侧需要配置的 SQL 函数、以及外部系统(MES、报表工具、OPC 客户端)怎么取数。
适合读这份文档的人有三类:一是现场调试工程师,需要确认 CITECT 有没有正常写库;二是二次开发人员,要基于这些表做报表、看板或对接 MES;三是运维人员,数据库涨太快、查询变慢时要定位是哪张表在膨胀。如果你手上正好有这么一份文档,或者你正在做 CITECT 与 SQL Server 的集成,下面我会按「先搞清楚它怎么存、再动手配、最后避坑」的顺序,把这份文档背后的技术链路拆开讲清楚。
2. CITECT 的数据库体系:内部历史库与外部 SQL 库怎么分工
2.1 内部历史库:Trend 文件和 DAT 目录的定位
CITECT 在默认配置下,历史数据并不直接进 SQL Server。它先写自己的内部历史文件,通常位于项目目录的DATA或HIST子目录下,扩展名可能是.dat、.hst或.trend。这些文件按时间分片,每个文件覆盖一段时间窗口,CITECT 的 Trend 控件和报表工具直接读这些文件,速度很快,不依赖外部数据库。这种设计的优点是现场断网、SQL Server 宕机时,历史数据不丢;缺点是外部系统读不了,只能通过 CITECT 自己的接口导出。
内部历史库的配置入口在 CITECT 项目编辑器的「Trend」或「History」设置里,关键参数包括:采样周期(常见 1 秒到 60 秒)、死区(Deadband,模拟量变化超过这个值才记录)、文件保留天数。很多现场历史查不到,就是因为采样周期设得太长,或者死区设得太大,变量小幅波动根本没被记录。我一般会在调试阶段先把采样周期设短、死区设小,确认数据能正常落盘后,再根据磁盘容量和实际需求放宽。
2.2 外部 SQL 库:CITECT 通过 SQL 函数写关系型数据库
当需要外部系统取数时,CITECT 提供了 SQL 函数族,最常用的是SQLConnect、SQLInsert、SQLSelect、SQLDisconnect。这些函数写在 CITECT 的 Cicode 脚本里,由事件触发(比如变量变化、报警产生、定时器到期)时执行。典型写法是:在变量变化事件里调用SQLInsert,把变量名、值、时间戳插到一张自定义表里。
外部数据库选型上,SQL Server 占绝对主流,原因很实际:CITECT 的 SQL 函数对 SQL Server 的 ODBC 驱动兼容最好,而且 SQL Server 有免费的 Express 版本,中小项目够用。也有项目用 MySQL 或 Oracle,但配置复杂度明显上升,尤其是 Oracle 的 ODBC 驱动和字符集问题,容易翻车。如果你正在选型,我建议优先 SQL Server 2016 或 2019,避开 2008 R2 太老的版本,因为新版 SSMS 和驱动对老版本的支持在逐步收紧。
2.3 表结构设计:实时表、报警表、操作日志表的分工
一份合格的 CITECT 数据库说明文档,核心价值就在表结构定义。常见做法是三张主表加若干辅助表:
| 表名 | 用途 | 关键字段 | 写入频率 |
|---|---|---|---|
| RealtimeData | 实时变量快照 | TagName, Value, Quality, UpdateTime | 变量变化时或定时 |
| AlarmLog | 报警记录 | AlarmTag, AlarmText, AlarmTime, AckTime, AckUser | 报警产生/确认时 |
| OperationLog | 操作记录 | UserName, Action, TagName, OldValue, NewValue, OpTime | 操作员动作时 |
| TrendHistory | 历史趋势 | TagName, Value, SampleTime | 定时批量 |
实时表最容易膨胀,如果每个变量每秒都插一条,几千个变量一天就是几亿条。常见优化是:只对关键变量写实时表,或者用「变化时写入」而不是「定时写入」。报警表和操作日志表数据量小,但查询频繁,建议在 AlarmTime 和 OpTime 上建索引。历史趋势表如果数据量大,可以按月分表,或者用 SQL Server 的分区表功能。
提示:表结构一旦确定,后期改字段成本很高,因为 CITECT 侧的 Cicode 脚本、外部报表 SQL、MES 接口都要同步改。设计阶段多花半天,后期省几天。
3. 从零配通 CITECT 写 SQL Server:ODBC、Cicode 与建表脚本
3.1 配置 ODBC 数据源:32 位还是 64 位,这是个问题
CITECT 是 32 位程序(至少大部分存量版本是),所以它调用的是 32 位 ODBC 数据源。在 64 位 Windows 上,默认打开的「ODBC 数据源管理器」是 64 位的,你在这里建的数据源 CITECT 看不到。必须运行C:\Windows\SysWOW64\odbcad32.exe来建 32 位数据源。
步骤:打开 32 位 ODBC 管理器 → 系统 DSN → 添加 → 选 SQL Server 或 SQL Server Native Client → 填名称(比如CitectDB)、服务器 IP 或主机名 → 选登录方式(建议用 SQL 账号,不要用 Windows 集成认证,现场域环境复杂)→ 选默认数据库 → 完成 → 测试连接。
# 验证 32 位 ODBC 数据源是否建成功 # 在命令行运行(注意路径是 SysWOW64) C:\Windows\SysWOW64\odbcad32.exe # 打开后切换到「系统 DSN」标签,确认你的数据源在列表里 # 如果 CITECT 报「数据源未找到」,九成是建到了 64 位管理器里逻辑说明:CITECT 的 SQL 函数底层走 ODBC API,它加载的是 32 位驱动。参数上,DSN 名称要和 Cicode 里SQLConnect的第一个参数完全一致,大小写敏感。服务器地址建议用 IP,不要用主机名,现场 DNS 不稳定时主机名解析失败会直接导致连接超时。
3.2 建表脚本:在 SQL Server 里先跑一遍
在 CITECT 写数据之前,表必须先存在。下面是一份最小可用的建表脚本,覆盖实时、报警、操作日志三张表:
-- 在 SQL Server Management Studio 里,选中目标数据库后执行 CREATE TABLE RealtimeData ( Id BIGINT IDENTITY(1,1) PRIMARY KEY, TagName NVARCHAR(100) NOT NULL, Value FLOAT NULL, Quality INT DEFAULT 0, UpdateTime DATETIME DEFAULT GETDATE() ); CREATE INDEX IX_Realtime_Tag_Time ON RealtimeData(TagName, UpdateTime DESC); CREATE TABLE AlarmLog ( Id BIGINT IDENTITY(1,1) PRIMARY KEY, AlarmTag NVARCHAR(100) NOT NULL, AlarmText NVARCHAR(500) NULL, AlarmTime DATETIME NOT NULL, AckTime DATETIME NULL, AckUser NVARCHAR(50) NULL, AlarmLevel INT DEFAULT 0 ); CREATE INDEX IX_Alarm_Time ON AlarmLog(AlarmTime DESC); CREATE TABLE OperationLog ( Id BIGINT IDENTITY(1,1) PRIMARY KEY, UserName NVARCHAR(50) NULL, Action NVARCHAR(200) NULL, TagName NVARCHAR(100) NULL, OldValue NVARCHAR(100) NULL, NewValue NVARCHAR(100) NULL, OpTime DATETIME DEFAULT GETDATE() );参数说明:TagName用NVARCHAR而不是VARCHAR,因为 CITECT 变量名可能含中文或特殊字符;Value用FLOAT兼容模拟量和数字量(数字量存 0/1);Quality对应 CITECT 的质量戳,0 表示良好,非 0 表示坏值或不确定;时间字段统一用DATETIME,精度到毫秒够用,如果现场要求微秒级,改用DATETIME2。索引建在查询最频繁的字段上,实时表按 TagName + 时间倒序,报警表按时间倒序。
3.3 Cicode 脚本:把数据从 CITECT 推到 SQL Server
CITECT 侧的核心是 Cicode 脚本。下面是一个变量变化时写实时表的示例:
// 在 CITECT 项目编辑器的 Cicode 编辑器里编写 // 假设变量名为 Motor1_Speed,关联到 SQL 写入 FUNCTION WriteRealtime(STRING sTag, REAL rValue) INT hSQL; STRING sSQL; hSQL = SQLConnect("CitectDB", "sa", "YourPassword"); IF hSQL <> 0 THEN sSQL = "INSERT INTO RealtimeData (TagName, Value, Quality, UpdateTime) VALUES ('" + sTag + "', " + RealToStr(rValue) + ", 0, GETDATE())"; SQLExec(hSQL, sSQL); SQLDisconnect(hSQL); ELSE // 连接失败,写系统日志 DebugMsg("SQL Connect Failed: " + IntToStr(hSQL)); END END逻辑说明:SQLConnect返回一个句柄,非 0 表示成功;SQLExec执行非查询语句;SQLDisconnect释放连接。参数上,DSN 名称CitectDB必须和 ODBC 里建的一致,用户名密码用 SQL 账号。这里每次写入都连接和断开,效率很低,生产环境应该用长连接:在 CITECT 启动时SQLConnect一次,把句柄存到全局变量,后续写入复用,退出时再断开。
注意:Cicode 的字符串拼接用
+,但数字要先用RealToStr或IntToStr转成字符串,否则类型不匹配会报错。SQL 语句里的单引号要转义,变量名里如果有单引号会直接导致 SQL 语法错误。
4. 外部系统读 CITECT 数据:报表、MES 与 OPC 的取数路径
4.1 报表工具直连 SQL Server 的查询写法
外部报表工具(比如 FineReport、Power BI、或者自己写的 C# 程序)读 CITECT 数据,本质就是查前面建的那几张表。最常见的需求是「查某个变量某段时间的历史曲线」,SQL 写法:
-- 查询 Motor1_Speed 在指定时间段的数据,按时间排序 SELECT TagName, Value, UpdateTime FROM RealtimeData WHERE TagName = 'Motor1_Speed' AND UpdateTime BETWEEN '2025-01-01 08:00:00' AND '2025-01-01 18:00:00' ORDER BY UpdateTime ASC;参数说明:时间范围用BETWEEN闭区间,注意 SQL Server 的DATETIME精度是 3.33 毫秒,边界值可能差几毫秒,如果要求精确,用>=和<替代。如果数据量大,查询会慢,建议在TagName和UpdateTime上建复合索引,或者把历史数据归档到单独的TrendHistory表,实时表只保留最近几天。
4.2 MES 对接:批量取数和增量同步
MES 系统通常要求增量同步,不能每次全量拉。常见做法是在表里加一个自增Id字段(前面建表脚本已经加了),MES 侧记录上次同步到的最大Id,下次只取Id > 上次值的记录:
-- MES 增量拉取,@LastId 是上次同步的最大 Id SELECT Id, TagName, Value, UpdateTime FROM RealtimeData WHERE Id > @LastId ORDER BY Id ASC;这种方式的坑在于:如果 CITECT 侧写入有事务回滚或删除操作,Id会跳号,但不会漏数据,因为自增列只增不减。另一个坑是并发写入时,MES 读到一半又有新数据插入,可能导致本次查询结果不完整,解决办法是每次查询限制条数(比如TOP 10000),循环拉取直到没有新数据。
4.3 OPC 方式取数:绕过数据库直接读实时值
有些场景不需要历史数据,只要实时值,这时候走 OPC 比查数据库更合适。CITECT 可以作为 OPC Server 对外提供数据,外部 OPC Client(比如 KEPServer、UAExpert、或者自己用 C# 写的客户端)直接订阅变量。配置步骤:在 CITECT 的「OPC」设置里启用 OPC Server 功能,添加要暴露的变量,然后外部客户端连 CITECT 的 OPC 服务(通常是Citect.OPC.1或类似 ProgID)。
OPC 方式的好处是实时性高、不依赖 SQL Server;缺点是只能拿当前值,拿不到历史,而且 OPC Classic(DA)在跨防火墙、跨网段时配置麻烦,现在新项目更多用 OPC UA。如果你的 CITECT 版本支持 OPC UA,优先走 UA,端口 4840,配置比 DA 简单,安全性也好。
5. 避坑与排查:CITECT 写库失败的 5 个血泪教训
5.1 现象:CITECT 画面正常,但 SQL 表里一条数据都没有
原因:最常见的是 ODBC 数据源建到了 64 位管理器里,CITECT 作为 32 位程序找不到。其次是 Cicode 脚本没有绑定到事件上,或者事件触发条件没满足。还有一种情况是 SQL Server 的 TCP/IP 协议没启用,只开了共享内存,远程连接失败。
解决:先确认 32 位 ODBC 里数据源存在且测试连接成功;再检查 Cicode 函数是否被正确调用(可以在函数开头加DebugMsg输出日志);最后在 SQL Server 配置管理器里确认 TCP/IP 已启用,端口 1433 在防火墙放行。
5.2 现象:数据能写入,但时间戳全是错的
原因:CITECT 运行主机和 SQL Server 主机时区不一致,或者 CITECT 用了本地时间而 SQL Server 用了 UTC。另一种可能是 Cicode 里用了GETDATE(),这个函数取的是 SQL Server 主机的时间,不是 CITECT 主机的时间。
解决:统一时区,所有机器都设成北京时间;如果跨时区,在 Cicode 里用TimeStr取 CITECT 本地时间,拼到 SQL 语句里,而不是依赖GETDATE()。检查方法:在 SQL Server 里执行SELECT GETDATE(),和 CITECT 主机时间对比。
5.3 现象:数据库涨得飞快,磁盘几天就满了
原因:实时表每个变量每次变化都插一条,几千个变量一天几百万条。或者采样周期设得太短,死区设得太小,模拟量微小波动全被记录。
解决:对实时表做清理策略,比如只保留最近 30 天,用 SQL Server 代理作业定时删除旧数据;或者把历史数据归档到TrendHistory表后删除实时表旧记录。更根本的办法是优化写入策略:只对关键变量写库,模拟量用死区过滤,数字量用变化时写入。
5.4 现象:Cicode 脚本报「SQL error: 连接超时」
原因:SQL Server 连接数满了,或者网络不稳定。CITECT 如果每次写入都SQLConnect和SQLDisconnect,高频写入时连接数会瞬间飙升,SQL Server Express 默认连接数有限,容易打满。
解决:改用长连接,CITECT 启动时连接一次,全局句柄复用。如果必须短连接,加连接池或降低写入频率。另外检查 SQL Server 的max worker threads和user connections配置,Express 版有上限,必要时升级到标准版。
5.5 现象:外部报表查询特别慢,动辄几十秒
原因:表没建索引,或者索引建了但查询没用上。比如在TagName上建了索引,但查询条件用了LIKE '%Motor%',前置通配符会导致索引失效。另一种情况是数据量太大,单表几亿条,即使有索引,范围扫描也慢。
解决:用 SQL Server 的「显示实际执行计划」分析查询,确认索引是否命中。避免前置通配符,改用TagName = 'Motor1_Speed'或TagName LIKE 'Motor1%'。数据量大的话,按月分表或建分区表,查询时只扫对应分区。
6. 进阶技巧:用 SQL Server 代理作业做 CITECT 数据自动归档
前面讲的都是「怎么把数据写进去、怎么读出来」,但现场跑久了,真正让人头疼的是数据无限增长。我一般会在 SQL Server 里建一个代理作业,每天凌晨把RealtimeData里超过 30 天的数据搬到TrendHistory归档表,然后删除原表旧数据。这样实时表始终保持较小规模,查询快,归档表只用于历史追溯。
具体做法:先建归档表,结构和RealtimeData一致,但去掉自增Id或保留都行。然后建作业,步骤一用INSERT INTO ... SELECT搬数据,步骤二用DELETE删旧数据。注意两步要放在同一个事务里,或者先搬后删,搬失败不删,避免数据丢失。
-- 归档作业的核心 SQL,放在 SQL Server 代理作业的步骤里 BEGIN TRANSACTION; INSERT INTO TrendHistory (TagName, Value, Quality, UpdateTime) SELECT TagName, Value, Quality, UpdateTime FROM RealtimeData WHERE UpdateTime < DATEADD(DAY, -30, GETDATE()); DELETE FROM RealtimeData WHERE UpdateTime < DATEADD(DAY, -30, GETDATE()); COMMIT TRANSACTION;参数说明:DATEADD(DAY, -30, GETDATE())里的 30 是保留天数,根据磁盘容量和查询需求调整。事务保证搬和删要么都成功,要么都回滚。作业调度设成每天凌晨 2 点,避开生产高峰。如果数据量特别大,一次性搬可能锁表太久,可以分批搬,每次TOP 10000,循环执行直到搬完。
验证方法:作业跑完后,查RealtimeData的最早时间,应该在 30 天以内;查TrendHistory的记录数,应该和删除的条数一致。如果发现归档表数据比预期少,检查事务是否回滚,或者作业是否被其他锁阻塞。
我自己的习惯是,每次交付 CITECT 项目时,除了那份「CITECT数据库说明.doc」,还会额外写一个「数据库维护手册」,把归档作业、索引重建、磁盘监控这几件事写清楚,交给业主的运维人员。因为现场真正出问题的,往往不是 CITECT 本身,而是数据库没人管,跑了一年半载磁盘满了、查询卡了,才回头找集成商。提前把维护动作固化下来,比事后救火省心得多。希望帮到你。
本文还有配套的精品资源,点击获取