CITECT数据库说明:SCADA历史存储与SQL Server集成实战
简介这份文档资料聚焦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 ServerODBC、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/1Quality对应 CITECT 的质量戳0 表示良好非 0 表示坏值或不确定时间字段统一用DATETIME精度到毫秒够用如果现场要求微秒级改用DATETIME2。索引建在查询最频繁的字段上实时表按 TagName 时间倒序报警表按时间倒序。3.3 Cicode 脚本把数据从 CITECT 推到 SQL ServerCITECT 侧的核心是 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 ClassicDA在跨防火墙、跨网段时配置麻烦现在新项目更多用 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 本身而是数据库没人管跑了一年半载磁盘满了、查询卡了才回头找集成商。提前把维护动作固化下来比事后救火省心得多。希望帮到你。本文还有配套的精品资源点击获取