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;