在工业自动化领域,WinCC作为西门子旗下的经典SCADA系统,其报表功能一直是工程师们的痛点。传统WinCC报表系统存在灵活性差、定制困难等问题,特别是在需要对接多种数据库和不同上位机系统的场景下。这个全脚本实现的通用数据库模板,正是为了解决这些痛点而生。
这套模板的核心价值在于:
提示:使用本模板需要具备WinCC脚本基础和SQL语言知识,适合中高级自动化工程师使用。
WinCC自带的报表系统虽然开箱即用,但在实际工业场景中存在明显局限:
全脚本方案的优势在于:
本模板主要采用以下技术:
c复制// 典型的技术栈使用示例
#include "apdefap.h" // WinCC API头文件
#include <sql.h> // ODBC标准头文件
#include <sqlext.h> // ODBC扩展头文件
数据库连接是报表系统的首要环节,本模板采用ODBC实现通用连接:
c复制void DB_Connect(char* server, char* dbName, char* user, char* pass)
{
SQLHENV env;
SQLHDBC dbc;
SQLRETURN ret;
// 分配环境句柄
ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
// 错误处理代码...
return;
}
// 设置ODBC版本
SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
// 分配连接句柄
SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);
// 构建连接字符串
char connStr[512];
sprintf(connStr, "DRIVER={SQL Server};SERVER=%s;DATABASE=%s;UID=%s;PWD=%s;",
server, dbName, user, pass);
// 建立连接
SQLDriverConnect(dbc, NULL, (SQLCHAR*)connStr, SQL_NTS,
NULL, 0, NULL, SQL_DRIVER_COMPLETE);
}
关键点说明:
DRIVER参数决定数据库类型,支持{SQL Server}、{Oracle}等频繁建立和断开数据库连接会影响性能,推荐使用连接池技术:
c复制// 全局连接池变量
SQLHDBC connectionPool[5];
int currentConn = 0;
void InitConnectionPool() {
for(int i=0; i<5; i++) {
DB_Connect(server, dbName, user, pass);
connectionPool[i] = dbc; // 保存连接
}
}
SQLHDBC GetConnection() {
SQLHDBC conn = connectionPool[currentConn];
currentConn = (currentConn + 1) % 5;
return conn;
}
报表的核心是数据表结构,本模板提供灵活的建表功能:
c复制void CreateReportTable(char* tableName)
{
char sqlCmd[1024];
sprintf(sqlCmd,
"IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='%s' AND xtype='U') "
"CREATE TABLE %s ("
"ID INT IDENTITY(1,1) PRIMARY KEY,"
"TimeStamp DATETIME DEFAULT GETDATE(),"
"DeviceID VARCHAR(50) NOT NULL,"
"TagName VARCHAR(100) NOT NULL,"
"Value FLOAT NULL,"
"Quality INT NULL,"
"Comment VARCHAR(255) NULL)",
tableName, tableName);
SQLHSTMT hstmt;
SQLAllocHandle(SQL_HANDLE_STMT, dbc, &hstmt);
SQLExecDirect(hstmt, (SQLCHAR*)sqlCmd, SQL_NTS);
SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
}
设计要点:
高效的数据存储是报表系统的关键:
c复制void SaveData(char* tableName, char* deviceID, char* tagName, float value, int quality, char* comment)
{
char sqlCmd[1024];
sprintf(sqlCmd,
"INSERT INTO %s (DeviceID, TagName, Value, Quality, Comment) "
"VALUES ('%s', '%s', %.4f, %d, '%s')",
tableName, deviceID, tagName, value, quality, comment);
SQLHSTMT hstmt;
SQLAllocHandle(SQL_HANDLE_STMT, dbc, &hstmt);
// 使用参数化查询防止SQL注入
SQLPrepare(hstmt, (SQLCHAR*)"INSERT INTO ? (DeviceID, TagName, Value, Quality, Comment) VALUES (?, ?, ?, ?, ?)", SQL_NTS);
// 绑定参数...
SQLExecute(hstmt);
SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
}
存储优化建议:
强大的查询功能是报表系统的价值所在:
c复制void QueryReport(char* tableName, char* deviceFilter, char* startTime, char* endTime)
{
char sqlCmd[2048];
sprintf(sqlCmd,
"SELECT DeviceID, TagName, Value, TimeStamp "
"FROM %s "
"WHERE TimeStamp BETWEEN '%s' AND '%s' "
"AND DeviceID LIKE '%%%s%%' "
"ORDER BY TimeStamp DESC",
tableName, startTime, endTime, deviceFilter);
SQLHSTMT hstmt;
SQLAllocHandle(SQL_HANDLE_STMT, dbc, &hstmt);
SQLExecDirect(hstmt, (SQLCHAR*)sqlCmd, SQL_NTS);
// 处理结果集
SQLCHAR deviceID[50], tagName[100];
SQLREAL value;
SQLCHAR timeStamp[20];
SQLINTEGER cbDeviceID, cbTagName, cbValue, cbTimeStamp;
while(SQLFetch(hstmt) == SQL_SUCCESS) {
SQLGetData(hstmt, 1, SQL_C_CHAR, deviceID, sizeof(deviceID), &cbDeviceID);
SQLGetData(hstmt, 2, SQL_C_CHAR, tagName, sizeof(tagName), &cbTagName);
SQLGetData(hstmt, 3, SQL_C_FLOAT, &value, sizeof(value), &cbValue);
SQLGetData(hstmt, 4, SQL_C_CHAR, timeStamp, sizeof(timeStamp), &cbTimeStamp);
// 将数据输出到WinCC控件或变量
SetTagChar("Report_DeviceID", (char*)deviceID);
SetTagChar("Report_TagName", (char*)tagName);
SetTagFloat("Report_Value", value);
SetTagChar("Report_Time", (char*)timeStamp);
}
SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
}
除了基础查询,还可以实现复杂统计:
c复制void GetStatistics(char* tableName, char* tagName, char* startTime, char* endTime)
{
char sqlCmd[2048];
sprintf(sqlCmd,
"SELECT "
"AVG(Value) as AvgValue, "
"MAX(Value) as MaxValue, "
"MIN(Value) as MinValue, "
"STDEV(Value) as StdDev "
"FROM %s "
"WHERE TagName = '%s' "
"AND TimeStamp BETWEEN '%s' AND '%s'",
tableName, tagName, startTime, endTime);
// 执行查询并处理结果...
}
索引策略:为常用查询条件添加索引
c复制sprintf(sqlCmd, "CREATE INDEX IX_%s_Time ON %s(TimeStamp)", tableName, tableName);
批量操作:减少数据库往返次数
c复制// 使用批量插入语句
sprintf(sqlCmd, "INSERT INTO %s VALUES (...), (...), (...)", tableName);
连接管理:合理使用连接池
健壮的错误处理是工业系统的必备特性:
c复制void CheckSQLError(SQLHANDLE handle, SQLSMALLINT type)
{
SQLCHAR sqlstate[6], msg[SQL_MAX_MESSAGE_LENGTH];
SQLINTEGER native;
SQLSMALLINT len;
SQLRETURN ret;
int i = 1;
while((ret = SQLGetDiagRec(type, handle, i, sqlstate, &native,
msg, sizeof(msg), &len)) == SQL_SUCCESS) {
printf("Error %d: %s\n", native, msg);
i++;
}
}
实现多数据库支持的技巧:
c复制#ifdef USE_ORACLE
#define GETDATE() "SYSDATE"
#elif defined USE_SQLSERVER
#define GETDATE() "GETDATE()"
#endif
void CreateTableCompat(char* tableName) {
sprintf(sqlCmd, "CREATE TABLE %s (TimeStamp DATETIME DEFAULT %s)",
tableName, GETDATE());
}
通过修改SQL查询和结果处理,可以实现各种报表格式:
c复制// 生成日报表
void GenerateDailyReport(char* tableName, char* date)
{
char startTime[20], endTime[20];
sprintf(startTime, "%s 00:00:00", date);
sprintf(endTime, "%s 23:59:59", date);
char sqlCmd[2048];
sprintf(sqlCmd,
"SELECT CONVERT(VARCHAR(10), TimeStamp, 120) as Date, "
"DeviceID, TagName, "
"AVG(Value) as AvgValue, MAX(Value) as MaxValue "
"FROM %s "
"WHERE TimeStamp BETWEEN '%s' AND '%s' "
"GROUP BY CONVERT(VARCHAR(10), TimeStamp, 120), DeviceID, TagName "
"ORDER BY Date, DeviceID",
tableName, startTime, endTime);
// 执行查询...
}
将查询结果展示在WinCC画面中:
c复制void FillWinCCTable(SQLHSTMT hstmt)
{
int row = 0;
while(SQLFetch(hstmt) == SQL_SUCCESS && row < MAX_ROWS) {
// 获取各列数据...
SetTableChar("ReportTable", row, 0, (char*)deviceID);
SetTableFloat("ReportTable", row, 1, value);
// 设置其他列...
row++;
}
SetTagWord("ReportTable_RowCount", row);
}
部署本模板需要准备:
c复制void DatabaseMaintenance()
{
// 定期执行数据库维护任务
sprintf(sqlCmd,
"BACKUP DATABASE YourDB TO DISK='C:\\Backups\\YourDB.bak' "
"WITH COMPRESSION, STATS=10");
// 执行索引重建
sprintf(sqlCmd, "ALTER INDEX ALL ON YourTable REBUILD");
}
这套WinCC通用外部数据库报表模板经过多个工业现场验证,能够显著提高报表系统的开发效率和运行稳定性。根据实际项目需求,可以进一步扩展以下功能:
模板中的核心代码已经过优化,但在具体项目中仍需根据数据量和性能要求进行适当调整。建议在开发测试环境中充分验证后再部署到生产环境。