MariaDB 中是否有任何方法可以从 json 对象数组中搜索小于值

Is there any way in MariaDB to search for less than value from array of json objects

这是我的 json 文档:

[
  {
    "ID":1,
    "Label":"Price",
    "Value":399
  },
  {
    "ID":2,
    "Label":"Company",
    "Value":"Apple"
  },
  {
    "ID":2,
    "Label":"Model",
    "Value":"iPhone SE"
  },
]

这是我的 table:

 +----+------------------------------------------------------------------------------------------------------------------------------------+
 | ID | Properties                                                                                                                         |
 +----+------------------------------------------------------------------------------------------------------------------------------------+
 |  1 | [{"ID":1,"Label":"Price","Value":399},{"ID":2,"Label":"Company","Value":"Apple"},{"ID":3,"Label":"Model","Value":"iPhone SE"}]     |
 |  2 | [{"ID":1,"Label":"Price","Value":499},{"ID":2,"Label":"Company","Value":"Apple"},{"ID":3,"Label":"Model","Value":"iPhone X"}]      |
 |  3 | [{"ID":1,"Label":"Price","Value":699},{"ID":2,"Label":"Company","Value":"Apple"},{"ID":3,"Label":"Model","Value":"iPhone 11"}]     |
 |  4 | [{"ID":1,"Label":"Price","Value":999},{"ID":2,"Label":"Company","Value":"Apple"},{"ID":3,"Label":"Model","Value":"iPhone 11 Pro"}] |
 +----+------------------------------------------------------------------------------------------------------------------------------------+

这是我要在搜索查询中搜索的内容:

  SELECT *
  FROM mobiles
  WHERE ($.Label = "Price" AND $.Value < 400)
    AND ($.Label = "Model" AND $.Value = "iPhone SE")

上述查询仅供参考。我只是想传达我想做的事情。

我也知道 table 可以归一化为两个。但是这个 table 也是一个占位符 table 并且我们只是说它将保持不变。

我需要知道是否可以为以下运算符查询给定的 json 结构:>, >=, <, <=, BETWEEN AND, IN, NOT IN, LIKE, NOT LIKE, <>

由于MariaDB does not support JSON_TABLE(), and JSON_PATH只支持member/object选择器,这里过滤JSON不是那么简单。您可以尝试这个查询,它试图克服这些限制:

with a as (
  select 1 as id, '[{"ID":1,"Label":"Price","Value":399},{"ID":2,"Label":"Company","Value":"Apple"},{"ID":3,"Label":"Model","Value":"iPhone SE"}]' as properties union all
  select 2 as id, '[{"ID":1,"Label":"Price","Value":499},{"ID":2,"Label":"Company","Value":"Apple"},{"ID":3,"Label":"Model","Value":"iPhone X"}]' as properties union all
  select 3 as id, '[{"ID":1,"Label":"Price","Value":699},{"ID":2,"Label":"Company","Value":"Apple"},{"ID":3,"Label":"Model","Value":"iPhone 11"}]' as properties union all
  select 4 as id, '[{"ID":1,"Label":"Price","Value":999},{"ID":2,"Label":"Company","Value":"Apple"},{"ID":3,"Label":"Model","Value":"iPhone 11 Pro"}]' as properties
)
select *
from a
where json_value(a.properties,
    /*Get path to Price property and replace property name to Value*/
    replace(replace(json_search(a.properties, 'one', 'Price'), '"', ''), 'Label', 'Value')
  ) < 400
  and json_value(a.properties,
    /*And the same for Model name*/
    replace(replace(json_search(a.properties, 'one', 'Model'), '"', ''), 'Label', 'Value')
  ) = "iPhone SE"

| id | properties
+----+------------
| 1  | [{"ID":1,"Label":"Price","Value":399},{"ID":2,"Label":"Company","Value":"Apple"},{"ID":3,"Label":"Model","Value":"iPhone SE"}]

db<>fiddle 这里

我不会使用字符串函数。 MariaDB 中缺少的是将数组取消嵌套到行的能力——但它具有我们访问数据所需的所有 JSON 访问器。使用这些方法而不是字符串方法可以避免边缘情况,例如当值包含嵌入的双引号时。

您通常会在 table 数字的帮助下取消数组嵌套,这些数字的行数至少与最大数组中的元素数一样多。一种动态生成它的方法是 row_number() 针对具有足够行的 table - 比如 sometable.

您可以按如下方式取消嵌套数组:

select t.id,
    json_unquote(json_extract(t.properties, concat('$[', n.rn, '].Label'))) as label,
    json_unquote(json_extract(t.properties, concat('$[', n.rn, '].Value'))) as value
from mytable t
inner join (select row_number() over() - 1 as rn from sometable) n
    on n.rn < json_length(t.properties)

剩下的只是聚合:

select t.id
from (
    select t.id,
        json_unquote(json_extract(t.properties, concat('$[', n.rn, '].Label'))) as label,
        json_unquote(json_extract(t.properties, concat('$[', n.rn, '].Value'))) as value
    from mytable t
    inner join (select row_number() over() - 1 as rn from sometable) n
        on n.rn < json_length(t.properties)
) t
group by id
having 
    max(label = 'Price' and value + 0 < 400) = 1 
    and max(label = 'Model' and value = 'iPhone SE') = 1
    

Demo on DB Fiddle