67、SQL Server JSON支持:JSON_VALUE、JSON_QUERY、OPENJSON、C语言处理JSON
说实话,JSON 这东西在数据库里越来越常见了。早些年我们做嵌入式数据库开发,数据格式基本都是定长字段、结构体硬编码,谁要是说用 JSON 存数据,大家会觉得你疯了。但现在不一样了,物联网设备上报的数据、配置文件的动态字段、甚至日志系统,JSON 几乎成了标配。
SQL Server 从 2016 版本开始就原生支持 JSON 了,提供了 JSON_VALUE、JSON_QUERY、OPENJSON 这几个核心函数。我在一个工业数据采集项目里,就靠它们把设备上报的 JSON 数据直接解析入库,省掉了中间层 C 代码的 JSON 解析步骤,性能提升很明显。
今天我们就来聊聊,怎么在 C 语言里调用 SQL Server 的 JSON 函数,以及我自己踩过的那些坑。
JSON_VALUE:提取标量值
JSON_VALUE 用来从 JSON 字符串中提取一个标量值(字符串、数字、布尔值)。说白了,就是你要拿 JSON 里某个字段的值,用这个函数就对了。
语法很简单:
JSON_VALUE ( expression , path )
其中 expression 是 JSON 字符串,path 是类似 '$.key.subkey' 的路径表达式。
举个例子,假设设备上报了这样的 JSON:
{
"deviceId": "SN-001",
"temperature": 36.5,
"status": "online"
}
在 SQL 里这样提取:
SELECT
JSON_VALUE(@json, '$.deviceId') AS DeviceId,
JSON_VALUE(@json, '$.temperature') AS Temperature,
JSON_VALUE(@json, '$.status') AS Status;
JSON_QUERY:提取对象或数组
当你要从 JSON 里取出一整个子对象或数组时,就得用 JSON_QUERY。它返回的是 JSON 片段,不是标量。
比如上面的 JSON,如果我想取出整个设备信息作为一个 JSON 对象:
SELECT JSON_QUERY(@json, '$') AS FullObject;
或者提取一个数组:
-- 假设 JSON 里有 "sensors": [{"type":"temp","value":25}, {"type":"humidity","value":60}]
SELECT JSON_QUERY(@json, '$.sensors') AS SensorArray;
$ 开头,而且路径中不能包含通配符。如果你需要更复杂的查询,可以考虑用 OPENJSON。
OPENJSON:行集函数,把 JSON 变成表
OPENJSON 是我用得最多的 JSON 函数。它能把 JSON 数组或对象展开成一张表,每一行对应一个元素。这在 C 语言里处理起来特别方便,因为你可以直接用 SQL 查询结果集,逐行读取。
基本用法:
SELECT * FROM OPENJSON(@json, '$.sensors')
WITH (
type VARCHAR(50) '$.type',
value FLOAT '$.value'
);
这段代码会把 sensors 数组里的每个元素解析成一行,type 和 value 作为列。你想想看,这比在 C 代码里手动遍历 JSON 数组要省多少事?
C 语言中调用 SQL Server JSON 函数
好了,理论说完了,咱们来点实战。在 C 语言里操作 SQL Server,通常用 ODBC 或者 SQL Server Native Client。我个人习惯用 ODBC,因为跨平台兼容性好一些。
下面是一个完整的示例,演示如何用 C 语言调用 OPENJSON 解析设备上报的 JSON 数据:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sql.h>
#include <sqlext.h>
void process_json_data(SQLHSTMT hstmt) {
const char* json_input =
"[{\"deviceId\":\"SN-001\",\"temp\":36.5,\"ts\":\"2024-01-15T10:30:00\"},"
"{\"deviceId\":\"SN-002\",\"temp\":37.2,\"ts\":\"2024-01-15T10:30:05\"}]";
// 构建 SQL 语句,使用 OPENJSON 解析
char sql[1024];
snprintf(sql, sizeof(sql),
"SELECT deviceId, temp, ts "
"FROM OPENJSON(?) "
"WITH ("
" deviceId VARCHAR(50) '$.deviceId',"
" temp FLOAT '$.temp',"
" ts DATETIME2 '$.ts'"
");"
);
// 绑定参数
SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT,
SQL_C_CHAR, SQL_VARCHAR,
strlen(json_input), 0,
(SQLPOINTER)json_input, 0, NULL);
// 执行查询
SQLExecDirect(hstmt, (SQLCHAR*)sql, SQL_NTS);
// 读取结果
char deviceId[50];
double temp;
char ts[30];
while (SQLFetch(hstmt) == SQL_SUCCESS) {
SQLGetData(hstmt, 1, SQL_C_CHAR, deviceId, sizeof(deviceId), NULL);
SQLGetData(hstmt, 2, SQL_C_DOUBLE, &temp, 0, NULL);
SQLGetData(hstmt, 3, SQL_C_CHAR, ts, sizeof(ts), NULL);
printf("设备: %s, 温度: %.1f, 时间: %s\n", deviceId, temp, ts);
}
}
int main() {
// 这里省略了 ODBC 连接和句柄初始化的代码
// 实际项目中需要先建立数据库连接
printf("JSON 数据处理完成\n");
return 0;
}
核心思路:把 JSON 解析工作交给数据库,C 代码只负责传参和读结果。这样既利用了数据库的索引和优化能力,又减少了 C 代码的复杂度。
避坑指南
我曾经在一个项目中,用 JSON_VALUE 去提取一个嵌套对象,结果一直返回 NULL。查了半天才发现,那个字段其实是个 JSON 对象,不是标量。换成 JSON_QUERY 就好了。
还有一次,我用 OPENJSON 解析一个很大的 JSON 数组(几万条记录),结果查询超时了。后来加了 WITH 子句里的类型声明,性能提升了不少。嗯,这里要注意:OPENJSON 的 WITH 子句里一定要显式指定数据类型,否则 SQL Server 会默认用 NVARCHAR(MAX),性能会差很多。
知识体系总览
下面这张图总结了 SQL Server JSON 支持的三个核心函数及其在 C 语言中的调用关系:
性能对比与选择建议
| 函数 | 适用场景 | 返回类型 | 性能特点 |
|---|---|---|---|
| JSON_VALUE | 提取单个字段值(字符串、数字) | 标量(NVARCHAR、INT、FLOAT 等) | 快,适合简单查询 |
| JSON_QUERY | 提取子对象或数组片段 | NVARCHAR(MAX)(JSON 格式) | 中等,适合嵌套结构 |
| OPENJSON | 将 JSON 数组展开为多行记录 | 行集(可配合 WITH 定义列) | 适合批量处理,注意类型声明 |
好了,关于 SQL Server 的 JSON 支持就聊到这里。这几个函数配合 C 语言的 ODBC 接口,能让你在处理 JSON 数据时游刃有余。记住一点:能用数据库做的事,尽量别在 C 代码里做。数据库的优化器比你手写的解析代码要聪明得多。