【问题标题】:How to split sql argument list string?如何拆分sql参数列表字符串?
【发布时间】:2012-03-26 07:09:27
【问题描述】:

问题:

我有一个搜索 ODBC 转义序列的函数, 然后递归地用它们的原生等价物替换它们。

例如,给定一个 ODBC 转义序列,如: {fn concat(column1, column2)}

我用它的原生 SQL 等价物替换它

if (StringComparer.OrdinalIgnoreCase.Equals("concat", strFunctionName)) {
    strArguments = strArguments;

    string[] astrArguments = strArguments.Split(',');
    string strTerm = astrArguments[0] + " || " + astrArguments[1];

    return strTerm;
}

产生第 1 列 || column2(PostGreSql 语法)

一般来说,我必须将字符串“arglist_comma_separated”解析为 {fn 随便(arglist_comma_separated)}

进入其组件

不用说,在我当前的版本中,它不起作用 如果 arglist 中的参数 1 类似于

'hello, world'

更糟糕的是,如果我将家庭和名字连接起来,例如,它可能是 {fn 不管('d''Amato', 'Alberto')}

如何最好地拆分论点?

如何使用正则表达式或解析来做到这一点?

编辑:
嗯,又想了想,有个绝妙的主意(把''换成转义符)。
这样一来,对于非正则表达式解决方案,问题的复杂性已从中高降低到微不足道:
附录:实际上,几乎是微不足道的,我忘了在拆分中考虑函数嵌套 - 愚蠢。

    protected static string[] GetArguments(string strAllArguments)
    {
        string EscapeCharacter = System.Convert.ToChar(8).ToString();

        strAllArguments = strAllArguments.Replace("''", EscapeCharacter);

        bool bInString = false;
        int iLastSplitAt = 0;
        int iInFunction = 0;

        System.Collections.Generic.List<string> lsArguments = new System.Collections.Generic.List<string>();

        for (int i = 0; i < strAllArguments.Length; i++)
        {
            char strCurrentChar = strAllArguments[i];

            if (strCurrentChar == '\'')
                bInString = !bInString;

            if (bInString)
                continue;


            if (strCurrentChar == '(')
                 iInFunction++;

            if (strCurrentChar == ')')
                 iInFunction--;

            if (strCurrentChar == ',')
            {
                if(iInFunction == 0)
                {
                    string strExtract = strAllArguments.Substring(iLastSplitAt, i - iLastSplitAt);
                    strExtract = strExtract.Replace(EscapeCharacter, "''");
                    lsArguments.Add(strExtract);
                    iLastSplitAt = i;
                }
            }

        }


        string strExtractLast = strAllArguments.Substring(iLastSplitAt + 1);
        strExtractLast = strExtractLast.Replace(EscapeCharacter, "''");
        lsArguments.Add(strExtractLast);

        string[] astrResult = lsArguments.ToArray();
        lsArguments.Clear();
        lsArguments = null;

        return astrResult;
    }

【问题讨论】:

  • 一个类似的问题,结论可能是“不要使用正则表达式”:stackoverflow.com/questions/4150697/…
  • @DavidBrabant 虽然我同意正则表达式不是灵丹妙药,但我想不出更好的方法来处理这种情况。这里的参数符合常规语言(否则 SQL 词法分析器将无法解析它们),因此将它们中的 each 与正则表达式匹配是有意义的。在另一个问题中,OP 想用 一个 正则表达式匹配/拆分它们中的 all,我完全同意你的观点,即 that '不可取。
  • @Quandary +1 您的新解决方案对我来说看起来不错,只要您确保 EscapeCharacter 不可能出现在您的原始输入中。我只建议将其移出问题并将其发布as a new answer

标签: c# sql regex parsing split


【解决方案1】:

编辑:我清理了这个答案很多,因为有很多反馈迭代;下面是我对这个主题的结论)

解析复杂的语言是一项艰巨的任务,因此我假设您将问题缩小到处理以逗号分隔的标记值列表(例如字符串、数字、简单标识符等 - 而不是复杂的表达式)。如果我弄错了,您手中的问题可能比您想象的要大。在这种情况下,我建议以this question 为起点。

