在SQLalchemy中动态生成过滤器,混合使用and / or和not / like

时间:2018-11-01 04:42:56

标签: python sqlalchemy

我有一个定义一些配置的字典,在这里我不会事先知道用户要构建的确切过滤器

它是这样指定的:

{"col1": ["val1", "~val2"],  
 "col2": ["val3", "val4"], 
 "col3": ["val5", "val6", "~val7", "~val8"]}

应翻译为:

WHERE ( 
  col1 LIKE val1 
  AND 
  col1 NOT LIKE val2 
) 
OR ( 
  col2 LIKE val3 
    OR 
  col2 LIKE val4 
) 
OR 
( 
  ( 
    col3 LIKE val5 
    OR 
    col3 LIKE val6 
  ) 
  AND 
    col3 NOT LIKE val7 
  AND 
    col3 NOT LIKE val8 
)

顶层条件始终是“或”(您匹配指定的一列或匹配指定的另一列)从不“与”

但是,在列表中,您可以匹配任何肯定的条件,但不能匹配任何否定的条件(这里的条件是字符串)

到目前为止,我尝试动态构建它的尝试看起来像这样:

def make_filter(definition):
    f = {}
    for variable in definition:
        v = getattr(Target, variable)
        f[v] = []
        for col in definition[variable]:
            f[v].append(col)
    return f


def make_query(def_dict):
    q = destination.query(Target)
    filter = make_filter(def_dict)
    for col in filter:
        for val in filter[col]:
            if '~' in val:
                q = q.filter(sa.not_(col.like(val)))
            else:
                q = q.filter(col.like(val))
    for record in q.all():
        # do stuff

但是很明显,这通过'and'组合了所有条件-对于未知数量的条件,我看不到一种与'or'动态组合的方法...

只要可以明确指定同一组标准,那么这是解决此问题的更好方法,那么我可以修改规范。我并没有特别要求用户将所有肯定的标准放在所有否定的标准之前,但这是合理的。

1 个答案:

答案 0 :(得分:0)

不确定上面给出的链接答案是否应该将其标记为重复-可能有所不同以至仍然有用,因此以防万一我基于链接答案发布我的解决方案以提高完整性:

def make_filter(definition):
    f = {}
    for variable in definition:
        v = getattr(Target, variable)
        f[v] = {"like": [], "not": []}
        for col in definition[variable]:
            if "~" in col:
                f[v]["not"].append(re.sub('~', '', col))
            else:
                f[v]["like"].append(col)
    return f

def make_query(def_dict):
    q = destination.query(Target)
    filter = make_filter(def_dict)
    for item in filter:
        like_this = filter[item]["like"]
        not_this = filter[item]["not"]
        if len(not_this):
            for nt in not_this:
                q = q.filter(sa.not_(item.like(nt)))
        clauses = []
        for lt in like_this:
            clauses.append(item.like(lt))
        q = q.filter(sa.or_(*clauses))
        for record in q.all():
            # do stuff