执行大型 SQL 脚本(使用 GO 命令)

Posted

技术标签:

【中文标题】执行大型 SQL 脚本(使用 GO 命令)【英文标题】:Execute a large SQL script (with GO commands) 【发布时间】:2010-09-07 15:15:59 【问题描述】:

我需要在 C# 程序中执行大量 SQL 语句(创建一堆表、视图和存储过程)。

这些语句需要用GO语句分隔,但SqlCommand.ExecuteNonQuery()不喜欢GO语句。我的解决方案(我想我会发布以供参考)是在 GO 行上拆分 SQL 字符串,并分别执行每个批处理。

有没有更简单/更好的方法?

【问题讨论】:

【参考方案1】:

您可以在每个语句的末尾使用;,因为它对我有用。 真的不知道有没有什么缺点。

【讨论】:

并非如此。例如,您必须在每个语句的开头有CREATE FUNCTION。在这种情况下,用; 终止最后一个是行不通的。【参考方案2】:

对于仍有问题的任何人。你可以使用微软官方 SMO

https://docs.microsoft.com/en-us/sql/relational-databases/server-management-objects-smo/overview-smo?view=sql-server-2017

using (var connection = new SqlConnection(connectionString))

  var server = new Server(new ServerConnection(connection));
  server.ConnectionContext.ExecuteNonQuery(sql);

【讨论】:

这并没有添加任何超过最高投票、接受的答案,这也暗示了 SMO(10 年前发布!)。【参考方案3】:

我看了几遍最后决定用EF implementation 为SqlConnection做了一点修改

public static void ExecuteSqlScript(this SqlConnection sqlConnection, string sqlBatch)
        
            // Handle backslash utility statement (see http://technet.microsoft.com/en-us/library/dd207007.aspx)
            sqlBatch = Regex.Replace(sqlBatch, @"\\(\r\n|\r|\n)", string.Empty);

            // Handle batch splitting utility statement (see http://technet.microsoft.com/en-us/library/ms188037.aspx)
            var batches = Regex.Split(
                sqlBatch,
                string.Format(CultureInfo.InvariantCulture, @"^\s*(0[ \t]+[0-9]+|0)(?:\s+|$)", BatchTerminator),
                RegexOptions.IgnoreCase | RegexOptions.Multiline);

            for (int i = 0; i < batches.Length; ++i)
            
                // Skip batches that merely contain the batch terminator
                if (batches[i].StartsWith(BatchTerminator, StringComparison.OrdinalIgnoreCase) ||
                    (i == batches.Length - 1 && string.IsNullOrWhiteSpace(batches[i])))
                
                    continue;
                

                // Include batch terminator if the next element is a batch terminator
                if (batches.Length > i + 1 &&
                    batches[i + 1].StartsWith(BatchTerminator, StringComparison.OrdinalIgnoreCase))
                
                    int repeatCount = 1;

                    // Handle count parameter on the batch splitting utility statement
                    if (!string.Equals(batches[i + 1], BatchTerminator, StringComparison.OrdinalIgnoreCase))
                    
                        repeatCount = int.Parse(Regex.Match(batches[i + 1], @"([0-9]+)").Value, CultureInfo.InvariantCulture);
                    

                    for (int j = 0; j < repeatCount; ++j)
                    
                       var command = sqlConnection.CreateCommand();
                       command.CommandText = batches[i];
                       command.ExecuteNonQuery();
                    
                
                else
                
                    var command = sqlConnection.CreateCommand();
                    command.CommandText = batches[i];
                    command.ExecuteNonQuery();
                
            
        

【讨论】:

谢谢@Filip Cordas。尽管这没有被标记为答案,但这对我很有帮助!我们有大量脚本,其中提到 BatchTerminator 的方式不同,例如大写和小写的组合(go、Go、GO 等),并且最大时间带有尾随或前导空格,这导致通过 c# 执行时出现大问题...... . 谢谢!! @DipakRiswadkar 是的,在这个问题上锁定了几次,提供的答案都没有满足我的需求,所以看了看 EF 实现看起来不错,所以我发布了答案。 很棒的答案,它就像一个魅力,非常感谢 @Really 也应该将其报告给实体框架团队。正如我所说,这只是过去的复制,几乎没有修改。【参考方案4】:

为了避免第三方、正则表达式、内存开销和快速处理大型脚本,我创建了自己的基于流的解析器。它

检查之前的语法

可以用 -- 或 /**/ 识别 cmets

-- some commented text
 /*
drop table Users;
GO
   */

可以识别带有 ' 或 "的字符串文字

set @s =
    'create table foo(...);
    GO
    create index ...';
保留 LF 和 CR 格式 在对象主体(存储过程、视图等)中保留 cmets 块

和其他结构,例如

      gO -- commented text

