跳到主要内容

数据库字段映射

SQL 数据库有几十种数据类型, IEC 61131-3 PLC 有十几种, 两边类型系统不完全对齐。本页定义标准映射规则, 以及向导和 FB 在背后做的转换细节。

典型场景

所有映射示例都是**"从数据库读配方/工单参数进 PLC"** 场景, 例如 SQL VARCHAR → PLC STRING (配方名), SQL DECIMAL → PLC REAL (配方参数), SQL INT → PLC DINT (工单号), SQL DATETIME → PLC DT (班次开始时间)。 读场景为主, 写场景仅在班次结束汇总等低频时使用。

映射总表

整数

SQL 类型位宽范围PLC 类型备注
TINYINT80~255USINT无符号
TINYINT (有符号)8-128~127SINT
SMALLINT16-32768~32767INT
SMALLINT UNSIGNED160~65535UINT
INT / INTEGER32±2GDINT
INT UNSIGNED320~4GUDINT
BIGINT64±9E18LINT
BIGINT UNSIGNED640~1.8E19ULINT

浮点与定点

SQL 类型位宽精度PLC 类型警告
REAL / FLOAT32~7 位十进制REAL-
DOUBLE / FLOAT(p>24)64~15 位LREAL-
DECIMAL(p,s) p≤7变长精确REAL近似, 可能有 0.1 型舍入
DECIMAL(p,s) p≤15变长精确LREAL精度损失较小
DECIMAL(p,s) p>15变长精确STRING + 解析PLC 无对应精度
NUMERIC同 DECIMAL同 DECIMAL
精度警告

配方里的"必须精确"字段 (例如灌装量 µL 级, 配料重量 mg 级), 强烈建议在 PLC 侧用 DINT 存放最小粒度整数 (而不是 REAL 存小数), 避免浮点舍入累计误差。

字符串

SQL 类型编码长度PLC 类型备注
CHAR(N)定长≤255STRING[N]尾部空格需要 TRIM
VARCHAR(N)变长≤65535STRING[N]N ≤ 255 最佳
TEXT / CLOB变长极大不直接映射仅存路径或 Hash
NCHAR / NVARCHARUnicodeWSTRING[N]如 SQL Server 默认
NTEXTUnicode 大不直接映射同 TEXT
JSON (MySQL/PG)文本STRING + 解析手工解析
XML (SQL Server)文本STRING + 解析手工解析
UUID / GUID固定 16BSTRING[36]转为文本表示

时间

SQL 类型粒度PLC 类型编码
DATEDATE年月日
TIME时分秒 [. 毫秒]TIME_OF_DAY (TOD)毫秒 (可选)
DATETIME / TIMESTAMP秒 or 毫秒DATE_AND_TIME (DT)YMD + HMS[.ms]
DATETIME2 (SQL Server)纳秒DT (毫秒级)精度损失
SMALLDATETIMEDT精度上升
TIMESTAMP WITH TIMEZONE带时区DT (UTC)建议 UTC 存储
INTERVAL时长TIME毫秒
时区

数据库时间一般建议 UTC 存储, PLC 侧显示时再转本地时区。避免 "DT 写入时 +8 读出时 +0" 这种偏移。

布尔

SQL 类型PLC 类型备注
BITBOOLSQL Server
BOOLEAN / BOOLBOOLPostgreSQL / MySQL
TINYINT (0/1 约定)BOOL老库常用
CHAR(1) ('Y'/'N')BOOL + 转换需在 FB 里 = 'Y'

二进制

SQL 类型PLC 类型备注
BINARY(N)ARRAY[0..N-1] OF BYTE定长
VARBINARY(N)ARRAY[0..N-1] OF BYTE + Length变长需单独记长度
BLOB / LONGVARBINARY不直接映射存文件路径即可
IMAGE (SQL Server 旧)不直接映射已弃用
BYTEA (PostgreSQL)ARRAY OF BYTE

数组 / 集合

SQL 类型PLC 类型支持数据库
ARRAY[] (PG)ARRAY OF TPostgreSQL
SET (MySQL)STRING + 解析MySQL
ENUM (MySQL)枚举类型MySQL

地理与特殊

SQL 类型PLC 类型备注
GEOMETRY / GEOGRAPHY不直接映射建议存 WKT 文本或经纬度分字段
HIERARCHYID (SQL Server)STRING + 解析特殊
SQL_VARIANTSTRING动态类型, 强转文本

NULL 处理

