我正在根据用户指定字段的用户提交条件构建动态查询。
我有两张桌子
Visitors(id, name, email...)
和
Trackings(id, visitor_id, field, string_value, integer_value, boolean_value, date_value)
条件以前面构建的SQL片段数组的形式出现。 visitors table
上的硬编码属性和自定义过滤器的默认过滤器,用于用户提交并存储在EAV架构中的值(跟踪)
示例:
Default:
{"name ILIKE 'Jack'", "(last_seen < (current_date - (7 || ' days')::interval))"}
Custom:
{"field = 'number_of_orders' > 10", "is_pro_user = true"}
访问者可以拥有多个跟踪,每个跟踪都会为该访问者记录一些自定义的,用户提交的数据字段。但每个访问者也有一些默认数据位于表格本身,例如email
,name
或last_seen
等。
现在,用户应该询问如下问题:
number_of_orders
的自定义字段的访问者(未知)name
设置为Jack
以及自定义属性total_purchase_value
大于1000
的人我尝试解决它是使用一个存储过程,它使用AND(对于访问者表上的默认数据)和WHERE-caluse内的OR语句(对于跟踪表中的自定义数据)动态连接一系列条件
CREATE OR REPLACE FUNCTION find_matching_visitors(app_id text, default_filters text[], custom_filters text[])
RETURNS TABLE (
id varchar
) AS
$body$
DECLARE
default_filterstring text;
custom_filterstring text;
default_filter_length integer;
custom_filter_length integer;
sql VARCHAR;
BEGIN
default_filter_length := COALESCE(array_length(default_filters, 1), 0);
custom_filter_length := COALESCE(array_length(custom_filters, 1), 0);
default_filterstring := array_to_string(default_filters, ' AND ');
custom_filterstring := array_to_string(custom_filters, ' OR ');
IF custom_filterstring = '' or custom_filterstring is null THEN
custom_filterstring := '1=1';
END IF;
IF default_filterstring = '' or default_filterstring is null THEN
default_filterstring := '1=1';
END IF;
sql := format('
SELECT v.id FROM visitors v
LEFT JOIN trackings t on v.id = t.visitor_id
WHERE v.app_id = app_id and (%s) and (%s)
group by v.id
having case when %s > 0 then count(v.id) = %s else true end
', custom_filterstring, default_filterstring, custom_filter_length, custom_filter_length);
RETURN QUERY EXECUTE sql;
END;
$body$
LANGUAGE 'plpgsql';
这很好用,但AFAIK无法表达自定义属性的unknown
过滤器。因为它需要使用left outer join
或not exists
子查询来访问外部范围。
我现在正在寻找完成上述相同的替代方法,但也支持这种查询。我正在考虑类似下面的内容,对每个条件使用一系列横向连接,但是这似乎只要有超过1-2个条件/连接就不能很好地执行。
select v.id, v.name from visitors v
inner join lateral ( <-- custom fields
select * from trackings t
where field = 'admin'
) as t1 on t1.visitor_id = v.id
inner join lateral (
select * from trackings t
where field = 'users_created'
) as t2 on t2.visitor_id = v.id
inner join lateral (
select * from trackings t
where field = 'teams_created' and integer_value > 0
) as t3 on t3.visitor_id = v.id
where v.app_id = 'ASnYW1-RgCl0I' and (v.type = 'lead' or v.type = 'user')
and name ILIKE 'mads' and email is not null // <-- default fields
有什么建议吗?