如何使用

    try
    
        using (SqlConnection connection = new SqlConnection("Integrated Security=SSPI;Persist Security Info=True;Initial Catalog=DATABASE-NAME;Data Source=SERVER-NAME"))
        
            connection.Open();

            int rowsAffected = SqlStatementReader.ExecuteSqlFile(
                "C:\\target-sql-script.sql",
                connection,
                // Don't forget to use the correct file encoding!!!
                Encoding.Default,
                // Indefinitely (sec)
                0
            );
        
    
    // implement your handlers
    catch (SqlStatementReader.SqlBadSyntaxException)  
    catch (SqlException)  
    catch (Exception)  

基于流的 SQL 脚本阅读器

class SqlStatementReader

    public class SqlBadSyntaxException : Exception
    
        public SqlBadSyntaxException(string description) : base(description)  
        public SqlBadSyntaxException(string description, int line) : base(OnBase(description, line, null))  
        public SqlBadSyntaxException(string description, int line, string filePath) : base(OnBase(description, line, filePath))  
        private static string OnBase(string description, int line, string filePath)
        
            if (filePath == null)
                return string.Format("Line: 0. 1", line, description);
            else
                return string.Format("File: 0\r\nLine: 1. 2", filePath, line, description);
        
    

    enum SqlScriptChunkTypes
    
        InstructionOrUnquotedIdentifier = 0,
        BracketIdentifier = 1,
        QuotIdentifierOrLiteral = 2,
        DblQuotIdentifierOrLiteral = 3,
        CommentLine = 4,
        CommentMultiline = 5,
    

    StreamReader _sr = null;
    string _filePath = null;
    int _lineStart = 1;
    int _lineEnd = 1;
    bool _isNextChar = false;
    char _nextChar = '\0';

    public SqlStatementReader(StreamReader sr)
    
        if (sr == null)
            throw new ArgumentNullException("StreamReader can't be null.");

        if (sr.BaseStream is FileStream)
            _filePath = ((FileStream)sr.BaseStream).Name;

        _sr = sr;
    

    public SqlStatementReader(StreamReader sr, string filePath)
    
        if (sr == null)
            throw new ArgumentNullException("StreamReader can't be null.");

        _sr = sr;
        _filePath = filePath;
    

    public int LineStart  get  return _lineStart;  
    public int LineEnd  get  return _lineEnd == 1 ? _lineEnd : _lineEnd - 1;  

    public void LightSyntaxCheck()
    
        while (ReadStatementInternal(true) != null) ;
    

    public string ReadStatement()
    
        for (string s = ReadStatementInternal(false); s != null; s = ReadStatementInternal(false))
        
            // skip empty
            for (int i = 0; i < s.Length; i++)
            
                switch (s[i])
                
                    case ' ': continue;
                    case '\t': continue;
                    case '\r': continue;
                    case '\n': continue;
                    default:
                        return s;
                
            
        
        return null;
    

    string ReadStatementInternal(bool syntaxCheck)
    
        if (_isNextChar == false && _sr.EndOfStream)
            return null;

        StringBuilder allLines = new StringBuilder();
        StringBuilder line = new StringBuilder();
        SqlScriptChunkTypes nextChunk = SqlScriptChunkTypes.InstructionOrUnquotedIdentifier;
        SqlScriptChunkTypes currentChunk = SqlScriptChunkTypes.InstructionOrUnquotedIdentifier;
        char ch = '\0';
        int lineCounter = 0;
        int nextLine = 0;
        int currentLine = 0;
        bool nextCharHandled = false;
        bool foundGO;
        int go = 1;

        while (ReadChar(out ch))
        
            if (nextCharHandled == false)
            
                currentChunk = nextChunk;
                currentLine = nextLine;

                switch (currentChunk)
                
                    case SqlScriptChunkTypes.InstructionOrUnquotedIdentifier:

                        if (ch == '[')
                        
                            currentChunk = nextChunk = SqlScriptChunkTypes.BracketIdentifier;
                            currentLine = nextLine = lineCounter;
                        
                        else if (ch == '"')
                        
                            currentChunk = nextChunk = SqlScriptChunkTypes.DblQuotIdentifierOrLiteral;
                            currentLine = nextLine = lineCounter;
                        
                        else if (ch == '\'')
                        
                            currentChunk = nextChunk = SqlScriptChunkTypes.QuotIdentifierOrLiteral;
                            currentLine = nextLine = lineCounter;
                        
                        else if (ch == '-' && (_isNextChar && _nextChar == '-'))
                        
                            nextCharHandled = true;
                            currentChunk = nextChunk = SqlScriptChunkTypes.CommentLine;
                            currentLine = nextLine = lineCounter;
                        
                        else if (ch == '/' && (_isNextChar && _nextChar == '*'))
                        
                            nextCharHandled = true;
                            currentChunk = nextChunk = SqlScriptChunkTypes.CommentMultiline;
                            currentLine = nextLine = lineCounter;
                        
                        else if (ch == ']')
                        
                            throw new SqlBadSyntaxException("Incorrect syntax near ']'.", _lineEnd + lineCounter, _filePath);
                        
                        else if (ch == '*' && (_isNextChar && _nextChar == '/'))
                        
                            throw new SqlBadSyntaxException("Incorrect syntax near '*'.", _lineEnd + lineCounter, _filePath);
                        
                        break;

                    case SqlScriptChunkTypes.CommentLine:

                        if (ch == '\r' && (_isNextChar && _nextChar == '\n'))
                        
                            nextCharHandled = true;
                            currentChunk = nextChunk = SqlScriptChunkTypes.InstructionOrUnquotedIdentifier;
                            currentLine = nextLine = lineCounter;
                        
                        else if (ch == '\n' || ch == '\r')
                        
                            currentChunk = nextChunk = SqlScriptChunkTypes.InstructionOrUnquotedIdentifier;
                            currentLine = nextLine = lineCounter;
                        
                        break;

                    case SqlScriptChunkTypes.CommentMultiline:

                        if (ch == '*' && (_isNextChar && _nextChar == '/'))
                        
                            nextCharHandled = true;
                            nextChunk = SqlScriptChunkTypes.InstructionOrUnquotedIdentifier;
                            nextLine = lineCounter;
                        
                        else if (ch == '/' && (_isNextChar && _nextChar == '*'))
                        
                            throw new SqlBadSyntaxException("Missing end comment mark '*/'.", _lineEnd + currentLine, _filePath);
                        
                        break;

                    case SqlScriptChunkTypes.BracketIdentifier:

                        if (ch == ']')
                        
                            nextChunk = SqlScriptChunkTypes.InstructionOrUnquotedIdentifier;
                            nextLine = lineCounter;
                        
                        break;

                    case SqlScriptChunkTypes.DblQuotIdentifierOrLiteral:

                        if (ch == '"')
                        
                            if (_isNextChar && _nextChar == '"')
                            
                                nextCharHandled = true;
                            
                            else
                            
                                nextChunk = SqlScriptChunkTypes.InstructionOrUnquotedIdentifier;
                                nextLine = lineCounter;
                            
                        
                        break;

                    case SqlScriptChunkTypes.QuotIdentifierOrLiteral:

                        if (ch == '\'')
                        
                            if (_isNextChar && _nextChar == '\'')
                            
                                nextCharHandled = true;
                            
                            else
                            
                                nextChunk = SqlScriptChunkTypes.InstructionOrUnquotedIdentifier;
                                nextLine = lineCounter;
                            
                        
                        break;
                
            
            else
                nextCharHandled = false;

            foundGO = false;
            if (currentChunk == SqlScriptChunkTypes.InstructionOrUnquotedIdentifier || go >= 5 || (go == 4 && currentChunk == SqlScriptChunkTypes.CommentLine))
            
                // go = 0 - break, 1 - begin of the string, 2 - spaces after begin of the string, 3 - G or g, 4 - O or o, 5 - spaces after GO, 6 - line comment after valid GO
                switch (go)
                
                    case 0:
                        if (ch == '\r' || ch == '\n')
                            go = 1;
                        break;
                    case 1:
                        if (ch == ' ' || ch == '\t')
                            go = 2;
                        else if (ch == 'G' || ch == 'g')
                            go = 3;
                        else if (ch != '\n' && ch != '\r')
                            go = 0;
                        break;
                    case 2:
                        if (ch == 'G' || ch == 'g')
                            go = 3;
                        else if (ch == '\n' || ch == '\r')
                            go = 1;
                        else if (ch != ' ' && ch != '\t')
                            go = 0;
                        break;
                    case 3:
                        if (ch == 'O' || ch == 'o')
                            go = 4;
                        else if (ch == '\n' || ch == '\r')
                            go = 1;
                        else
                            go = 0;
                        break;
                    case 4:
                        if (ch == '\r' && (_isNextChar && _nextChar == '\n'))
                            go = 5;
                        else if (ch == '\n' || ch == '\r')
                            foundGO = true;
                        else if (ch == ' ' || ch == '\t')
                            go = 5;
                        else if (ch == '-' && (_isNextChar && _nextChar == '-'))
                            go = 6;
                        else
                            go = 0;
                        break;
                    case 5:
                        if (ch == '\r' && (_isNextChar && _nextChar == '\n'))
                            go = 5;
                        else if (ch == '\n' || ch == '\r')
                            foundGO = true;
                        else if (ch == '-' && (_isNextChar && _nextChar == '-'))
                            go = 6;
                        else if (ch != ' ' && ch != '\t')
                            throw new SqlBadSyntaxException("Incorrect syntax was encountered while parsing go.", _lineEnd + lineCounter, _filePath);
                        break;
                    case 6:
                        if (ch == '\r' && (_isNextChar && _nextChar == '\n'))
                            go = 6;
                        else if (ch == '\n' || ch == '\r')
                            foundGO = true;
                        break;
                    default:
                        go = 0;
                        break;
                
            
            else
                go = 0;

            if (foundGO)
            
                if (ch == '\r' || ch == '\n')
                
                    ++lineCounter;
                
                // clear GO
                string s = line.Append(ch).ToString();
                for (int i = 0; i < s.Length; i++)
                
                    switch (s[i])
                    
                        case ' ': continue;
                        case '\t': continue;
                        case '\r': continue;
                        case '\n': continue;
                        default:
                            _lineStart = _lineEnd;
                            _lineEnd += lineCounter;
                            return allLines.Append(s.Substring(0, i)).ToString();
                    
                
                return string.Empty;
            

            // accumulate by string
            if (ch == '\r' && (_isNextChar == false || _nextChar != '\n'))
            
                ++lineCounter;
                if (syntaxCheck == false)
                    allLines.Append(line.Append('\r').ToString());
                line.Clear();
            
            else if (ch == '\n')
            
                ++lineCounter;
                if (syntaxCheck == false)
                    allLines.Append(line.Append('\n').ToString());
                line.Clear();
            
            else
            
                if (syntaxCheck == false)
                    line.Append(ch);
            
        

        // this is the end of the stream, return it without GO, if GO exists
        switch (currentChunk)
        
            case SqlScriptChunkTypes.InstructionOrUnquotedIdentifier:
            case SqlScriptChunkTypes.CommentLine:
                break;
            case SqlScriptChunkTypes.CommentMultiline:
                if (nextChunk != SqlScriptChunkTypes.InstructionOrUnquotedIdentifier)
                    throw new SqlBadSyntaxException("Missing end comment mark '*/'.", _lineEnd + currentLine, _filePath);
                break;
            case SqlScriptChunkTypes.BracketIdentifier:
                if (nextChunk != SqlScriptChunkTypes.InstructionOrUnquotedIdentifier)
                    throw new SqlBadSyntaxException("Unclosed quotation mark [.", _lineEnd + currentLine, _filePath);
                break;
            case SqlScriptChunkTypes.DblQuotIdentifierOrLiteral:
                if (nextChunk != SqlScriptChunkTypes.InstructionOrUnquotedIdentifier)
                    throw new SqlBadSyntaxException("Unclosed quotation mark \".", _lineEnd + currentLine, _filePath);
                break;
            case SqlScriptChunkTypes.QuotIdentifierOrLiteral:
                if (nextChunk != SqlScriptChunkTypes.InstructionOrUnquotedIdentifier)
                    throw new SqlBadSyntaxException("Unclosed quotation mark '.", _lineEnd + currentLine, _filePath);
                break;
        

        if (go >= 4)
        
            string s = line.ToString();
            for (int i = 0; i < s.Length; i++)
            
                switch (s[i])
                
                    case ' ': continue;
                    case '\t': continue;
                    case '\r': continue;
                    case '\n': continue;
                    default:
                        _lineStart = _lineEnd;
                        _lineEnd += lineCounter + 1;
                        return allLines.Append(s.Substring(0, i)).ToString();
                
            
        

        _lineStart = _lineEnd;
        _lineEnd += lineCounter + 1;
        return allLines.Append(line.ToString()).ToString();
    

    bool ReadChar(out char ch)
    
        if (_isNextChar)
        
            ch = _nextChar;
            if (_sr.EndOfStream)
                _isNextChar = false;
            else
                _nextChar = Convert.ToChar(_sr.Read());
            return true;
        
        else if (_sr.EndOfStream == false)
        
            ch = Convert.ToChar(_sr.Read());
            if (_sr.EndOfStream == false)
            
                _isNextChar = true;
                _nextChar = Convert.ToChar(_sr.Read());
            
            return true;
        
        else
        
            ch = '\0';
            return false;
        
    

    public static int ExecuteSqlFile(string filePath, SqlConnection connection, Encoding fileEncoding, int commandTimeout)
    
        int rowsAffected = 0;
        using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
        
            // Simple syntax check (you can comment out these two lines below)
            new SqlStatementReader(new StreamReader(fs, fileEncoding)).LightSyntaxCheck();
            fs.Seek(0L, SeekOrigin.Begin);

            // Read statements without GO
            SqlStatementReader rd = new SqlStatementReader(new StreamReader(fs, fileEncoding));
            string stmt;
            while ((stmt = rd.ReadStatement()) != null)
            
                using (SqlCommand cmd = connection.CreateCommand())
                
                    cmd.CommandText = stmt;
                    cmd.CommandTimeout = commandTimeout;
                    int i = cmd.ExecuteNonQuery();
                    if (i > 0)
                        rowsAffected += i;
                
            
        
        return rowsAffected;
    

