替换字符串中的字符(自动化过滤条件) - Oracle SQL

时间:2017-10-11 14:00:53

标签: sql oracle

工作是在查询中参数化过滤条件,以便用户可以动态选择过滤条件并生成查询。 表中有3列,col2有过滤值,col3有AND / OR过滤条件。请帮帮我。

col1    col2           col3
------------------------------

v1      sal1 > 10

v2      sal2 > 500  

v3                     v1 OR v2  (comments :  col2 is NULL, col3 is v1 OR v2)

v4                     v1 AND v2  (comments :  col2 is NULL, col3 is v1 AND v2)

v5                     v3 AND v6  (comments:  col2 is NULL, col3 is v1 AND v2)

v6  amt > 20    

选择v3时

v3  (sal1> 10 OR sal2 > 500)    

选择v4时

v4  (sal1> 10 AND sal2 > 500)   

选择v5时

v5  ((sal1> 10 OR sal2 > 500) AND amt > 20) 

附加了示例脚本,它将使用动态SQL根据用户选择生成SQL。

CREATE TABLE TEST_2
(
 COL1 VARCHAR2(4000),
 COL2 VARCHAR2(4000),
 COL3 VARCHAR2(4000)
 );

insert into TEST_2 values ('v1', 'sal1 > 10', null);
insert into TEST_2 values ('v2', 'sal2 > 500', null);
insert into TEST_2 values ('v3', null, 'v1 OR v2');
insert into TEST_2 values ('v4', null, 'v1 AND v2');
insert into TEST_2 values ('v5', null, 'v3 AND v6');
insert into TEST_2 values ('v6', 'amt > 20', null);
commit;


col1    col2        col3
v1      sal1 > 10   
v2      sal2 > 500  
v3                  v1 OR v2
v4                  v1 AND v2
v5                  v3 AND v6
v6  amt > 20    

2 个答案:

答案 0 :(得分:0)

这是一个使用Regex构建递归解析器的C#解决方案。我创建了一个类QueryTemplate来存储表的内容。

public class QueryTemplate
{
    public string Id { get; set; }
    public string Term { get; set; }
    public string Expression { get; set; }
}

作为测试设置,我按如下方式初始化数据:

private Dictionary<string, QueryTemplate> _templates;

public void Test()
{
    _templates = new Dictionary<string, QueryTemplate> {
        ["v1"] = new QueryTemplate { Id = "v1", Term = "sal1 > 10" },
        ["v2"] = new QueryTemplate { Id = "v2", Term = "sal2 > 500" },
        ["v3"] = new QueryTemplate { Id = "v3", Expression = "v1 OR v2" },
        ["v4"] = new QueryTemplate { Id = "v4", Expression = "v1 AND v2" },
        ["v5"] = new QueryTemplate { Id = "v5", Expression = "v3 AND v6" },
        ["v6"] = new QueryTemplate { Id = "v6", Term = "amt > 20" },
    };

   Console.WriteLine(CreateSqlCondition("v5"));
}

控制台输出:

  <(>(sal1> 10)OR(sal2> 500))AND(amt> 20)

条件以递归方式构建:

public string CreateSqlCondition(string id)
{
    if (_templates.TryGetValue(id, out QueryTemplate qt)) {
        if (qt.Term != null) {
            return qt.Term;
        } else { // Expression
            // Assuming that all Ids start with "v", but not column names.
            var idMatches = Regex.Matches(qt.Expression, @"\bv\w+\b");
            string expr = qt.Expression;
            foreach (var match in idMatches.Cast<Match>().Reverse()) {
                // Replace in reverse order to preserve the match positions.
                string idName = match.Value;
                string cond = CreateSqlCondition(idName);
                string prefix = (expr.Substring(0, match.Index));
                string suffix = expr.Substring(match.Index + match.Length);
                expr = $"{prefix}({cond}){suffix}";
            }
            return expr;
        }
    } else {
        throw new ArgumentException($"Query template \"{id}\" not found.");
    }
}

我还在每个术语周围添加括号以保留AND / OR运算符的优先级。

问题是区分ID和列名称。也许你应该使用Id名称,如$ 1,$ 2,$ 3 ......来避免混淆。此外,对术语和表达式仅使用一列,将允许您组合ID和列名称。示例(使用&#34; $&#34; ID):$5 AND amt <= 20。当然,我的CreateSqlCondition方法必须适应它。

正则表达式\bv\w+\b表示

\b     word boundary (beginning of word)
v      the character v  
\w+    all following word characters (letters, digits and underscores)  
\b     word boundary (end of word)

如果您选择使用&#34; $&#34;而不是&#34; v&#34;,用&#34; \&#34;:

来逃避它
@"\b\$\w+\b"

答案 1 :(得分:0)

好的,我这样做很有趣,因为我从未在SQL中实现过binary expression tree。但它只会让你在未来的路上受苦。

select listagg(dyn_where) within group (order by bintree_order) as dynamic_where from
(select c.*, rpad(c.syspath, max(lvl) over (), '1') as bintree_order from (
    select level as lvl, 
           replace(sys_connect_by_path(case when sign(nvl(instr(prior col3, col1)-floor(length(prior col3)/2),0)) <= 0 then '0' else '2' end, ' '), ' ') as syspath,
           col2 || substr(col3, 3, length(col3)-4) as dyn_where
    from TEST_2
    connect by instr(prior col3, col1) > 0  
    start with col1 = 'v5'
    ) c 
);

此查询当前依赖于col1为2个字符长(用于在col3上执行substr)。如果您更改表结构以将COL3拆分为3列,则不需要这样做 - 一个用于第一个操作数,一个用于布尔运算符(例如OR),另一个用于第二个操作数。在这个例子中,col3和col5实际上可能是col1的外键,你可以更容易。哈,比较。还是个坏主意。 :)

CREATE TABLE TEST_2
(
 COL1 VARCHAR2(4000), -- primary key
 COL2 VARCHAR2(4000), -- binary tree leaf node value
 COL3 VARCHAR2(4000), -- operator node - link to left child
 COL4 VARCHAR2(4000), -- operator node - operand (e.g. AND)
 COL5 VARCHAR2(4000)  -- operator node - link to right child
 );