离线导出 SQL 说明

本文说明离线导出 SQL 支持的能力与语法,用于指导在配置离线导出类任务(如分析型文本导出、敏感词导出上报)时填写「导出 SQL」。本文只聚焦 SQL 本身,不涉及导出任务如何配置。

1. 概述

离线导出 SQL 是一条类 SQL 的 SELECT 语句:它从 TcaplusDB 表的离线(每日备份)静态数据筛选记录、投影并转换字段,导出为文本(字段间以 | 分隔)。

基本形态:

SELECT1, 列2, ... FROM 表名 [WHERE 过滤条件]

一个最小示例——从 PlayerInfo 表导出 uinname 两列,仅保留 uin > 1000 的记录:

SELECT uin, name FROM PlayerInfo WHERE uin > 1000

几个要点:

  • 导出的是静态备份数据,不是在线实时数据。
  • 必须显式列出要导出的列,不支持 SELECT *
  • 字段间以 | 分隔输出;字符串中的 | 和换行符会被替换为空格(设计列时需留意)。

与在线读写 SQL 的区别(重要,请勿混用)

本文的离线导出 SQL,与在线读写请求所使用的条件过滤/更新语法不完全一样(在线语法见《条件过滤和更新功能》《条件过滤和更新语法说明》)。

离线导出 SQL 是一条完整的 SELECT 查询(包含字段投影、表达式转换和条件过滤);而在线读写只是请求上携带的 condition/operation 文本片段,不含 SELECT 投影。离线导出在 SELECT 列、函数、算术运算、CASE WHEN、数组展开等方面,能力更丰富。两者的差异小结见 §5

2. 快速上手(高频能力)