【讨论】:

【参考方案5】:

如果你不想使用 SMO,比如你需要跨平台,你也可以使用 SubText 中的 ScriptSplitter 类。

Here's C# & VB.NET 中的实现

用法:

    string strSQL = @"
SELECT * FROM INFORMATION_SCHEMA.columns
GO
SELECT * FROM INFORMATION_SCHEMA.views
";

    foreach(string Script in new Subtext.Scripting.ScriptSplitter(strSQL ))
    
        Console.WriteLine(Script);
    

如果您对多行 c 样式 cmets 有问题,请使用正则表达式删除 cmets:

static string RemoveCstyleComments(string strInput)

    string strPattern = @"/[*][\w\d\s]+[*]/";
    //strPattern = @"/\*.*?\*/"; // Doesn't work
    //strPattern = "/\\*.*?\\*/"; // Doesn't work
    //strPattern = @"/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/ "; // Doesn't work
    //strPattern = @"/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/ "; // Doesn't work

    // http://***.com/questions/462843/improving-fixing-a-regex-for-c-style-block-comments
    strPattern = @"/\*(?>(?:(?>[^*]+)|\*(?!/))*)\*/";  // Works !

    string strOutput = System.Text.RegularExpressions.Regex.Replace(strInput, strPattern, string.Empty, System.Text.RegularExpressions.RegexOptions.Multiline);
    Console.WriteLine(strOutput);
    return strOutput;
 // End Function RemoveCstyleComments

