

# 处理 JSON 格式的 Amazon RDS 数据 API 查询结果
<a name="data-api-json"></a>

 当您调用 `ExecuteStatement` 操作时，可以选择将查询结果作为 JSON 格式的字符串返回。这样，您就可以使用编程语言的 JSON 解析功能来解释和重新格式化结果集。这样做有助于避免编写额外的代码来循环浏览结果集并解释每个列值。

 要请求 JSON 格式的结果集，您需要传递值为 `JSON` 的可选 `formatRecordsAs` 参数。JSON 格式的结果集将在 `ExecuteStatementResponse` 结构的 `formattedRecords` 字段中返回。

 `BatchExecuteStatement` 操作不返回结果集。因此，JSON 选项不适用于该操作。

 要自定义 JSON 哈希结构中的键，请在结果集中定义列别名。为此，您可以使用 SQL 查询的列列表中的 `AS` 子句。

 您可以使用 JSON 功能使结果集更易于阅读，并将其内容映射到特定于语言的框架。由于 ASCII 编码的结果集的卷大于默认表示形式，因此如果查询返回大量的行或较大的列值，占用的内存超过应用程序可用的内存，则您可以选择默认表示形式。

**Topics**
+ [检索 JSON 格式的查询结果](#data-api-json-querying)
+ [数据类型映射](#data-api-json-datatypes)
+ [故障排除](#data-api-json-troubleshooting)
+ [示例](#data-api-json-examples)

## 检索 JSON 格式的查询结果
<a name="data-api-json-querying"></a>

 要以 JSON 字符串的形式接收结果集，请在 `ExecuteStatement` 调用中包括 `.withFormatRecordsAs(RecordsFormatType.JSON)`。返回值在 `formattedRecords` 字段中以 JSON 字符串的形式返回。在本例中，`columnMetadata` 为 `null`。列标签是表示每行的对象的键。这些列名称在结果集中的每一行都会重复出现。列值是带引号的字符串、数值或表示 `true`、`false` 或 `null` 的特殊值。JSON 响应中不会保留列元数据，例如长度约束以及数字和字符串的精确类型。

 如果您省略 `.withFormatRecordsAs()` 调用或指定 `NONE` 的参数，系统将使用 `Records` 和 `columnMetadata` 字段以二进制格式返回结果集。

## 数据类型映射
<a name="data-api-json-datatypes"></a>

 结果集中的 SQL 值映射到一组较小的 JSON 类型。这些值在 JSON 中表示为字符串、数字和一些特殊常量，例如 `true`、`false` 和`null`。您可以根据编程语言的不同，使用强类型或弱类型将这些值转换为应用程序中的变量。


****  

|   JDBC 数据类型   |   JSON 数据类型   | 
| --- | --- | 
|   `INTEGER`, `TINYINT`, `SMALLINT`, `BIGINT`   |   默认情况下为数字。如果 `LongReturnType` 选项设置为 `STRING`，则为字符串。  | 
|   `FLOAT`, `REAL`, `DOUBLE`   |   数字   | 
|   `DECIMAL`   |   默认情况下为字符串。如果 `DecimalReturnType` 选项设置为 `DOUBLE_OR_LONG`，则为数字。  | 
|   `STRING`   |   字符串   | 
|   `BOOLEAN`, `BIT`   |   布尔值   | 
|   `BLOB`, `BINARY`, `VARBINARY`, `LONGVARBINARY`   |   base64 编码的字符串。  | 
|   `CLOB`   |   字符串   | 
|   `ARRAY`   |   数组   | 
|   `NULL`   |   `null`   | 
|   其他类型（包括与日期和时间有关的类型）   |   字符串   | 

## 故障排除
<a name="data-api-json-troubleshooting"></a>

 JSON 响应限制为 10 兆字节。如果响应超过此限制，则您的程序会收到 `BadRequestException` 错误。在这种情况下，您可以使用以下方法之一解决该错误：
+  减少结果集中的行数。为此，请添加 `LIMIT` 子句。您可以通过提交多个带有 `LIMIT` 和 `OFFSET` 子句的查询，将一个大型结果集拆分为多个较小的结果集。

   如果结果集中包含被应用程序逻辑筛选掉的行，您可以通过在 `WHERE` 子句中添加更多条件，从结果集中删除这些行。
+  减少结果集中的列数。为此，请从查询的选择列表中删除项目。
+  通过在查询中使用列别名来缩短列标签。对于结果集中的每一行，每个列名都会在 JSON 字符串中重复出现。因此，具有长列名和许多行的查询结果可能会超过大小限制。特别是，对复杂的表达式使用列别名，以避免在 JSON 字符串中重复整个表达式。
+  虽然在 SQL 中可以使用列别名来生成具有多个同名列的结果集，但在 JSON 中不允许有重复的键名。如果您请求 JSON 格式的结果集，并且多个列具有相同名称，则 RDS Data API 将返回错误。因此，请确保所有列标签都具有唯一的名称。

## 示例
<a name="data-api-json-examples"></a>

 以下 Java 示例演示了如何调用 `ExecuteStatement` 并将响应作为 JSON 格式的字符串，然后解释结果集。用适当的值替换 *databaseName*、*secretStoreArn* 和 *clusterArn* 参数。

 以下 Java 示例演示了一个在结果集中返回十进制数值的查询。`assertThat` 调用会测试响应的字段是否具有基于 JSON 结果集规则的预期属性。

 该示例适用于以下架构和示例数据：

```
create table test_simplified_json (a float);
insert into test_simplified_json values(10.0);
```

```
public void JSON_result_set_demo() {
    var sql = "select * from test_simplified_json";
    var request = new ExecuteStatementRequest()
      .withDatabase(databaseName)
      .withSecretArn(secretStoreArn)
      .withResourceArn(clusterArn)
      .withSql(sql)
      .withFormatRecordsAs(RecordsFormatType.JSON);
    var result = rdsdataClient.executeStatement(request);
}
```

 之前程序中的 `formattedRecords` 字段的值为：

```
[{"a":10.0}]
```

 由于存在 JSON 结果集，响应中的 `Records` 和 `ColumnMetadata` 字段均为 null。

 以下 Java 示例演示了一个在结果集中返回整数数值的查询。该示例调用 `getFormattedRecords` 以仅返回 JSON 格式的字符串，并忽略其他空白或为 null 的响应字段。该示例将结果反序列化为一个表示记录列表的结构。每条记录都有一些字段，字段名称对应于结果集中的列别名。此技术简化了解析结果集的代码。您的应用程序不必循环浏览结果集的行和列并将每个值转换为适当类型。

 该示例适用于以下架构和示例数据：

```
create table test_simplified_json (a int);
insert into test_simplified_json values(17);
```

```
public void JSON_deserialization_demo() {
    var sql = "select * from test_simplified_json";
    var request = new ExecuteStatementRequest()
      .withDatabase(databaseName)
      .withSecretArn(secretStoreArn)
      .withResourceArn(clusterArn)
      .withSql(sql)
      .withFormatRecordsAs(RecordsFormatType.JSON);
    var result = rdsdataClient.executeStatement(request)
      .getFormattedRecords();

/* Turn the result set into a Java object, a list of records.
   Each record has a field 'a' corresponding to the column
   labelled 'a' in the result set. */
    private static class Record { public int a; }
    var recordsList = new ObjectMapper().readValue(
        response, new TypeReference<List<Record>>() {
        });
}
```

 之前程序中的 `formattedRecords` 字段的值为：

```
[{"a":17}]
```

 要检索结果行 0 的 `a` 列，应用程序将引用 `recordsList.get(0).a`。

 相比之下，以下 Java 示例显示了在不使用 JSON 格式的情况下，构建保存结果集的数据结构所需的代码类型。在这种情况下，结果集的每一行都包含一些字段，其中包含有关单个用户的信息。构建表示结果集的数据结构需要循环浏览这些行。对于每一行，代码会检索每个字段的值，执行适当的类型转换，并将结果分配给表示该行的对象中的相应字段。然后，代码将表示每个用户的对象添加到表示整个结果集的数据结构中。如果将查询更改为重新排序、添加或删除结果集中的字段，则也必须更改应用程序代码。

```
/* Verbose result-parsing code that doesn't use the JSON result set format */
for (var row: response.getRecords()) {
    var user = User.builder()
      .userId(row.get(0).getLongValue())
      .firstName(row.get(1).getStringValue())
      .lastName(row.get(2).getStringValue())
      .dob(Instant.parse(row.get(3).getStringValue()))
      .build();
    result.add(user);
  }
```

 以下示例值显示了具有不同列数、列别名和列数据类型的结果集的 `formattedRecords` 字段的值。

 如果结果集包含多行，则每一行都表示为一个作为数组元素的对象。结果集中的每一列都成为对象中的一个键。这些键在结果集中的每一行都会重复出现。因此，对于包含许多行和列的结果集，您可能需要定义短列别名，以避免超出整个响应的长度限制。

 该示例适用于以下架构和示例数据：

```
create table sample_names (id int, name varchar(128));
insert into sample_names values (0, "Jane"), (1, "Mohan"), (2, "Maria"), (3, "Bruce"), (4, "Jasmine");
```

```
[{"id":0,"name":"Jane"},{"id":1,"name":"Mohan"},
{"id":2,"name":"Maria"},{"id":3,"name":"Bruce"},{"id":4,"name":"Jasmine"}]
```

 如果结果集中的某一列被定义为表达式，则该表达式的文本将成为 JSON 键。因此，为查询的选择列表中的每个表达式定义一个描述性列别名通常很方便。例如，以下查询在其选择列表中包含诸如函数调用和算术运算之类的表达式。

```
select count(*), max(id), 4+7 from sample_names;
```

 这些表达式将作为键传递到 JSON 结果集。

```
[{"count(*)":5,"max(id)":4,"4+7":11}]
```

 添加带有描述性标签的 `AS` 列可以简化 JSON 结果集中键的解释。

```
select count(*) as rows, max(id) as largest_id, 4+7 as addition_result from sample_names;
```

 在修订后的 SQL 查询中，使用由 `AS` 子句定义的列标签作为键名。

```
[{"rows":5,"largest_id":4,"addition_result":11}]
```

 JSON 字符串中每个键值对的值可以是带引号的字符串。该字符串可能包含 Unicode 字符。如果该字符串包含转义序列或者 `"` 或 `\` 字符，则这些字符前面必须有反斜杠转义字符。以下 JSON 字符串示例演示了这些可能性。例如，`string_with_escape_sequences` 结果包含特殊字符：退格符、换行符、回车符、制表符、换页符和 `\`。

```
[{"quoted_string":"hello"}]
[{"unicode_string":"邓不利多"}]
[{"string_with_escape_sequences":"\b \n \r \t \f \\ '"}]
```

 JSON 字符串中每个键值对的值也可以表示一个数字。该数字可能是整数、浮点值、负值或以指数表示法表示的值。以下 JSON 字符串示例演示了这些可能性。

```
[{"integer_value":17}]
[{"float_value":10.0}]
[{"negative_value":-9223372036854775808,"positive_value":9223372036854775807}]
[{"very_small_floating_point_value":4.9E-324,"very_large_floating_point_value":1.7976931348623157E308}]
```

 布尔值和 null 值用不带引号的特殊关键字 `true`、`false` 和 `null` 表示。以下 JSON 字符串示例演示了这些可能性。

```
[{"boolean_value_1":true,"boolean_value_2":false}]
[{"unknown_value":null}]
```

 如果选择 BLOB 类型的值，则结果将在 JSON 字符串中表示为 base64 编码值。要将值转换回原始表示形式，可以使用应用程序语言中的相应解码函数。例如，在 Java 中调用函数 `Base64.getDecoder().decode()`。以下示例输出显示了选择 `hello world` 的 BLOB 值并将结果集作为 JSON 字符串返回的结果。

```
[{"blob_column":"aGVsbG8gd29ybGQ="}]
```

 以下 Python 示例展示了如何访问调用 Python `execute_statement` 函数的结果中的值。结果集是字段 `response['formattedRecords']` 中的字符串值。该代码通过调用 `json.loads` 函数，将 JSON 字符串转换为数据结构。然后，结果集的每一行都是数据结构中的一个列表元素，在每一行中，您可以按名称引用结果集的每个字段。

```
import json

result = json.loads(response['formattedRecords'])
print (result[0]["id"])
```

 以下 JavaScript 示例展示了如何访问调用 JavaScript `executeStatement` 函数的结果中的值。结果集是字段 `response.formattedRecords` 中的字符串值。该代码通过调用 `JSON.parse` 函数，将 JSON 字符串转换为数据结构。然后，结果集的每一行都是数据结构中的一个数组元素，在每一行中，您可以按名称引用结果集的每个字段。

```
<script>
    const result = JSON.parse(response.formattedRecords);
    document.getElementById("display").innerHTML = result[0].id;
</script>
```