我正在使用 MySQL 5.7,目前有一个 JSON 列,其中包含如下数据:
{"quiz_scores":[{"quiz_date":1697706210448,"symptom_improvement_score":false,"coping_skill_acquisition_score":true,"increased_self_awareness_score":true,"account_age_in_days":2197},{"quiz_date":1697706316322,"symptom_improvement_score":false,"coping_skill_acquisition_score":false,"increased_self_awareness_score":false,"account_age_in_days":2197},{"quiz_date":1697706347304,"symptom_improvement_score":true,"coping_skill_acquisition_score":true,"increased_self_awareness_score":true,"account_age_in_days":2197},{"quiz_date":1698229246050,"symptom_improvement_score":true,"coping_skill_acquisition_score":false,"increased_self_awareness_score":true,"account_age_in_days":2203},{"quiz_date":1698320799172,"symptom_improvement_score":true,"coping_skill_acquisition_score":true,"increased_self_awareness_score":true,"account_age_in_days":2204}]}
我可以使用
json_length
并减一来提取某些条件下的最新结果。
select
sp.user_uuid,
sp.stub,
FROM_UNIXTIME(JSON_EXTRACT(sp.`value`,CONCAT("$.quiz_scores[",JSON_LENGTH(sp.`value` ->> '$.quiz_scores')-1,"].quiz_date")) /1000) as quiz_date,
JSON_EXTRACT(sp.`value`,CONCAT("$.quiz_scores[",JSON_LENGTH(sp.`value` ->> '$.quiz_scores')-1,"].account_age_in_days")) as account_age_in_days
from series_progress sp
where sp.stub in ('questionnaire_my_progress')
and JSON_EXTRACT(sp.`value`,CONCAT("$.quiz_scores[",JSON_LENGTH(sp.`value` ->> '$.quiz_scores')-1,"].symptom_improvement_score")) = true
and JSON_EXTRACT(sp.`value`,CONCAT("$.quiz_scores[",JSON_LENGTH(sp.`value` ->> '$.quiz_scores')-1,"].coping_skill_acquisition_score")) = true
and JSON_EXTRACT(sp.`value`,CONCAT("$.quiz_scores[",JSON_LENGTH(sp.`value` ->> '$.quiz_scores')-1,"].increased_self_awareness_score")) = true
and JSON_EXTRACT(sp.`value`,CONCAT("$.quiz_scores[",JSON_LENGTH(sp.`value` ->> '$.quiz_scores')-1,"].account_age_in_days")) > 1
我想做的,不是只检查最新的分数,而是从 JSON 中获取所有分数。听起来MySQL 8中的
JSON_TABLE
可以完成这项工作,MySQL 5.7中有办法吗?
为此,请制作一个包含序数的小表格:
CREATE TABLE numbers ( n INTEGER UNSIGNED PRIMARY KEY );
INSERT INTO numbers (n) VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9), (10);
此表不必包含最多 232 的所有整数,但它至少需要与您需要支持的 JSON 数组的最大长度一样多。
然后您可以使用 JSON 将其交叉连接到您的表,并从 JSON 数组中获取每个元素。
SELECT
t.user_uuid,
t.stub,
FROM_UNIXTIME(t.quiz_score->>'$.quiz_date'/1000) AS quiz_date,
t.quiz_score->>'$.account_age_in_days' AS account_age_in_days
FROM (
SELECT
sp.user_uuid,
sp.stub,
JSON_EXTRACT(sp.`value`, CONCAT('$.quiz_scores[',n.n,']')) AS quiz_score
FROM series_progress AS sp
CROSS JOIN numbers AS n
WHERE n.n < JSON_LENGTH(sp.`value`->'$.quiz_scores')
AND sp.stub IN ('questionnaire_my_progress')
) AS t
WHERE t.quiz_score->>'$.symptom_improvement_score' = true
AND t.quiz_score->>'$.coping_skill_acquisition_score' = true
AND t.quiz_score->>'$.increased_self_awareness_score' = true
AND t.quiz_score->>'$.account_age_in_days' > 1;
老实说,当您需要使用 SQL 谓词在 JSON 文档中搜索或排序字段时,这样的解决方法显然表明 JSON 实际上并不是一个有效的想法。
回复您的评论:
您面临的问题是 MySQL 不会透明地将 JSON 布尔值转换为 SQL 布尔值。像
true
或 false
这样的 JSON 布尔值会以二进制字符串 'true'
或 'false'
的形式返回,而 MySQL 布尔值分别是无符号整数 1 和 0。
当我们使用
--column-type-info
选项运行时,我们可以在 MySQL 客户端中看到这一点:
mysql> set @j = '{"f1": true, "f2": false}';
mysql> select json_unquote(json_extract(@j, "$.f1")) as f1;
Field 1: `f1`
Catalog: `def`
Database: ``
Table: ``
Org_table: ``
Type: LONG_BLOB
Collation: utf8mb4_0900_ai_ci (255)
Length: 4294967295
Max_length: 4
Decimals: 31
Flags: BINARY
+------+
| f1 |
+------+
| true |
+------+
当您将此字符串与 SQL
true
(实际上是整数 1)进行比较时,它会迫使 MySQL 尝试将二进制字符串 'true'
转换为可比较的数值。 MySQL 通过检查任何前导数字将字符串转换为数值,如果没有前导数字,则假定字符串的数值为 0。因此 'true'
和 'false'
都转换为 0(假)。
这与此错误相关:https://bugs.mysql.com/bug.php?id=99237该错误的解决方法是使用新的MySQL 8.0函数JSON_VALUE()来提取布尔值和将其显式转换为无符号值。你会做类似的事情:
WHERE ...
JSON_VALUE(t.quiz_score, '$.coping_skill_acquisition_score' RETURNING UNSIGNED) = true
但是当然 JSON_VALUE() 在 MySQL 5.7 中没有实现。
您可以根据您的情况解决此问题,方法是将提取的值与 string
'true'
而不是布尔文字 true
进行比较。
WHERE t.quiz_score->>'$.symptom_improvement_score' = 'true'
AND t.quiz_score->>'$.coping_skill_acquisition_score' = 'true'
AND t.quiz_score->>'$.increased_self_awareness_score' = 'true'
这是另一个例子,说明为什么使用 JSON 来处理关系数据是一个糟糕的主意。您最终会花费大量时间研究类型转换的所有复杂语义来解决这些类型的错误。
进一步测试这一点,我发现,如果您不使用 JSON 取消引号,则 JSON 布尔值 可以正确转换。通过使用
->>
,您可以将布尔值转换为二进制字符串。但如果您使用了 ->
,那么 JSON 布尔值可以隐式转换为 SQL 布尔值。
以下工作,使用非不带引号的 JSON 提取并与 SQL 布尔文字进行比较:
WHERE t.quiz_score->'$.symptom_improvement_score' = true
AND t.quiz_score->'$.coping_skill_acquisition_score' = true
AND t.quiz_score->'$.increased_self_awareness_score' = true
越来越多的令人困惑的语义层!