SQL 允许任意列为 NULL, PLC 类型大多没有 NULL 概念。处理策略:

策略 1: 默认值替代

PLC 类型NULL → 默认值
BOOLFALSE
整数 (SINT/INT/DINT/LINT)0
无符号整数0
浮点 (REAL/LREAL)0.0
STRING'' (空串)
DT1970-01-01 00:00:00

策略 2: 配套 NULL 标志字段

向导可以为允许 NULL 的列生成一个配对的 BOOL 字段:

VAR
Speed : REAL; (* 实际值 *)
Speed_Null : BOOL; (* TRUE 表示原值为 NULL *)
END_VAR

PLC 程序用:

IF NOT Speed_Null THEN
(* 使用 Speed *)
END_IF;

策略 3: Sentinel 值

用不可能出现的值表示 NULL:

类型Sentinel
INT-32768
DINTMIN_DINT
REALNaN (IEEE 754)

查询时:

SELECT COALESCE(speed, -1) AS speed FROM ...

或在 FB 里:

IF IS_VALID_REAL(Speed) THEN ...

推荐优先用策略 2 (配套 BOOL), 最清晰。

自增 ID

大多数表有自增主键 (INT IDENTITY / SERIAL / AUTO_INCREMENT):

写入时不传 ID

Insert(
SQL := 'INSERT INTO log(ts, qty, ok) VALUES(:ts, :q, :ok)',
Execute := TRUE
);
(* 不 Bind id 字段 *)

数据库自动分配。

读回新 ID

不同引擎的语法:

引擎方法
SQL ServerSCOPE_IDENTITY()OUTPUT INSERTED.id
MySQLLAST_INSERT_ID()
PostgreSQLINSERT ... RETURNING id
SQLitelast_insert_rowid()
Oracle用 Sequence + RETURNING

FB 统一通过 Insert.LastInsertId 返回:

IF Insert.Done THEN
NewId := Insert.LastInsertId;
END_IF;

底层适配器根据引擎自动用对应方法。

UUID 主键

如果用 UUID 主键, 不需要读回:

NewUuid := GENERATE_UUID();   (* 在 PLC 侧生成 *)
Insert.BindString('id', NewUuid);

外键

PLC 通常不主动维护外键, 但需要处理:

读取时 JOIN

SELECT r.id, r.name, g.code
FROM recipes r JOIN recipe_groups g ON r.group_id = g.id

向导把两张表 JOIN 后的视图导入为一个"逻辑表", 生成一个合并 DB:

VAR
RecipeId : DINT;
RecipeName : STRING[64];
GroupCode : STRING[16];
END_VAR

写入时校验

写入审计记录前检查父表 (配方分组) 是否存在:

(* 1. 先查分组是否存在 *)
CheckGroup(Execute := TRUE);
IF CheckGroup.RowCount = 0 THEN
ErrorMsg := '配方分组不存在';
RETURN;
END_IF;

(* 2. 再插审计记录 *)
Insert.BindInt('group_id', CheckGroup.Row[0].Cell[0].AsDInt);
Insert(Execute := TRUE);

或者依赖数据库的外键约束 — 插入失败时 Insert 返回错误码 FOREIGN_KEY_VIOLATION, PLC 程序处理:

IF Insert.Error THEN
IF Insert.ErrorCode = FOREIGN_KEY_VIOLATION THEN
ErrorMsg := '外键约束冲突';
END_IF;
END_IF;

枚举 (ENUM)

MySQL 的 ENUM('DRAFT','APPROVED','RETIRED'), 或 SQL Server 的 CHECK 约束都是"值域限制" (例如配方状态):

策略 1: 字符串

VAR
Status : STRING[32]; (* 'DRAFT' / 'APPROVED' / 'RETIRED' *)
END_VAR

PLC 比较时:

IF Status = 'APPROVED' THEN ...

简单但性能较差 (字符串比较), 且代码易出现 typo。

策略 2: PLC 枚举类型

工程里定义枚举:

TYPE E_RecipeStatus :
(
Draft := 0,
Approved := 1,
Retired := 2
) INT;
END_TYPE

VAR
Status : E_RecipeStatus;
END_VAR

读数据库时转换:

CASE SqlStatusString OF
'DRAFT': Status := E_RecipeStatus.Draft;
'APPROVED': Status := E_RecipeStatus.Approved;
'RETIRED': Status := E_RecipeStatus.Retired;
ELSE
Status := E_RecipeStatus.Draft;
END_CASE

