测试数据
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
中不存在的“列”(或“标记”),则不需要错误 - 它只是找不到匹配项。
为了简单起见,我使用了数字值,但是想要一种可以推广到text
的方法。
到目前为止我尝试了什么。我查看了包含方法,该方法适用于相等条件,但我对处理范围条件的一般方法感到困惑:
select * from t
where t.data@> '{"c":13}'::jsonb;
背景:在网站上构建通用表格预览页面(针对管理员用户)时出现此问题。 该页面根据选择用于预览的任何表格中的各个列显示过滤器。 然后将过滤器传递给Postgres DB中的函数,该函数将此动态过滤条件应用于表。 它返回与用户指定的过滤器匹配的行的jsonb数组。 然后使用此jsonb数组填充Preview结果集。 构成过滤器的列可能会发生变化。
我的Postgres版本是9.6 - 谢谢。
答案 0 :(得分:1)
如果要解析{ "b":{ "from":0, "to":20 }, "c":13 }
,则需要解析器。它超出了json函数的范围,但你可以编写" generic"使用AND
和OR
进行查询以按此类json进行过滤,例如:
https://www.db-fiddle.com/f/jAPBQggG3p7CxqbKLMbPKw/0
with filt(f) as (values('{ "b":{ "from":0, "to":20 }, "c":13 }'::json))
select *
from t
join filt on
(f->'b'->>'from')::int < (data->>'b')::int
and
(f->'b'->>'to')::int > (data->>'b')::int
and
(data->>'c')::int = (f->>'c')::int
;
答案 1 :(得分:0)
感谢您的意见/建议。 当我有更多时间时,我一定会看看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"}')
动态应用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;