利用 JSON_EXTRACT() 函数进行嵌套 JSON 查询
大家好,今天我们来深入探讨 MySQL 中 JSON_EXTRACT() 函数在处理嵌套 JSON 数据时的应用。JSON_EXTRACT() 是一个强大的工具,它允许我们从 JSON 文档中提取特定路径下的数据,尤其是在处理复杂、嵌套的 JSON 结构时,其作用更加明显。
1. JSON_EXTRACT() 函数的基本语法
首先,我们回顾一下 JSON_EXTRACT() 的基本语法:
JSON_EXTRACT(json_doc, path[, path] ...)
json_doc: 包含 JSON 数据的字符串或列。path: 一个或多个 JSON 路径表达式,用于指定要提取的数据的位置。路径表达式以$开头,表示 JSON 文档的根节点。
2. JSON 路径表达式的构成
JSON 路径表达式是 JSON_EXTRACT() 的核心,它决定了我们如何精确地定位到 JSON 文档中的目标数据。路径表达式可以包含以下元素:
$: 表示根节点。.key: 用于访问 JSON 对象中的键值对。[index]: 用于访问 JSON 数组中的元素,索引从 0 开始。[*]: 用于访问 JSON 数组中的所有元素。**: 用于递归搜索,匹配所有层级的元素。这个用法需要 MySQL 8.0.17 及以上版本。
3. 嵌套 JSON 数据的例子
为了更好地理解 JSON_EXTRACT() 在嵌套 JSON 查询中的应用,我们先创建一个包含嵌套 JSON 数据的表。
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
order_details JSON
);
INSERT INTO orders (order_details) VALUES
(
'
{
"customer": {
"name": "Alice Smith",
"email": "[email protected]",
"address": {
"street": "123 Main St",
"city": "Anytown",
"zipcode": "12345"
}
},
"items": [
{
"product_id": 101,
"product_name": "Laptop",
"quantity": 1,
"price": 1200.00
},
{
"product_id": 102,
"product_name": "Mouse",
"quantity": 1,
"price": 25.00
}
],
"order_date": "2023-10-27",
"total_amount": 1225.00
}
'
),
(
'
{
"customer": {
"name": "Bob Johnson",
"email": "[email protected]",
"address": {
"street": "456 Oak Ave",
"city": "Somewhereville",
"zipcode": "67890"
}
},
"items": [
{
"product_id": 201,
"product_name": "Keyboard",
"quantity": 1,
"price": 75.00
},
{
"product_id": 202,
"product_name": "Monitor",
"quantity": 1,
"price": 300.00
}
],
"order_date": "2023-10-28",
"total_amount": 375.00
}
'
);
现在,我们有了 orders 表,其中 order_details 列存储了包含客户信息、商品信息和订单信息的 JSON 数据。
4. 提取嵌套对象中的数据
假设我们需要提取每个订单中客户的姓名和城市。我们可以使用以下查询:
SELECT
JSON_EXTRACT(order_details, '$.customer.name') AS customer_name,
JSON_EXTRACT(order_details, '$.customer.address.city') AS customer_city
FROM
orders;
这个查询使用了 JSON_EXTRACT() 函数,并通过路径表达式 $.customer.name 和 $.customer.address.city 分别提取了客户姓名和城市。
| customer_name | customer_city |
|---|---|
| "Alice Smith" | "Anytown" |
| "Bob Johnson" | "Somewhereville" |
5. 提取数组中的数据
如果我们需要提取每个订单中第一个商品的名称,可以使用以下查询:
SELECT
JSON_EXTRACT(order_details, '$.items[0].product_name') AS first_product_name
FROM
orders;
在这个查询中,$.items[0].product_name 路径表达式首先访问了 items 数组,然后使用 [0] 获取了数组中的第一个元素,最后提取了 product_name 属性。
| first_product_name |
|---|
| "Laptop" |
| "Keyboard" |
6. 提取数组中的所有元素
如果我们想提取每个订单中所有商品的名称,可以使用 [*] 通配符:
SELECT
JSON_EXTRACT(order_details, '$.items[*].product_name') AS all_product_names
FROM
orders;
这个查询会返回一个 JSON 数组,其中包含了所有商品的名称。
| all_product_names |
|---|
| ["Laptop", "Mouse"] |
| ["Keyboard", "Monitor"] |
如果我们想将数组展开成多行,可以使用 JSON_TABLE() 函数 (MySQL 8.0 及以上版本):
SELECT
ot.product_name
FROM
orders
CROSS JOIN JSON_TABLE(
order_details,
'$.items[*]'
COLUMNS (
product_name VARCHAR(255) PATH '$.product_name'
)
) AS ot;
这个查询首先使用 JSON_TABLE() 函数将 items 数组展开成一个临时表,然后从这个临时表中提取 product_name 属性。
| product_name |
|---|
| "Laptop" |
| "Mouse" |
| "Keyboard" |
| "Monitor" |
7. 使用条件进行过滤
JSON_EXTRACT() 可以与其他 SQL 语句结合使用,例如 WHERE 子句,根据 JSON 数据中的值进行过滤。
例如,要查找所有总金额大于 1000 的订单,可以使用以下查询:
SELECT
id
FROM
orders
WHERE
JSON_EXTRACT(order_details, '$.total_amount') > 1000;
这个查询首先使用 JSON_EXTRACT() 提取 total_amount,然后使用 WHERE 子句过滤结果。
| id |
|---|
| 1 |
要查找所有购买了 "Laptop" 的订单,可以使用 JSON_CONTAINS() 函数:
SELECT id
FROM orders
WHERE JSON_CONTAINS(order_details, '{"product_name": "Laptop"}', '$.items');
更精确的查找需要配合JSON_SEARCH()函数, 查找items数组中product_name为"Laptop"的订单
SELECT id
FROM orders
WHERE JSON_SEARCH(order_details, 'one', 'Laptop', NULL, '$.items[*].product_name') IS NOT NULL;
8. 使用 `` 进行递归搜索 (MySQL 8.0.17 及以上)**
** 路径表达式允许我们递归地搜索 JSON 文档中的所有层级。 例如,如果我们想找到 JSON 文档中所有包含 "city" 键的路径,可以使用以下查询:
假设我们的 orders 表中新增了更深层嵌套的数据:
INSERT INTO orders (order_details) VALUES (
'{
"company": {
"location": {
"hq": {
"address": {
"city": "New York"
}
},
"regional_offices": [
{
"address": {
"city": "Los Angeles"
}
},
{
"address": {
"city": "Chicago"
}
}
]
}
}
}'
);
现在,我们可以使用 ** 递归搜索 city:
SELECT JSON_EXTRACT(order_details, '$**.city') FROM orders WHERE id = 3;
这个查询会返回一个 JSON 数组,其中包含了所有找到的 city 的值。
| JSON_EXTRACT(order_details, ‘$**.city’) |
|---|
| ["New York", "Los Angeles", "Chicago"] |
9. JSON_UNQUOTE() 用于去除引号
JSON_EXTRACT()返回的结果通常带有引号,如果需要去除引号,可以使用JSON_UNQUOTE()函数。
SELECT
JSON_UNQUOTE(JSON_EXTRACT(order_details, '$.customer.name')) AS customer_name,
JSON_UNQUOTE(JSON_EXTRACT(order_details, '$.customer.address.city')) AS customer_city
FROM
orders;
| customer_name | customer_city |
|---|---|
| Alice Smith | Anytown |
| Bob Johnson | Somewhereville |
10. 错误处理
当路径表达式无效或者 JSON 文档不符合预期结构时,JSON_EXTRACT() 函数可能会返回 NULL。 为了避免这种情况,可以使用 IFNULL() 或 COALESCE() 函数来提供默认值。
例如,如果 customer.address.city 可能不存在,可以使用以下查询:
SELECT
customer_name,
IFNULL(JSON_UNQUOTE(JSON_EXTRACT(order_details, '$.customer.address.city')), 'Unknown') AS customer_city
FROM
(SELECT JSON_UNQUOTE(JSON_EXTRACT(order_details, '$.customer.name')) AS customer_name, order_details FROM orders) AS subquery;
如果 customer.address.city 不存在,则返回 "Unknown"。
总结:JSON_EXTRACT() 的强大之处
JSON_EXTRACT() 函数在处理嵌套 JSON 数据时非常强大,它允许我们通过路径表达式精确地定位和提取所需的数据。结合 JSON_TABLE()、JSON_CONTAINS()、JSON_SEARCH() 和 JSON_UNQUOTE() 等函数,我们可以更加灵活地处理 JSON 数据,并将其集成到 SQL 查询中。
总结:灵活运用路径表达式是关键
掌握 JSON 路径表达式的语法是使用 JSON_EXTRACT() 的关键。 通过合理地构建路径表达式,我们可以轻松地从复杂的 JSON 文档中提取所需的数据,并与其他 SQL 功能结合使用,实现更高级的查询和分析。
总结:错误处理和数据清洗很重要
在使用 JSON_EXTRACT() 时,需要注意错误处理和数据清洗,确保查询的稳定性和准确性。 使用 IFNULL()、COALESCE() 和 JSON_UNQUOTE() 等函数可以有效地处理缺失值和去除引号,提高数据的质量。