删除单行 cmets 在这里:

https://***.com/questions/9842991/regex-to-remove-single-line-sql-comments

【讨论】:

这个类是否考虑/* Go */的情况? @cMinor:不在拆分器中,但您可以在拆分之前使用正则表达式删除多行 cmets。【参考方案6】:

使用能够理解 GO 分隔符的 SQL Server 管理对象 (SMO)。在此处查看我的博文:http://weblogs.asp.net/jongalloway/Handling-_2200_GO_2200_-Separators-in-SQL-Scripts-2D00-the-easy-way

示例代码:

public static void Main()    
        
  string scriptDirectory = "c:\\temp\\sqltest\\";
  string sqlConnectionString = "Integrated Security=SSPI;" +
  "Persist Security Info=True;Initial Catalog=Northwind;Data Source=(local)";
  DirectoryInfo di = new DirectoryInfo(scriptDirectory);
  FileInfo[] rgFiles = di.GetFiles("*.sql");
  foreach (FileInfo fi in rgFiles)
  
        FileInfo fileInfo = new FileInfo(fi.FullName);
        string script = fileInfo.OpenText().ReadToEnd();
        using (SqlConnection connection = new SqlConnection(sqlConnectionString))
        
            Server server = new Server(new ServerConnection(connection));
            server.ConnectionContext.ExecuteNonQuery(script);
        
   

