【问题标题】:How do I accurately handle a batch separator for SQL from C#如何从 C# 准确处理 SQL 的批处理分隔符
【发布时间】:2011-02-25 19:48:09
【问题描述】:

对于Data Explorer,我想添加对批处理分隔符的支持。

例如,如果用户输入:

select 'GO' go select 1 as go 走 选择 100

我想返回三个结果集。

很明显,我需要某种解析器,我希望这是一个已解决的问题,我可以将其插入。(编写完整的 T-SQL 解析器不是我想做的事)

什么组件/演示代码可以实现将这个批次分成 3 个部分?

【问题讨论】:

  • jeez ... 这个要 12k temporal-wave.com/…
  • 注意:微软发布了一个 TSql100Parser 但是......对于我的一生,我无法弄清楚如何为它提供一个批处理标识符......我倾向于只做一个微不足道的事情,尽管非常受限解决方案。

标签: c# sql tsql parsing


【解决方案1】:

我并不经常这么说,但在这种情况下,我肯定会提倡弯曲用户输入以符合计算机规则,而不是试图解决让计算机理解大量不同用户输入的问题

施加一个简单的规则: 单词“go”必须单独出现在一行中才能被解释为继续执行的命令

如果您的用户不能遵守这样的规则,他们是否真的应该参与编写 SQL 查询这一更为复杂的任务?

【讨论】:

  • 来自docs.microsoft.com/en-us/sql/t-sql/language-elements/…,“Transact-SQL 语句不能与 GO 命令占用同一行。但是,该行可以包含 cmets。”所以这已经(几乎)是真的了。相反的情况是我所关心的:select '\n go \n';(不能在 cmets afaik 中添加换行符)。
【解决方案2】:

我一直在寻找相同问题的解决方案,但没有找到合适的解决方案(在我的情况下,使用 SMO 是不可接受的)。所以,我不得不编写自己的解析器。这里是:

static IEnumerable<string> ParseSqlBatch(Stream s)
{
    if (s == null)
        throw new ArgumentNullException();

    StringBuilder sbSqlStatement = new StringBuilder();
    Stack<string> state = new Stack<string>();
    StreamReader sr = new StreamReader(s);

    //initially search for "GO" or open tag of strings ('), comments (--, /*) or identifiers ([)
    string pattern = @"(?>(?<=^\s*)go(?=\s*(--.*)?$)|''(?!')|(?<!')'|(?<!\[)\[|--(?=.*)?|/\*)";
    //if open tag found search for close tag, then continue search
    string patternCloseString = @"(?>''|'(?!'))";
    string patternCloseIdentifier = @"(?>\]\]|\](?!\]))";
    string patternComments = @"(?>\*/|/\*)";

    Regex rx = new Regex(pattern, RegexOptions.IgnoreCase);

    while (!sr.EndOfStream)
    {
        string line = sr.ReadLine();

        int ix = 0;
        bool bBreak = false;
        while (ix < line.Length && !bBreak)
        {
            Match m = rx.Match(line, ix);

            if (!m.Success)
            {
                sbSqlStatement.Append(line.Substring(ix));
                break;
            }

            int ix2 = m.Index;
            string word = m.Value;

            sbSqlStatement.Append(line.Substring(ix, ix2 - ix));

            if (state.Count == 0)
            {
                if (string.Compare(word, "GO", true) == 0)
                {
                    if (sbSqlStatement.Length > 0)
                    {
                        yield return sbSqlStatement.ToString();
                        sbSqlStatement = new StringBuilder();
                        break;
                    }
                }
                else
                {
                    switch (word)
                    {
                        case "'":
                            rx = new Regex(patternCloseString);
                            break;
                        case "[":
                            rx = new Regex(patternCloseIdentifier);
                            break;
                        case "/*":
                            rx = new Regex(patternComments);
                            break;
                        case "--":
                            sbSqlStatement.Append(line.Substring(ix2));
                            bBreak = true;
                            continue;
                    }

                    if (word != "''")
                        state.Push(word);
                }
            }
            else
            {
                string st = state.Peek();

                switch (st)
                {
                    case "'":
                        if (st == word)
                            state.Pop();
                        break;
                    case "[":
                        if (word == "]")
                            state.Pop();
                        break;
                    case "/*":
                        if (word == "*/")
                            state.Pop();
                        else if (word == "/*")
                            state.Push(word);
                        break;
                }

                if (state.Count == 0)
                    rx = new Regex(pattern, RegexOptions.IgnoreCase);
            }

            ix = ix2 + word.Length;
            sbSqlStatement.Append(word);
        }

        sbSqlStatement.AppendLine();
    }

    if (sbSqlStatement.Length > 0)
        yield return sbSqlStatement.ToString();
}

它可以正确处理字符串、标识符和 cmets 中的“GO”。也许不是理想的一个,但成功地测试了数百种不同的 .sql 脚本。

然后,例如:

using (FileStream fs = new FileStream("SampleBatch.sql", FileMode.Open, FileAccess.Read))
{
    foreach (string statement in ParseSqlBatch(fs))
    {
        //execute statement here, or do something with it
    }

    fs.Close();
}

我希望它会对某人有所帮助。

【讨论】:

  • 这是否处理带引号的标识符/双引号字符串?
  • @ArinTaylor,对于双引号字符串 - 是的(如果它不处理语法正确的内容,请告诉我),对于带引号的标识符 - 我认为,不。它不会处理像create table "GO" 这样的情况。我总是使用[name]-style 标识符,从不使用"name"-style,而且代码不是为它设计的。但它也可以很容易地修改以支持带引号的标识符。只需通过代码添加",类似于将' 用于字符串。
【解决方案3】:

我不知道现有的解决方案(尽管我同意可能有一个)。我只想指出,您可能不需要编写完整的 T-SQL 解析器:您真正需要找到的只是引号之外的单词“go”。也就是说,查找&lt;word boundary&gt;GO&lt;word boundary&gt; 并一路跟踪开盘价和收盘价。如果您找到匹配项并且它不在开始报价之后(在匹配的结束报价之前),那么它就是批次分隔符。这应该很容易做到,而无需编写任何您称之为适当“解析器”的东西。

【讨论】:

  • yerp 这可能有点工作,但它会变得很棘手,例如:select 1 as go is valid sql it mgmt studio
【解决方案4】:

在你上面的例子中,你不能只分割换行符,测试每一行是否以“go”开头,然后分割脚本吗?

在重读了几次之后,这是一个非常丑陋的问题。查看脚本的第一行,实际上没有命令分隔符(分号或换行符)。我认为您别无选择,只能实际解析整个事情。

但是,无论如何,这必须得到解析,对吧?也许你可以在里面做一些事情,或者使用 existing 解析器来做这件事。根据您对它的访问权限,您可以:

  • 更改现有解析器的代码以理解“go”命令执行并返回它所拥有的内容,然后再次运行。

  • 复制现有的解析代码,调整它以理解“go”命令,去掉解释器部分,然后只用它来拆分块并提供给真正的解析器?

【讨论】:

    【解决方案5】:

    您可以非常轻松地为打开的 SqlConnection 更改当前数据库:

     connection.ChangeDatabase("YourDB");
    

    一个例子:

    private static void ConctDatabase(string connectionString)
    {
        using (SqlConnection conn = new SqlConnection(connectionString))
        {
            conn.Open();
            MessageBox.Show("Database: {0}", conn.Database);
            conn.ChangeDatabase("Northwind");
            MessageBox.Show("Database: {0}", conn.Database);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多