最简单的解决方案 - 在, 上拆分 - 主要是因为字符串不起作用,因为逗号可以出现在字符串中。解析字符串是一项简单的任务,假设您正确处理转义字符:它以引号开头,有零个或多个字符并以另一个引号结尾。

在大多数语言中,如果字符串由' 分隔,您可以使用\' 转义其中的引号。 SQL 将字符串中的'' 解释为转义引号。如果您知道这些表格中只有一个会出现,您可以忽略另一个。在下面的回答中,我决定将两者都包括在内。

此外,某些语言同时接受单引号 (') 和双引号 (") 来分隔字符串。关于转义字符的相同观察适用。我的解决方案也同时处理这两种形式。

除了字符串之外,指定参数的有效字符也很重要。为简单起见,我假设它是“任何不是逗号的东西”。出于同样的原因,我提出的解决方案将接受任意数量的字符串和非字符串,并将它们组合在一起,将它们作为单个实体返回(重申,如果需要复杂的表达式,则应采用更通用的解析技术这个简单的解决方案)。

实现这一点的一种方法是在应用上述逻辑时循环遍历字符,正如您在最近的更新中所做的那样。另一个将使用正则表达式。正则表达式具有更好的性能(通常)和更简洁的代码,不易出错。主要缺点是正则表达式本身的复杂性,因为“密集”格式可能更难理解/维护。

我建议的正则表达式将是(添加空格/换行符以提高可读性):

(
    (?:  \'   (?: ['\\]\' | [^'] )*   \'  |
         \"   (?: ["\\]\" | [^"] )*   \"  |
         [^,'"]
    )+
)
(?: \, | $)

简而言之:

((?:\'(?:['\\]\'|[^'])*\'|\"(?:["\\]\"|[^"])*\"|[^,'"])+)(?:\,|$)

每个字符串都接受转义引号('\ 后跟')或任何非引号作为“字符”。匹配项(大捕获组)后面必须跟 , 或输入的结尾。

上面的正则表达式的一个活生生的例子可以看到here(这个例子使用了Ruby,但在C#中应该同样有效)。只要整个输入都匹配(即不存在不匹配的子字符串),每个匹配项都会正确捕获参数。 警告:格式错误的输入会产生不正确的输出,因此上面的正则表达式必须用于验证。

要在您的 C# 代码中使用此解决方案,您可以使用 Regex.Matches:

MatchCollection matches = Regex.Matches(strArguments, "((?:\'(?:['\\]\'|[^'])*\'|\"(?:["\\]\"|[^"])*\"|[^,'"])+)(?:\,|$)");
string[] arguments = from m in matches select m.Captures[1].Value;

如上所述,您还必须确保匹配覆盖整个输入。我把它作为练习留给读者...... ;)

注意事项:

  1. 我假设Matches 的结果不重叠;如果我弄错了,上面的代码必须适应从前一个结束的索引开始的每个匹配;
  2. 像往常一样,我还假设捕获组 #0 将是整个比赛,而 #1 将是第一个捕获组。

【讨论】:

  • 这不适用于 strArguments = "'hello d''someapostroph, world', 'test', foo";所以它会在 {fn concat('d''Amato', 'Alberto')} 上失败。参数可以,但不必用引号括起来。
  • 抱歉,正在编辑我的答案以修复一些错误,但没有看到评论。新的正则表达式应该可以工作,见this example
  • 我纠正自己:它不起作用。这个正则表达式比我的拆分函数差得多。它已经失败了:“AND {fn LCASE(BE_User)} LIKE {fn LCASE({fn CONCAT(@strTerm, '%')} )} ”。它带走了 at。然后它在更多的地方也失败了。
  • 您必须指定在未转义部分中被认为是有效的正则表达式才能工作。例如,将\w+ 替换为[\w@]+ 并修复“@”。另一种选择是匹配“不是逗号的任何内容”,无论输入是否有效......请参阅上面的更新。
  • mgibsonbr: 现在在 "{fn concat({fn concat('(', DG_Code)}, ') ')}, DG_Lang_DE" 上失败, strArguments = "'(' || DG_Code, ' ) '";
猜你喜欢
  • 2019-06-27
  • 2015-05-16
  • 1970-01-01
  • 2017-06-22
  • 1970-01-01
  • 1970-01-01
  • 2021-12-18
相关资源
最近更新 更多