如果这对您不起作用,请参阅处理该问题的 Phil Haack 的库:http://haacked.com/archive/2007/11/04/a-library-for-executing-sql-scripts-with-go-separators-and.aspx

【讨论】:

如何与事务集成?当使用 SqlConnection 创建 ServerConnection 时,该代码会引发 InvalidOperationException,其中包含挂起的事务。 此解决方案有效,我只想补充一点,如果您想将 transactionsTransactionScope 对象一起使用,您只需要注册与当前环境事务的连接即可。在这里查看我的答案:***.com/a/18322938/1268570 效果很好,但是我们可以使用 SqlConnection.InfoMessage) 在 C# 应用程序中查看结果将结果保存在 txt 文件中,以了解脚本是否成功执行,因为最近在远程主机上执行 150 mb 脚本文件时使用sqlcmd,55 分钟后,一些行受到此错误的影响,TCP Provider: An existing connection was forcibly closed by the remote host.communication link failure。 ,不知道影响的行数,但我担心运行数据库生成的脚本文件时出现错误消息。 当机器上没有安装某些 SQL Dll 时,此解决方案会导致您的代码失败。 .NET 使用 Windows 中内置的一些 dll。某些 SQL 功能包(包括管理对象)的缺失可能会阻止类似“未找到 Microsoft.SqlServer.SqlClrProvider.dll”的错误。修复它(这并不容易)下一个错误将是“Microsoft.SqlServer.BathParser.dll”等找到其他解决方案以确保您的应用程序的灵活性。 这是行不通的,特别是如果其中有一个 alter procedure 语句。它只是抱怨它需要成为批处理中的第一个语句,也就是说,因为在它之前有一个批处理分隔符,但这仍然会引发错误。【参考方案7】:

您可以使用SQL Management Objects 来执行此操作。这些与 Management Studio 用于执行查询的对象相同。我相信Server.ConnectionContext.ExecuteNonQuery() 会满足您的需求。

【讨论】:

【参考方案8】:

我遇到了同样的问题,最终通过简单的字符串替换解决了这个问题,用分号 (;) 替换了单词 GO

在使用内联 cmets、块 cmets 和 GO 命令执行脚本时,一切似乎都运行良好

public static bool ExecuteExternalScript(string filePath)

    using (StreamReader file = new StreamReader(filePath))
    using (SqlConnection conn = new SqlConnection(dbConnStr))
    
        StringBuilder sql = new StringBuilder();

        string line;
        while ((line = file.ReadLine()) != null)
        
            // replace GO with semi-colon
            if (line == "GO")
                sql.Append(";");
            // remove inline comments
            else if (line.IndexOf("--") > -1)
                sql.AppendFormat(" 0 ", line.Split(new string[]  "--" , StringSplitOptions.None)[0]);
            // just the line as it is
            else
                sql.AppendFormat(" 0 ", line);
        
        conn.Open();

        SqlCommand cmd = new SqlCommand(sql.ToString(), conn);
        cmd.ExecuteNonQuery();
    

    return true;

【讨论】:

