【问题标题】:Select rows where nested json array field includes any of values from a provided array in PostgreSQL?选择嵌套 json 数组字段包含 PostgreSQL 中提供的数组中的任何值的行?
【发布时间】:2021-05-06 16:28:24
【问题描述】:

我正在尝试编写一个 sql 查询来查找表中与提供的 json 数组的任何值匹配的行。

更具体地说,我有以下 db 表:

CREATE TABLE mytable (
    name text,
    id SERIAL PRIMARY KEY,
    config json,
    matching boolean
);

INSERT INTO "mytable"(
  "name", "id", "config", "matching"
) 
VALUES 
  (
    E 'Name 1', 50,
    E '{"employees":[1,7],"industries":["1","3","4","13","14","16"],"levels":["1110","1111","1112","1113","1114"],"revenue":[0,5],"states":["AK","Al","AR","AZ","CA","CO","CT","DC","DE","FL","GA","HI","IA","ID","IL"]}', 
    TRUE
  ), 
  (
    E 'Name 2', 63,  
    E '{"employees":[3,5],"industries":["1"],"levels":["1110"],"revenue":[2,5],"states":["AK","AZ","CA","CO","HI","ID","MT","NM","NV","OR","UT","WA","WY"]}', 
    TRUE,
  ), 
  (
    E 'Name 3', 56,
    E '{"employees":[0,0],"industries":["14"],"levels":["1111"],"revenue":[7,7],"states":["AK","AZ","CA","CO","HI","ID","MT","NM","NV","OR","UT","WA","WY"]}', 
    TRUE,
  ),  
  (
    E 'Name 4', 61, 
    E '{"employees":[3,8],"industries":["1"],"levels":["1110"],"revenue":[0,5],"states":["AK","AZ","CA","CO","HI","ID","WA","WY"]}', 
    FALSE
  );

我需要使用给定的过滤参数对此表执行搜索查询。过滤参数基本对应config字段中的json键。它们来自客户端,看起来像这样:

{"employees": [1, 8], "industries": ["12", "5"]}
{"states": ["LA", "WA", "CA"], "levels": ["1100", "1100"], "employees": [3]}

给定这样的过滤器,我需要在我的表中找到包含所提供的每个过滤器键的相应过滤器键中的任何数组元素的行。

因此,给定过滤器{"employees": [1, 8], "industries": ["12", "5"]},查询必须返回所有行,其中(config 字段中的employees 键包含18 AND 其中@987654329 @key in config 字段包含125);

我需要从 javascript 代码中动态生成这样的查询,以便我可以通过添加/删除 AND 运算符的某个参数来包含/排除过滤。

到目前为止,我所拥有的是一个超级长时间运行的查询,它会在 config 字段中生成所有可能的数组元素组合,感觉非常错误:

select * from mytable
    cross join lateral json_array_elements(config->'employees') as e1
    cross join lateral json_array_elements(config->'states') as e2
    cross join lateral json_array_elements(config->'levels') as e3
    cross join lateral json_array_elements(config->'revenue') as e4;

我也尝试过这样做:

select * from mytable
    where
        matching = TRUE
        and (config->'employees')::jsonb @> ANY(ARRAY ['[1, 7, 8]']::jsonb[])
        and (config->'states')::jsonb @> ANY(ARRAY ['["AK", "AZ"]']::jsonb[])
        and ........;

然而这并没有奏效,尽管看起来很有希望。

另外,我尝试过使用?| 运算符,但无济于事。


基本上,我需要的是:给定一个 json 字段中的数组键,检查该字段是否包含另一个数组中提供的任何值(这是我的过滤参数);我必须动态地为多个过滤参数执行此操作。

所以逻辑如下:

select all rows from the table
   *where*
   matching = TRUE
   *and* config->key1 includes any of the keys from [5,6,8,7]
   *and* config->key2 includes any of the keys from [8,6,2]
   *and* so forth;

你能帮我实现这样一个 sql 查询吗?

或者也许这样的 sql 查询总是非常慢,最好在数据库级别之外进行这样的过滤?

【问题讨论】:

    标签: sql postgresql plpgsql jsonb


    【解决方案1】:

    我会尝试类似的东西。我想有一定的副作用(例如,如果比较数据为空怎么办?)而且我没有在更大的数据集上测试它......这只是我想到的第一个......:

    demo:db<>fiddle

    SELECT 
        *
    FROM
        mytable t
    JOIN (SELECT '{"states": ["LA", "WA", "CA"], "levels": ["1100", "1100"], "employees": [3]}'::json as data) c 
    ON 
      CASE WHEN c.data -> 'employees' IS NOT NULL THEN
         ARRAY(SELECT json_array_elements_text(t.config -> 'employees')) && ARRAY(SELECT json_array_elements_text(c.data -> 'employees'))
      ELSE TRUE END
      
      AND
      
      CASE WHEN c.data -> 'industries' IS NOT NULL THEN
         ARRAY(SELECT json_array_elements_text(t.config -> 'industries')) && ARRAY(SELECT json_array_elements_text(c.data -> 'industries'))
      ELSE TRUE END
      
      AND
      
      CASE WHEN c.data -> 'states' IS NOT NULL THEN
         ARRAY(SELECT json_array_elements_text(t.config -> 'states')) && ARRAY(SELECT json_array_elements_text(c.data -> 'states'))
      ELSE TRUE END
      
      AND
      
      CASE WHEN c.data -> 'revenue' IS NOT NULL THEN
         ARRAY(SELECT json_array_elements_text(t.config -> 'revenue')) && ARRAY(SELECT json_array_elements_text(c.data -> 'revenue'))
      ELSE TRUE END
      
      AND
      
      CASE WHEN c.data -> 'levels' IS NOT NULL THEN
         ARRAY(SELECT json_array_elements_text(t.config -> 'levels')) && ARRAY(SELECT json_array_elements_text(c.data -> 'levels'))
      ELSE TRUE END
    

    连接条件说明:

          CASE WHEN c.data -> 'levels' IS NOT NULL THEN
             ARRAY(SELECT json_array_elements_text(t.config -> 'levels')) && ARRAY(SELECT json_array_elements_text(c.data -> 'levels'))
          ELSE TRUE END
    

    如果您的比较数据不包含特定属性,则条件为true,因此将被忽略。如果它包含一个属性,则通过将两个 JSON 数组转换为简单的 Postgres 数组来比较该属性的表和比较数组

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-03-12
      • 2015-10-23
      • 2022-11-28
      • 2015-11-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-11-25
      相关资源
      最近更新 更多