2.1 SELECT 列

  • 一级字段:直接写字段名,如 uinnameopenid
  • 列别名(AS:为导出列指定名称,如 c_binary AS bin
  • 常量:用类型函数提示常量类型,如 INT(0)STRING('foo')。常用于补齐目标格式中的固定列。
SELECT uin, name, openid, INT(0) AS area_id, INT(9999) AS plat_id
FROM PlayerInfo

文本导出必须显式列出列(不支持 SELECT *),输出时各列以 | 分隔。

2.2 嵌套字段访问

TDR 表和 PB 表都支持多级嵌套字段访问,用 . 逐级深入:

  • TDR 原生嵌套ReasonID.IDsingle_struct.x,支持任意深度。
  • PB 普通嵌套additional.card_brief_info.card_signature
  • PB map 元素model_data['basic'].basic_info.declaration,其中 model_data 是 map 类型、'basic' 是 key。
  • 数组元素all_type_array[1].anamerstr[0],下标从 0 开始。
-- TDR 表的多级嵌套字段
SELECT UID, MailID, ReasonID.ID FROM tbMail WHERE ReasonID.ID > 0
-- PB 表的嵌套字段与 map 取值
SELECT
    additional.card_brief_info.card_signature AS card_signature,
    model_data['basic'].basic_info.guild_name AS guild_name
FROM GuildActorData

更多字段路径形态(按 PB 字段编号访问、bytes 字段内嵌 PB、选取整个复杂字段等)见 §3.2

2.3 WHERE 过滤(常用)

仅导出满足条件的记录。常用:

  • 比较>>=<<==/==!=/<>
  • 逻辑ANDORNOT,可用括号控制优先级。
  • 模糊匹配LIKENOT LIKE,与 MySQL 一致,默认忽略大小写,% 匹配任意字符串、_ 匹配单字符。
  • 集合IN (...)NOT IN (...)
SELECT actor_id, guild_name
FROM GuildActorData
WHERE actor_id LIKE 'guild:%' AND actor_id NOT LIKE 'guild:%:%'

2.4 常用函数

函数 说明 示例
INT / UINT / FLOAT / STRING 类型提示或转换 STRING(openid)UINT(platid)
LENGTH 字符串/bytes 的字节长度 LENGTH(name)
SUBSTRING 取子串,SUBSTRING(s, begin[, len]) SUBSTRING(name, 0, 5)
CONCAT 字符串拼接 CONCAT(name, '_', UINT(platid))
UPPER / LOWER 转大写/小写 UPPER(name)
SIZE 数组或 map 的元素个数 SIZE(mailbox)
SELECT uin, UPPER(name) AS name_upper, LENGTH(name) AS name_len
FROM PlayerInfo
WHERE LENGTH(name) > 0

完整函数清单见 §3.4

3. 详细能力

3.1 SELECT 列表达式总览

每一个 SELECT 列都可以是下列形态之一,并可用 AS 命名:

  • 常量INT(0)STRING('x')
  • 字段路径:一级字段或嵌套字段,见 §3.2
  • 系统字段:如 __last_access_time__,见 §3.3
  • 函数调用:如 STRING(openid)CONCAT(a, b),见 §3.4
  • 算术表达式+-*/,如 (base + bonus) * 2total / 1024
  • CASE WHEN 表达式:见 §3.7
  • 数组展开 EXPAND_ARRAY:见 §3.6

3.2 字段路径(详解)

字段路径用来定位「取哪个字段」,可用于 SELECT 列和 WHERE 条件。一条路径里可以混用:字段名. 逐级嵌套、['key']/[index] 容器取值,以及 PB 字段编号(tag)

3.2.1 按字段名访问

形态 示例 说明
一级字段 openid 适用于所有表
TDR 原生多级嵌套 ReasonID.IDsingle_struct.xa.b.c.d 任意深度,需要表的 TDR 结构定义可用
PB 多级嵌套 additional.card_brief_info.card_signature message 逐级访问
PB map 元素 str_map['k10'].aname(字符串 key)、int_map[20].aid(整型 key) ['key']/[key] 定位 map 元素,可继续嵌套
数组元素 all_type_array[1].anamerstr[0] [index] 定位数组元素,下标从 0 开始
-- TDR 嵌套 + PB 嵌套 + map + 数组
SELECT
    ReasonID.ID,
    all_type.aname,
    str_map['k10'].aname,
    all_type_array[1].aname
FROM tbl

取数组/map 的全部元素并展开成多行,请用 EXPAND_ARRAY,见 §3.6

3.2.2 按 PB 字段编号(tag)访问

PB 字段可以用其 tag 编号 代替字段名来访问,形如 a.1.2.3:把路径中的字段名换成对应的 PB tag 编号即可逐级深入。

  • $ 代表整条记录(根),从根按编号访问顶层字段:$.1$.2$.11.10
  • 可与字段名、map、数组混用all_type.11.2all_type_array[1].2int_map[20].1$.17.102.14['k20'].1$.17.102.12[0].1
  • 默认按 uint64 解析,请用类型函数/类型提示纠正:STRING($.2)INT($.3)FLOAT($.6)
  • 不依赖 proto 定义:即使没有提供该 PB 的 proto,也能按编号解析。
-- $ 是整条记录;$.1=id, $.2=name, $.3=i32, $.11.10=all_type.fix64, $.11.13=all_type.rinfo
SELECT $.1, STRING($.2), INT($.3), $.11.10, $.11.13 FROM tbl

应用场景:业务没有提供(完整的)proto 定义,或希望直接按编号取字段(schema-free)。

3.2.3 bytes 字段中内嵌的 PB 数据

很常见的一种情况:某个 bytes/char 字段里实际保存的是一段序列化的 PB 数据(例如 TDR 表用 char 字段存 PB,或 PB 表用 bytes 字段存 PB)。此时可以把它当作嵌套 message,继续按字段名或编号往里取,包括其中的嵌套结构、数组、map:

-- scalar_group.bytes_val 是一个内嵌 PB 的 bytes 字段
SELECT
    scalar_group.bytes_val.1,
    STRING(scalar_group.bytes_val.14['k10'].aname),
    scalar_group.bytes_val.all_type_array[0].aid
FROM tbl

buffer 偏移:若 bytes 字段里真正的 PB 数据前面还有 N 个字节的其它内容(如长度头),用 字段[起始字节 - 结束字节] 跳过这 N 字节再解析,-1 表示末尾。例如 additional[16 - -1].3.5 表示 additional 从第 16 字节解析到末尾、再按编号取 .3.5。仅支持 TDR 表的一级字段。

3.2.4 选取整个复杂字段

可以直接 SELECT 一个结构体/message/数组/map 的整体字段(不指定内部子字段),如 single_structall_typeall_type_arrayrstr。这类复杂字段会以编码形式输出(默认十六进制,导出时也可按需配置为 JSON 或 Base64)。

SELECT id, name, all_type, all_type_array FROM tbl

3.3 系统字段

下列系统字段不属于表定义,但可在 SELECT 和 WHERE 中直接引用:

系统字段 含义 时间类型
__last_access_time__ 记录最后修改时间(秒级)。注意:字段名虽含 access,实际语义是最后修改时间
__ttl__ 记录 TTL(秒)
__version__ 数据版本号
__index__ List 表元素的 index
__app_id__ 业务 ID
__zone_id__ 区 ID
__make_data_time__ 引擎数据构造的时间,即所导出的备份数据的生成时刻
__run_task_time__ 导出任务开始运行的时刻
-- 导出 app/zone 标识,并按最后修改时间过滤
SELECT __app_id__, __zone_id__, uin, name
FROM PlayerInfo
WHERE __last_access_time__ > '2025-01-01'

应用场景

  • __last_access_time__(最后修改时间)做活跃度过滤(如只导出近期有改动的记录)。
  • __version__ 导出数据版本,用 __app_id__ / __zone_id__ 标记数据来源。
  • __run_task_time__ / __make_data_time__ 作为时间基准,配合时间函数判断/计算记录相对该时刻的新旧(见 §3.4)。

时间类型的系统字段可与字符串形式的时间比较,如 __last_access_time__ < '2024-01-01''2024-01-01 00:00:00'

3.4 函数参考

函数名不区分大小写。下表按用途分组。

类型转换 / 类型提示

函数 参数 说明
INT / UINT / FLOAT / DOUBLE / STRING / BYTES 1 转换为目标类型,或为常量/字段标注类型
INT8 / INT16 / INT32 / INT64 / UINT8 / UINT16 / UINT32 / UINT64 1 整型位宽类型提示,常用于按 PB tag 访问或解析原始 bytes 时指明解释方式(默认 uint64

字符串函数

函数 参数 说明
LENGTH 1 字符串/bytes 的字节长度
SUBSTRING 2~3 SUBSTRING(s, begin[, len]),省略 len 取到末尾
CONCAT 1~100 拼接多个值为字符串(任一参数为空值则结果为空值)
UPPER / UCASE 1 转大写
LOWER / LCASE 1 转小写
TRIM / LTRIM / RTRIM 1 去除首尾/左侧/右侧空白
REVERSE 1 字符串反转
STRCMP 2 按字典序比较两个字符串,返回 -1/0/1

数学函数与算术

函数 / 运算符 参数 说明
+ - * / 算术运算,可用于 SELECT 列和 WHERE 比较中,如 total / 1024
ABS 1 绝对值
MOD 2 取模,MOD(a, b)
LEAST 2 取较小值
GREATEST 2 取较大值

时间函数(较少用)

函数 参数 说明
NOW / CURRENT_TIMESTAMP 0 当前时间
UNIX_TIMESTAMP 0 当前时间的 Unix 秒
DATEDIFF 2 两个时间相差的天数
TIMEDIFF 2 两个时间相差的秒数

时间函数应用场景:配合时间类型字段计算记录的"年龄"。例如以导出任务运行时刻为基准,计算记录距最后修改相差的天数:DATEDIFF(__run_task_time__, __last_access_time__)

容器与展开

函数 参数 说明
SIZE 1 数组或 map 的元素个数,如 SIZE(mailbox)
EXPAND_ARRAY 1~2 展开数组/map 成多行,见 §3.6

函数可以嵌套、可以与字段和算术组合使用:

SELECT
    uin,
    CONCAT(UPPER(name), '_', STRING(platid)) AS tag,
    LENGTH(SUBSTRING(name, 0, 8)) AS prefix_len
FROM PlayerInfo
WHERE ABS(score) > 100 AND MOD(uin, 100) = 0

3.5 WHERE 过滤(完整)

WHERE 支持的运算符:

  • 比较==/=!=/<><<=>>=。整型与浮点可跨精度比较;字符串按字典序比较;数值与字符串之间不可比较。
  • 逻辑ANDORNOT,可用括号分组。优先级从高到低为 比较NOTANDOR
  • 位运算&~<<>>
  • 集合IN (...)NOT IN (...)
  • 模糊匹配LIKENOT LIKE(仅字符串字段,默认忽略大小写,%/_ 通配)。
  • 数组包含array CONTAINS (子条件)array NOT CONTAINS (子条件),子条件中用 $ 引用当前数组元素。
  • 算术表达式参与比较:如 c_int32 / 1024 = 0
  • WHERE 中也可使用 §3.4 的全部函数。
-- 位运算:判断 flag 第 4 位(值 8)是否置位
SELECT uin, flag FROM PlayerInfo WHERE flag & 8

-- 数组包含:mailbox 中存在 title='tcaplus' 且未读的元素
SELECT uin FROM PlayerInfo WHERE mailbox CONTAINS (title = 'tcaplus' AND read = 0)

-- 标量数组:gameids 不包含 101
SELECT uin FROM PlayerInfo WHERE gameids NOT CONTAINS ($ = 101)

应用场景

  • 位运算常用于 flag/标志位判断,如 flag & 8 判断某个标志位是否置位。
  • CONTAINS 常用于判断数组(结构体数组用字段名,标量数组用 $)中是否存在满足条件的元素。

关键字冲突:若字段名与 SQL 关键字(如 keylikeupdate)相同,需用反引号转义,如 `key` > 100。关键字集合参考 MySQL 5.7 Keywords

3.6 数组展开(EXPAND_ARRAY,高级)

某些场景下,除了导出一级字段,还要把一个数组(PB repeated 字段或 map)里的每个元素都导出,并与一级字段联合成多行——相当于关系数据库中数组表与主表的 JOIN。

语法:EXPAND_ARRAY(数组路径 [, $.子字段])

  • 第 1 个参数是数组(或 map)字段路径。
  • 第 2 个参数可选,用 $ 引用当前元素,$.子字段 取元素的嵌套字段;省略则元素整体作为一列。
  • 一条记录若有 N 个数组元素,展开后生成 N 行。
  • 同一条 SQL 中多次调用 EXPAND_ARRAY,展开的必须是同一个数组
  • 数组路径与 $.子字段 同样支持按 PB 字段编号访问(见 §3.2.2),如 EXPAND_ARRAY($.12, $.1)

示例:PlayerInfo 表中 section_data_5 是 PB(DBPlayerPetInfo),其 pet_info.pet_datarepeated PetData(含 gidname)。把每个精灵展开为一行,与玩家一级字段联合:

SELECT
    EXPAND_ARRAY(section_data_5.pet_info.pet_data, $.name) AS pet_name,
    STRING(openid) AS openid,
    UINT32(platid) AS platid,
    EXPAND_ARRAY(section_data_5.pet_info.pet_data, $.gid) AS gid
FROM PlayerInfo

若某玩家有 N 个精灵,则该玩家展开为 N 行(精灵的 name/gid 与玩家的 openid/platid 联合)。

map 也支持展开,$.1 取 key、$.2 取 value(value 可继续取嵌套字段 $.2.aid):

SELECT
    id,
    EXPAND_ARRAY(int_map, $.1) AS map_key,
    EXPAND_ARRAY(int_map, $.2.aid) AS aid
FROM tbl

EXPAND_ARRAY 也可用于 WHERE,对展开后的元素做过滤:

SELECT id, EXPAND_ARRAY(all_type_array, $.aid) AS aid
FROM tbl
WHERE EXPAND_ARRAY(all_type_array, $.aid) > 150

3.7 CASE WHEN(高级)

支持类似 MySQL 的 CASE WHEN ... THEN ... [ELSE ...] END,用于把字段值映射成另一组值:

SELECT uin, name,
    CASE WHEN level > 100 AND level <= 200 THEN 0
         WHEN level > 200 AND level <= 500 THEN 1
         WHEN level > 500 AND level <= 1000 THEN 2
         ELSE 9999 END AS level_group
FROM PlayerInfo WHERE uin > 1000

可与函数结合:

SELECT uin,
    CASE WHEN LENGTH(name) > 5 THEN 'long' ELSE 'short' END AS name_len
FROM PlayerInfo

4. 综合示例

示例 1:TDR 内嵌 PB 的多级取值

TDR 表 PlayerInfoadditional 字段保存的是 PB(PlayerAdditionalData),要导出其中的 card_brief_info.card_signature,并补齐若干固定列:

SELECT
    additional.card_brief_info.card_signature AS card_signature,
    STRING(openid) AS openid,
    UINT32(platid) AS platid,
    INT(0) AS area_id,
    INT(9999) AS f5
FROM PlayerInfo

示例 2:map 嵌套取值 + 过滤

SELECT
    model_data['basic'].basic_info.declaration AS declaration,
    model_data['basic'].basic_info.guild_name AS guild_name,
    model_data['basic'].basic_info.guild_id AS guild_id
FROM GuildActorData
WHERE actor_id LIKE 'guild:%' AND actor_id NOT LIKE 'guild:%:%'

示例 3:EXPAND_ARRAY 多行展开

SELECT
    EXPAND_ARRAY(section_data_5.pet_info.pet_data, $.name) AS pet_name,
    STRING(openid) AS openid,
    EXPAND_ARRAY(section_data_5.pet_info.pet_data, $.gid) AS gid
FROM PlayerInfo

示例 4:按 PB 字段编号访问(无需 proto)

未提供 proto 时,用 $(整条记录)+ tag 编号直接取字段,并用类型函数指明类型:

SELECT
    $.1 AS id,
    STRING($.2) AS name,
    $.11.10 AS fix64
FROM tbl
WHERE $.3 > 1000

5. 与在线条件过滤语法的差异

维度 离线导出 SQL(本文) 在线条件过滤 / 更新
形态 完整 SELECT 查询(投影 + 转换 + 过滤) 请求上携带的 condition / operation 文本片段
字段投影(SELECT 列、别名、常量) 支持 不涉及
算术运算、CASE WHEN 支持 不支持
数组展开 EXPAND_ARRAY 支持 不支持
函数 类型/字符串/数学/时间等丰富函数 size/length/substring/类型转换等
过滤运算符 比较、逻辑、位运算、INLIKECONTAINS、算术比较 风格相近,但能力不完全一致
数据 静态备份数据 在线实时数据

在线语法详见《条件过滤和更新功能》与《条件过滤和更新语法说明》。

6. 注意事项与限制

  • 文本导出必须显式列出 SELECT 列,不支持 SELECT *
  • 字段名命中 SQL 关键字(keylikeupdate 等)时,需用反引号转义,如 `key`
  • 输出以 | 分隔;字符串中的 | 与换行符会被替换为空格。
  • 解析嵌套字段需要表的结构定义可用:TDR 嵌套需要 TDR 结构定义、PB 嵌套需要对应 proto;若改用 PB 字段编号(tag)访问,则可不依赖 proto(见 §3.2.2)。
  • 一条字段路径中的容器下标(数组 [i] / map ['k'])一般只出现一次;要遍历数组的全部元素,请用 EXPAND_ARRAY
  • 字符串/数学等函数遵循空值传播:任一参数为空值(NULL)时,结果为空值。

7. 相关文档

results matching ""

    No results matching ""