它不适用于需要在自己的批处理中的 DDL 命令。例如。创建/更改表等 另外,您似乎无缘无故地删除了 cmets。例如,这会破坏任何包含 -- 的字符串。 嗨 Blorgbeard - SQL Server 2012 似乎可以正常处理 DDL 语句。我使用的脚本允许我重建整个数据库结构,擦除当前结构,构建表,添加索引等。我想;也完成了一批? 此外,删除 cmets 是因为这将产生单行 SQL,因此注释后的任何 SQL 都将被注释掉,但如果有一个字符串包含 - 我同意你的观点不是评论。 啊,好的,刚刚查了一下:“CREATE DEFAULT、CREATE FUNCTION、CREATE PROCEDURE、CREATE RULE、CREATE SCHEMA、CREATE TRIGGER、CREATE VIEW语句不能与其他语句在一个批处理中组合。 CREATE 语句必须启动批处理。该批处理中的所有其他语句将被解释为第一个 CREATE 语句定义的一部分。不能更改表,然后在同一批处理中引用新列。"【参考方案9】:

使用下面的方法来拆分字符串,并逐批执行

using System;
using System.IO;
using System.Text.RegularExpressions;
namespace RegExTrial

    class Program
    
        static void Main(string[] args)
        
            string sql = String.Empty;
            string path=@"D:\temp\sample.sql";
            using (StreamReader reader = new StreamReader(path)) 
                sql = reader.ReadToEnd();
                        
            //Select any GO (ignore case) that starts with at least 
            //one white space such as tab, space,new line, verticle tab etc
            string pattern="[\\s](?i)GO(?-i)";

            Regex matcher = new Regex(pattern, RegexOptions.Compiled);
            int start = 0;
            int end = 0;
            Match batch=matcher.Match(sql);
            while (batch.Success) 
                end = batch.Index;
                string batchQuery = sql.Substring(start, end - start).Trim();
                //execute the batch
                ExecuteBatch(batchQuery);
                start = end + batch.Length;
                batch = matcher.Match(sql,start);
            

        

        private static void ExecuteBatch(string command)
         
            //execute your query here
        

    

【讨论】:

【参考方案10】:

如果您不想使用 SMO(这比下面的解决方案更好,但我想提供一个替代方案...)您可以使用此功能拆分您的查询。

它是:

评论证明(例如 --GO 或 /* GO */) 仅适用于新行,就像在 SSMS 中一样(示例 /* test /* GO 有效并选择 1 作为 go not

字符串证明(例如 print 'no go')

private List<string> SplitScriptGo(string script)

    var result = new List<string>();
    int pos1 = 0;
    int pos2 = 0;
    bool whiteSpace = true;
    bool emptyLine = true;
    bool inStr = false;
    bool inComment1 = false;
    bool inComment2 = false;

    while (true)
    
        while (pos2 < script.Length && Char.IsWhiteSpace(script[pos2]))
        
            if (script[pos2] == '\r' || script[pos2] == '\n')
            
                emptyLine = true;
                inComment1 = false;
            

            pos2++;
        

        if (pos2 == script.Length)
            break;

        bool min2 = (pos2 + 1) < script.Length;
        bool min3 = (pos2 + 2) < script.Length;

        if (!inStr && !inComment2 && min2 && script.Substring(pos2, 2) == "--")
            inComment1 = true;

        if (!inStr && !inComment1 && min2 && script.Substring(pos2, 2) == "/*")
            inComment2 = true;

        if (!inComment1 && !inComment2 && script[pos2] == '\'')
            inStr = !inStr;

        if (!inStr && !inComment1 && !inComment2 && emptyLine
            && (min2 && script.Substring(pos2, 2).ToLower() == "go")
            && (!min3 || char.IsWhiteSpace(script[pos2 + 2]) || script.Substring(pos2 + 2, 2) == "--" || script.Substring(pos2 + 2, 2) == "/*"))
        
            if (!whiteSpace)
                result.Add(script.Substring(pos1, pos2 - pos1));

            whiteSpace = true;
            emptyLine = false;
            pos2 += 2;
            pos1 = pos2;
        
        else
        
            pos2++;
            whiteSpace = false;

            if (!inComment2)
                emptyLine = false;
        

        if (!inStr && inComment2 && pos2 > 1 && script.Substring(pos2 - 2, 2) == "*/")
            inComment2 = false;
    

    if (!whiteSpace)
        result.Add(script.Substring(pos1));

    return result;

【讨论】:

【参考方案11】:

我在 java 中遇到了同样的问题,我用一些逻辑和正则表达式解决了它。我相信可以应用相同的逻辑。首先我从 slq 文件中读取到内存中。然后我应用以下逻辑。这几乎是之前所说的,但是我相信使用正则表达式单词绑定比期待新行字符更安全。

String pattern = "\\bGO\\b|\\bgo\\b";

String[] splitedSql = sql.split(pattern);
for (String chunk : splitedSql) 
  getJdbcTemplate().update(chunk);

这基本上将 sql 字符串拆分为一个 sql 字符串数组。正则表达式基本上是检测小写或大写的完整“go”词。然后依次执行不同的查询。

【讨论】:

小心:你将如何分割这个? insert into books values ('1478355824', 'An Introduction To Programming in Go (paperback)', 9.00) 好点 :-) 我的情况没有插入数据。只是创建表、存储过程和函数。在我的特殊情况下,bound 这个词更有用,因为它还处理了最后一行的“go”。【参考方案12】:

