Sql 基于任意jsonb筛选器表达式,从具有jsonb列的表中选择行

Sql 基于任意jsonb筛选器表达式,从具有jsonb列的表中选择行,sql,json,postgresql,jsonb,postgresql-9.6,Sql,Json,Postgresql,Jsonb,Postgresql 9.6,测试数据 DROP TABLE t; CREATE TABLE t(_id serial PRIMARY KEY, data jsonb); INSERT INTO t(data) VALUES ('{"a":1,"b":2, "c":3}') , ('{"a":11,"b":12, "c":13}') , ('{"a":21,"b":22, "c":23}') 问题陈述:我想接收一个任意的JSONB参数,它充当列t.data的过滤器,例如 { "b":{ "from":0,

测试数据

DROP TABLE t;
CREATE TABLE t(_id serial PRIMARY KEY, data jsonb);
INSERT INTO t(data) VALUES
   ('{"a":1,"b":2, "c":3}')
   , ('{"a":11,"b":12, "c":13}')
   , ('{"a":21,"b":22, "c":23}')
问题陈述:我想接收一个任意的JSONB参数,它充当列
t.data
的过滤器,例如

{ "b":{ "from":0, "to":20 }, "c":13 }
并使用此选项从我的测试表
t
中选择匹配的行。 在本例中,我需要
b
介于0和20之间且
c
=13的行。 如果筛选器指定的“列”(或“标记”)在
t.data
中不存在,则无需出错-它只是找不到匹配项

为了简单起见,我使用了数值,但我也希望采用一种方法,将其推广到
文本

到目前为止我都试过了。我研究了遏制方法,它适用于平等条件,但在处理射程条件的一般方法上遇到了难题:

select * from t
where t.data@> '{"c":13}'::jsonb;
背景:在网站上构建通用表预览页面(针对管理员用户)时出现此问题。 该页面根据选择用于预览的表中的各个列显示过滤器。 然后将筛选器传递给Postgres DB中的函数,该函数将此动态筛选器条件应用于表。 它返回与用户指定的筛选器匹配的行的jsonb数组。 然后使用此jsonb数组填充预览结果集。 组成筛选器的列可能会更改


我的Postgres版本是9.6-谢谢。

如果你想解析
{“b”:{“from”:0,“to”:20},“c”:13}
你需要一个解析器。它超出了json函数的范围,但您可以使用
编写“通用”查询,以按此类json进行过滤,例如:

以filt(f)为(值({“b”:{“from”:0,“to”:20},“c”:13}:::json))的

选择*
从t
加入filt on
(f->'b'->'from')::int<(数据->'b')::int
和
(f->'b'->'to')::int>(数据->'b')::int
和
(数据->>'c')::int=(f->>'c')::int
;

感谢您的评论/建议。 当我有更多的时间时,我肯定会看看GraphQL——我现在的工作期限很紧。 似乎大家一致认为,如果没有解析器,完全通用的解决方案是不可能实现的。 然而,我得到了一份可行的初稿——虽然远不理想,但我们可以用它。欢迎提出任何意见/改进

测试数据(扩展为包括日期和文本字段)

动态应用jsonb过滤器的代码初稿,但对支持的语法有限制。 此外,如果提供的语法与预期的语法不匹配,那么它将以静默方式失败。 时间戳处理也有点笨拙

-- Handle timestamp & text types as well as int
-- See is_timestamp(text) function at bottom

with cte as (
    select t.data, f.filt, fk.key
    from t
    , ( values ('{ "a":11, "b":{ "from":0, "to":20 }, "c":13, "d":"2018-03-14", "e":{ "from":"2018-03-11", "to": "2018-03-14" }, "f":"Howzat!" }'::jsonb ) ) as f(filt) -- equiv to cross join
    , lateral (select * from jsonb_each(f.filt)) as fk
)
select data, filt       --, key, jsonb_typeof(filt->key), jsonb_typeof(filt->key->'from'), is_timestamp((filt->key)::text), is_timestamp((filt->key->'from')::text) 
from cte
where 
    case when (filt->key->>'from') is null then 
        case jsonb_typeof(filt->key) 
            when 'number' then (data->>key)::numeric = (filt->>key)::numeric
            when 'string' then 
                case is_timestamp( (filt->key)::text )
                    when true then (data->>key)::timestamp = (filt->>key)::timestamp
                    else (data->>key)::text = (filt->>key)::text
                end
            when 'boolean' then (data->>key)::boolean = (filt->>key)::boolean
            else false
        end
    else 
        case jsonb_typeof(filt->key->'from') 
            when 'number' then (data->>key)::numeric between (filt->key->>'from')::numeric and (filt->key->>'to')::numeric
            when 'string' then 
                case is_timestamp( (filt->key->'from')::text )
                    when true then (data->>key)::timestamp between (filt->key->>'from')::timestamp and (filt->key->>'to')::timestamp
                    else (data->>key)::text between (filt->key->>'from')::text and (filt->key->>'to')::text
                end
            when 'boolean' then false
            else false
        end
    end
group by data, filt
having count(*) = ( select count(distinct key) from cte ) -- must match on all filter elements
;

create or replace function is_timestamp(s text) returns boolean as $$
begin
  perform s::timestamp;
  return true;
exception when others then
  return false;
end;
$$ strict language plpgsql immutable;

我可以对相等条件使用包含运算符,但不确定范围条件(例如,我希望“b”介于0和20之间)。没有通用(或内置)方法来实现这一点。您需要编写自己的JSON解析器,分析查询参数并为存储的JSON构建适当的条件。您可能需要研究GraphQL,它为您提供了一个解析器基础设施。谢谢您的帮助
DROP TABLE t;
CREATE TABLE t(_id serial PRIMARY KEY, data jsonb);
INSERT INTO t(data) VALUES
   ('{"a":1,"b":2, "c":3, "d":"2018-03-10", "e":"2018-03-10", "f":"Blah blah" }')
   , ('{"a":11,"b":12, "c":13, "d":"2018-03-14", "e":"2018-03-14", "f":"Howzat!"}')
   , ('{"a":21,"b":22, "c":23, "d":"2018-03-14", "e":"2018-03-14", "f":"Blah blah"}')
-- Handle timestamp & text types as well as int
-- See is_timestamp(text) function at bottom

with cte as (
    select t.data, f.filt, fk.key
    from t
    , ( values ('{ "a":11, "b":{ "from":0, "to":20 }, "c":13, "d":"2018-03-14", "e":{ "from":"2018-03-11", "to": "2018-03-14" }, "f":"Howzat!" }'::jsonb ) ) as f(filt) -- equiv to cross join
    , lateral (select * from jsonb_each(f.filt)) as fk
)
select data, filt       --, key, jsonb_typeof(filt->key), jsonb_typeof(filt->key->'from'), is_timestamp((filt->key)::text), is_timestamp((filt->key->'from')::text) 
from cte
where 
    case when (filt->key->>'from') is null then 
        case jsonb_typeof(filt->key) 
            when 'number' then (data->>key)::numeric = (filt->>key)::numeric
            when 'string' then 
                case is_timestamp( (filt->key)::text )
                    when true then (data->>key)::timestamp = (filt->>key)::timestamp
                    else (data->>key)::text = (filt->>key)::text
                end
            when 'boolean' then (data->>key)::boolean = (filt->>key)::boolean
            else false
        end
    else 
        case jsonb_typeof(filt->key->'from') 
            when 'number' then (data->>key)::numeric between (filt->key->>'from')::numeric and (filt->key->>'to')::numeric
            when 'string' then 
                case is_timestamp( (filt->key->'from')::text )
                    when true then (data->>key)::timestamp between (filt->key->>'from')::timestamp and (filt->key->>'to')::timestamp
                    else (data->>key)::text between (filt->key->>'from')::text and (filt->key->>'to')::text
                end
            when 'boolean' then false
            else false
        end
    end
group by data, filt
having count(*) = ( select count(distinct key) from cte ) -- must match on all filter elements
;

create or replace function is_timestamp(s text) returns boolean as $$
begin
  perform s::timestamp;
  return true;
exception when others then
  return false;
end;
$$ strict language plpgsql immutable;