策略 3: 数据库端用整数

-- 新设计
CREATE TABLE recipes (
id INT PRIMARY KEY,
code VARCHAR(32),
status TINYINT -- 0=Draft 1=Approved 2=Retired
);

-- 搭配元表
CREATE TABLE status_meta (
code TINYINT PRIMARY KEY,
name VARCHAR(32)
);

PLC 直接拿整数, 元表只在 UI/报表用。推荐。

字符串编码

SQL 服务器端编码和 PLC 端编码可能不一致:

情况后果处理
MySQL 默认 latin1, 写中文乱码改表为 utf8mb4
SQL Server VARCHAR 存中文按 Windows 代码页 (GB2312)用 NVARCHAR
PostgreSQL 默认 UTF-8兼容-
PLC STRING 默认 UTF-8兼容大多数-

IDE 连接配置里指定 CharSet=utf8mb4 (MySQL) 或 CharSet=UTF-8 (PostgreSQL), 驱动负责转码。

日期时间细节

精度

  • PLC 的 DT 精度是毫秒
  • SQL DATETIME 精度可能是秒 (传统 MySQL) 或毫秒/纳秒
  • 写入 SQL 时会按目标列的精度截断

时区

推荐策略:

  1. 服务器 UTC, PLC 本地: 写入时 DT_TO_UTC(localDt), 读出时 UTC_TO_DT(utcDt)
  2. 全 UTC: 显示时由 UI 层转换
  3. 全本地: 简单但不跨时区

向导默认策略 2, 可改。

特殊值

  • '0000-00-00' (MySQL 允许, 其他不允许): 视为 NULL
  • '1970-01-01 00:00:00' (Unix 纪元): 有效但常作 sentinel
  • '9999-12-31' (远未来): 常作 "永不过期" 的 sentinel

JSON 字段

MySQL 5.7+ / PostgreSQL 的 JSON 类型:

SELECT data FROM config WHERE id = 1;
-- data = '{"speed": 100, "mode": "auto"}'

PLC 侧手工解析:

VAR
JsonStr : STRING[512];
JsonObj : JSON_OBJECT;
END_VAR

JsonObj.Parse(JsonStr);
Speed := JsonObj.GetReal('speed');
Mode := JsonObj.GetString('mode');

或者在 SQL 侧提取:

SELECT JSON_EXTRACT(data, '$.speed') AS speed, 
JSON_EXTRACT(data, '$.mode') AS mode
FROM config WHERE id = 1;

结果作为独立列, PLC 只拿最终值, 简单得多。

数组类型 (PostgreSQL)

PostgreSQL 允许列是数组:

CREATE TABLE recipes (
id SERIAL PRIMARY KEY,
tags TEXT[]
);

向导把数组映射为 ARRAY[0..N-1] OF T, 但需要指定最大长度:

VAR
Tags : ARRAY[0..9] OF STRING[32];
TagsCount : INT;
END_VAR

驱动按 N 截断或动态分配。

BLOB 处理

大文件 (图片、PDF) 不建议存数据库:

  • 性能差 (BLOB 不走索引)
  • 备份开销大
  • 网络吞吐有限

推荐:

  • 文件存 MinIO / 文件服务器
  • 数据库只存文件元信息 (路径, 大小, Hash)
CREATE TABLE files (
id INT PRIMARY KEY,
path VARCHAR(512),
size BIGINT,
sha256 CHAR(64)
);

PLC 拿 path, 通过 HTTP 或文件协议下载。

UDT (结构体) 支持

某些数据库 (Oracle, PostgreSQL) 有自定义复合类型。Darra 目前只支持基础类型, 复合类型需拆分为多列。

映射冲突处理

向导遇到无法映射的类型时的行为:

冲突向导处理
DECIMAL(20,5)警告 + 默认 LREAL + 备注"精度丢失"
VARCHAR(10000)警告 + 截断到 STRING[255] + 备注
BLOB警告 + 不生成 + 建议改表结构
未知类型跳过 + 记录到导入日志

用户可以在字段映射步骤手工调整。

验证与测试

向导生成后建议的验证步骤:

  1. 插入测试数据: 手工在数据库里插入一行, 验证 FB_Read 能正确读回
  2. 边界值测试: NULL, 空串, 最大值, 最小值
  3. 并发测试: 两个 PLC 同时读写, 看是否有竞争条件
  4. 故障测试: 断开数据库, 看 FB 是否正确进入错误状态并重试

相关文档