基于 Blorgbeard 的解决方案。

foreach (var sqlBatch in commandText.Split(new[]  "GO" , StringSplitOptions.RemoveEmptyEntries))

   sqlCommand.CommandText = sqlBatch;
   sqlCommand.ExecuteNonQuery();

【讨论】:

new[] "GO", "Go", "go" new[] "GO", "Go", "go", "gO" 只要您对代码中的两个字母没有其他用途,例如 GOTO-Statements 或 cmets,就可以使用。 new[] "GO --DELIMITER" 【参考方案13】:

我今天通过将我的 SQL 从文本文件加载到一个字符串中来实现这一点。然后我使用字符串拆分函数将字符串分成单独的命令,然后单独发送到服务器。简单的:)

刚刚意识到您需要在 \nGO 上进行拆分,以防万一字母 GO 出现在您的任何表名等中。我猜我很幸运!

【讨论】:

【参考方案14】:

太难了:)

创建字符串数组 str[] 将 GO 替换为 ",@" :

            string[] str =
                @"
USE master;
",@"


CREATE DATABASE " +con_str_initdir+ @";
",@"
-- Verify the database files and sizes
--SELECT name, size, size*1.0/128 AS [Size in MBs] 
--SELECT name 
--FROM sys.master_files
--WHERE name = N'" + con_str_initdir + @"';
--GO

USE " + con_str_initdir + @";
",@"

SET ANSI_NULLS ON
",@"
SET QUOTED_IDENTIFIER ON
",@"

IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Customers]') AND type in (N'U'))
BEGIN
CREATE TABLE [dbo].[Customers](
    [CustomerID] [int] IDENTITY(1,1) NOT NULL,
    [CustomerName] [nvarchar](50) NULL,
 CONSTRAINT [PK_Customers] PRIMARY KEY CLUSTERED 
(
    [CustomerID] ASC
)WITH (PAD_INDEX  = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
END
",@"



SET ANSI_NULLS ON
",@"
SET QUOTED_IDENTIFIER ON
",@"
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[GOODS]') AND type in (N'U'))
BEGIN
CREATE TABLE [dbo].[GOODS](
    [GoodsID] [int] IDENTITY(1,1) NOT NULL,
    [GoodsName] [nvarchar](50) NOT NULL,
    [GoodsPrice] [float] NOT NULL,
 CONSTRAINT [PK_GOODS] PRIMARY KEY CLUSTERED 
(
    [GoodsID] ASC
)WITH (PAD_INDEX  = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
END
",@"
SET ANSI_NULLS ON
",@"
SET QUOTED_IDENTIFIER ON
",@"
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Orders]') AND type in (N'U'))
BEGIN
CREATE TABLE [dbo].[Orders](
    [OrderID] [int] IDENTITY(1,1) NOT NULL,
    [CustomerID] [int] NOT NULL,
    [Date] [smalldatetime] NOT NULL,
 CONSTRAINT [PK_Orders] PRIMARY KEY CLUSTERED 
(
    [OrderID] ASC
)WITH (PAD_INDEX  = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
END
",@"
SET ANSI_NULLS ON
",@"
SET QUOTED_IDENTIFIER ON
",@"
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[OrderDetails]') AND type in (N'U'))
BEGIN
CREATE TABLE [dbo].[OrderDetails](
    [OrderID] [int] NOT NULL,
    [GoodsID] [int] NOT NULL,
    [Qty] [int] NOT NULL,
    [Price] [float] NOT NULL,
 CONSTRAINT [PK_OrderDetails] PRIMARY KEY CLUSTERED 
(
    [OrderID] ASC,
    [GoodsID] ASC
)WITH (PAD_INDEX  = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
END
",@"

SET ANSI_NULLS ON
",@"
SET QUOTED_IDENTIFIER ON
",@"
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[InsertCustomers]') AND type in (N'P', N'PC'))
BEGIN
EXEC dbo.sp_executesql @statement = N'-- =============================================
-- Author:      <Author,,Name>
-- Create date: <Create Date,,>
-- Description: <Description,,>
-- =============================================
create PROCEDURE [dbo].[InsertCustomers]
 @CustomerName nvarchar(50),
 @Identity int OUT
AS
INSERT INTO Customers (CustomerName) VALUES(@CustomerName)
SET @Identity = SCOPE_IDENTITY()

' 
END
",@"
IF NOT EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[FK_Orders_Customers]') AND parent_object_id = OBJECT_ID(N'[dbo].[Orders]'))
ALTER TABLE [dbo].[Orders]  WITH CHECK ADD  CONSTRAINT [FK_Orders_Customers] FOREIGN KEY([CustomerID])
REFERENCES [dbo].[Customers] ([CustomerID])
ON UPDATE CASCADE
",@"
ALTER TABLE [dbo].[Orders] CHECK CONSTRAINT [FK_Orders_Customers]
",@"
IF NOT EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[FK_OrderDetails_GOODS]') AND parent_object_id = OBJECT_ID(N'[dbo].[OrderDetails]'))
ALTER TABLE [dbo].[OrderDetails]  WITH CHECK ADD  CONSTRAINT [FK_OrderDetails_GOODS] FOREIGN KEY([GoodsID])
REFERENCES [dbo].[GOODS] ([GoodsID])
ON UPDATE CASCADE
",@"
ALTER TABLE [dbo].[OrderDetails] CHECK CONSTRAINT [FK_OrderDetails_GOODS]
",@"
IF NOT EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[FK_OrderDetails_Orders]') AND parent_object_id = OBJECT_ID(N'[dbo].[OrderDetails]'))
ALTER TABLE [dbo].[OrderDetails]  WITH CHECK ADD  CONSTRAINT [FK_OrderDetails_Orders] FOREIGN KEY([OrderID])
REFERENCES [dbo].[Orders] ([OrderID])
ON UPDATE CASCADE
ON DELETE CASCADE
",@"
ALTER TABLE [dbo].[OrderDetails] CHECK CONSTRAINT [FK_OrderDetails_Orders]


                ";


            for(int i =0; i<str.Length;i++)     
            
                myCommand.CommandText=str[i];
                try
                
                myCommand.ExecuteNonQuery();
                
                catch (SystemException ee)
                
                    MessageBox.Show("Error   "+ee.ToString());
                

            

就是这样,享受吧。

【讨论】:

【参考方案15】:

如果您不想安装 SMO 对象,可以使用 gplex 工具(请参阅this answer)

【讨论】:

【参考方案16】:

如果您不想走 SMO 路线,您可以搜索“GO”并将“;”替换为“;”和你想要的查询。请注意,只会返回最后一个结果集。

【讨论】:

他们正在执行 ExecuteNonQuery。这是迄今为止更简单的方法。 使用“GO”将允许您在批处理的下一个命令中再次定义相同的变量。放置分号不会这样做。【参考方案17】:

“GO”批处理分隔符关键字实际上是由 SQL Management Studio 自己使用的,因此它知道在哪里终止它正在发送到服务器的批处理,而不是传递给 SQL 服务器。如果您愿意,您甚至可以在 Management Studio 中更改关键字。

【讨论】:

【参考方案18】:

这是我为了解决眼前的问题而拼凑起来的。

private void ExecuteBatchNonQuery(string sql, SqlConnection conn) 
    string sqlBatch = string.Empty;
    SqlCommand cmd = new SqlCommand(string.Empty, conn);
    conn.Open();
    sql += "\nGO";   // make sure last batch is executed.
    try 
        foreach (string line in sql.Split(new string[2]  "\n", "\r" , StringSplitOptions.RemoveEmptyEntries)) 
            if (line.ToUpperInvariant().Trim() == "GO") 
                cmd.CommandText = sqlBatch;
                cmd.ExecuteNonQuery();
                sqlBatch = string.Empty;
             else 
                sqlBatch += line + "\n";
            
                    
     finally 
        conn.Close();
    

它要求GO命令在自己的一行,并且不会检测block-cmets,所以这种事情会被拆分,并导致错误:

ExecuteBatchNonQuery(@"
    /*
    GO
    */", conn);

【讨论】:

如果需要的话,我可以很容易地将它适配到 SqlCe,这很好——其他代码使用 Sql 连接类和命令。 我想用一个包含多个存储过程的 SQL 脚本运行这段代码,但我有点困惑,它在哪里读取 SQL?当您提到“最后一批”时,您是指 SQL 代码吗?如果是这样,您将如何确定最后一批,如果我想运行所有批次而不仅仅是最后一批怎么办?我知道的问题太多,但如果你有时间回答,谢谢。 您将 SQL 作为字符串传递给函数:string sql - 这就是整个脚本。当我提到“批处理”时,我指的是两个“GO”语句之间的一段 SQL 代码。该代码将GO 添加到脚本的末尾,以便foreach 中的代码在您没有以GO 结束脚本时不会跳过最后一批。因此编写的代码将执行所有 SQL。 我创建了一个扩展方法:internal static class SqlCommandHelper internal static void ExecuteBatchNonQuery(this SqlCommand cmd, string sql) 如果你想提高一点效率,你可以改用StringBuilder sqlBatch【参考方案19】:

我也遇到了同样的问题,除了将单个 SQL 操作拆分到单独的文件中,然后依次执行所有这些操作之外,我找不到其他方法。

显然问题不在于 DML 命令列表,它们可以在没有 GO 的情况下执行; DDL 的不同故事(创建、更改、删​​除...)

【讨论】:

以上是关于执行大型 SQL 脚本(使用 GO 命令)的主要内容,如果未能解决你的问题,请参考以下文章

使用 GO 批处理分隔符执行 SQL 脚本并读取结果

GO 语句炸毁 .NET 中的 sql 执行

SQL脚本中的go是啥意思

dbaccess如何调用sql脚本

Python中执行sql脚本时GO语句出错问题

如何使用绑定变量执行 SQL 脚本