Wykonaj duży skrypt SQL (za pomocą poleceń GO)

89

Muszę wykonać duży zestaw instrukcji SQL (tworząc kilka tabel, widoków i procedur składowanych) z poziomu programu C #.

Te stwierdzenia muszą być oddzielone GOoświadczeniami, ale SqlCommand.ExecuteNonQuery()nie lubi GOoświadczeń. Moim rozwiązaniem, które, jak przypuszczam, opublikuję jako odniesienie, było podzielenie ciągu SQL na GOwiersze i wykonanie każdej partii osobno.

Czy jest łatwiejszy / lepszy sposób?

Blorgbeard wyszedł
źródło

Odpowiedzi:

107

Użyj obiektów zarządzania SQL Server (SMO), które rozumieją separatory GO. Zobacz mój post na blogu tutaj: http://weblogs.asp.net/jongalloway/Handling-_2200_GO_2200_-Separators-in-SQL-Scripts- 2D00 -the-easy-way

Przykładowy kod:

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);
        }
   }
}

Jeśli to nie zadziała, zobacz bibliotekę Phila Haacka, która to obsługuje: http://haacked.com/archive/2007/11/04/a-library-for-executing-sql-scripts-with-go-separators -and.aspx

Jon Galloway
źródło
2
Jak można to zintegrować z transakcją? Kod zgłasza InvalidOperationException podczas tworzenia ServerConnection z SqlConnection, w którym znajduje się oczekująca transakcja.
benPearce
1
To rozwiązanie działa, chcę tylko dodać, że jeśli chcesz używać transakcji z TransactionScopeobiektem, wystarczy wpisać połączenie z bieżącą transakcją otoczenia. Sprawdź moją odpowiedź tutaj: stackoverflow.com/a/18322938/1268570
Jupaol
działa świetnie, ale czy możemy użyć SqlConnection.InfoMessage), aby zobaczyć wynik w aplikacji C # lub zapisać wynik w txtpliku, tylko po to, aby wiedzieć, czy skrypt został pomyślnie wykonany, ponieważ ostatnio używam, sqlcmdgdy wykonałem plik skryptu 150 MB na zdalnym hoście, po 55 minutach wierszy zostało dokonane z tego błędu TCP Provider: An existing connection was forcibly closed by the remote host., communication link failure. , liczba wykonanych wierszy może być znana, ale obawiam się komunikatów o błędach podczas uruchamiania pliku skryptu wygenerowanego przez bazę danych.
shaijut
5
To rozwiązanie spowodowało awarię kodu, gdy niektóre biblioteki DLL SQL nie są zainstalowane na komputerze. .NET używa niektórych bibliotek dll wbudowanych w system Windows. Brak niektórych pakietów funkcji SQL (w tym Managment Objects) może zapobiec błędom takim jak „Microsoft.SqlServer.SqlClrProvider.dll”. Naprawianie tego (nie jest to łatwa praca) następnym błędem będzie „Microsoft.SqlServer.BathParser.dll” itp. Znajdź inne rozwiązanie, aby zapewnić elastyczność swojej aplikacji.
Alexandr Sargsyan
To nie działa, zwłaszcza jeśli jest tam instrukcja zmiany procedury. Po prostu narzeka, że ​​musi to być pierwsza instrukcja w partii, czyli dlatego, że tuż przed nią znajduje się separator partii, ale nadal generuje błąd.
Triynko
36

Oto, co zapukałem, aby rozwiązać mój bezpośredni problem.

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();
    }
}

Wymaga, aby komendy GO znajdowały się we własnej linii i nie będą wykrywać komentarzy blokowych, więc tego rodzaju rzeczy zostaną podzielone i spowodują błąd:

ExecuteBatchNonQuery(@"
    /*
    GO
    */", conn);
Blorgbeard wyszedł
źródło
Fajnie, że mogę łatwo dostosować to do SqlCe w razie potrzeby - inny kod używa klas i poleceń połączenia Sql.
Blue Toque
Chcę uruchomić ten kod za pomocą skryptu SQL z kilkoma procedurami składowanymi, ale jestem trochę zdezorientowany, gdzie czyta SQL? Mówiąc o „ostatniej partii”, masz na myśli kod SQL? A jeśli tak, w jaki sposób określilibyście ostatnią partię i co jeśli chciałbym uruchomić wszystkie partie, a nie tylko ostatnią? Znam zbyt wiele pytań, ale dzięki, jeśli masz czas na odpowiedź.
user1676874
Przekazujesz kod SQL do funkcji jako ciąg znaków: string sql- to cały skrypt. Kiedy mówię o „partii”, mam na myśli fragment kodu SQL między dwoma instrukcjami „GO”. Kod dodaje a GOna końcu skryptu, aby kod wewnątrz foreachnie pomijał ostatniej partii, jeśli nie zakończyłeś skryptu rozszerzeniem GO. Tak więc napisany kod wykona cały kod SQL.
Blorgbeard wychodzi
Utworzyłem metodę rozszerzenia: wewnętrzna statyczna klasa SqlCommandHelper {wewnętrzna statyczna void ExecuteBatchNonQuery (to SqlCommand cmd, string sql)
Rob Sedgwick

1
Jeśli chcesz być trochę bardziej wydajny, możesz StringBuilder sqlBatchzamiast tego użyć .
Lii

11

W tym celu można użyć obiektów zarządzania SQL . Są to te same obiekty, których Management Studio używa do wykonywania zapytań. Wierzę, Server.ConnectionContext.ExecuteNonQuery()że wykona to, czego potrzebujesz.


6

Słowo kluczowe „GO” separatora wsadowego jest w rzeczywistości używane przez samo SQL Management Studio, dzięki czemu wie, gdzie zakończyć wysyłane do serwera partie i nie jest przekazywane do serwera SQL. Możesz nawet zmienić słowo kluczowe w Management Studio, jeśli chcesz.


6

Patrzę na to kilka razy na końcu zdecydowałem się z implementacją EF Trochę zmodyfikowany dlaSqlConnection

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();
                }
            }
        }

Dziękuję @Filip Cordas. Chociaż nie jest to oznaczone jako odpowiedź, pomogło mi to jak urok! Mieliśmy dużą liczbę skryptów, w których BatchTerminator był wymieniany na różne sposoby, takie jak kombinacje wielkich i małych liter (go, Go, GO itp.), A maksymalne czasy miały końcowe lub początkowe spacje, co powodowało duży problem przy wykonywaniu przez c # ... . Dziękuję Ci !!
DipakRiswadkar

2
@DipakRiswadkar Tak, zablokowałem się na tym pytaniu kilka razy i żadna z udzielonych odpowiedzi nie spełniała moich potrzeb, więc przyjrzałem się implementacji EF, więc opublikowałem odpowiedź.
Filip Cordas,

Świetna odpowiedź, działa jak urok, wielkie dzięki
cuongle

@Really powinien również zgłosić to zespołowi Entity Framework. Jak powiedziałem, to tylko kopia przeszłości z niewielkimi modyfikacjami.
Filip Cordas


4

Oparty na rozwiązaniu Blorgbearda.

foreach (var sqlBatch in commandText.Split(new[] { "GO" }, StringSplitOptions.RemoveEmptyEntries))
{
   sqlCommand.CommandText = sqlBatch;
   sqlCommand.ExecuteNonQuery();
}

3
nowy [] {"GO", "Go", "go"}
Andrew Veriga,
2
nowy [] {"GO", "Go", "go", "gO"}
Brandon Ward,
Działa tak długo, jak długo nie masz innego zastosowania z dwóch liter w swoim kodzie, jak GOTO-Instrukcje lub komentarze.
Patrik
3

Jeśli nie chcesz używać SMO, na przykład dlatego, że musisz działać na wielu platformach, możesz również użyć klasy ScriptSplitter z SubText.

Oto implementacja w C # i VB.NET

Stosowanie:

    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);
    }

Jeśli masz problemy z wielowierszowymi komentarzami w stylu c, usuń komentarze za pomocą wyrażenia regularnego:

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://stackoverflow.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

Usuwanie komentarzy jednowierszowych jest tutaj:

https://stackoverflow.com/questions/9842991/regex-to-remove-single-line-sql-comments
Stefan Steiger
źródło
czy ta klasa rozważa /* Go */przypadek?
edgarmtze
@cMinor: Nie w rozdzielaczu, ale możesz usunąć komentarze wielowierszowe za pomocą wyrażenia regularnego przed podziałem.
Stefan Steiger,
2

Miałem też ten sam problem i nie mogłem znaleźć innego sposobu niż podzielenie pojedynczej operacji SQL na oddzielne pliki, a następnie wykonanie ich wszystkich po kolei.

Oczywiście problem nie dotyczy list poleceń DML, można je wykonać bez GO pomiędzy nimi; inna historia z DDL (tworzenie, zmienianie, upuszczanie ...)

ila
źródło
2

Jeśli nie chcesz jechać trasą SMO, możesz wyszukać i zamienić „GO” na „;” i zapytanie tak, jak byś zrobił. Zwróć uwagę, że soly zostanie zwrócony ostatni zestaw wyników.

jason saldo
źródło
1
Wykonują ExecuteNonQuery. To zdecydowanie łatwiejszy sposób.
DaveMorganTexas
3
Użycie „GO” umożliwi ponowne zdefiniowanie tych samych zmiennych w kolejnym poleceniu w partii. Wstawienie średnika tego nie zrobi.
DDRider62
2

Osiągnąłem to dzisiaj, ładując mój SQL z pliku tekstowego do jednego ciągu. Następnie użyłem funkcji Split ciągu, aby rozdzielić ciąg na osobne polecenia, które były następnie indywidualnie wysyłane do serwera. Simples :)

Właśnie zdałem sobie sprawę, że musisz dokonać podziału \ nGO na wypadek, gdyby litery GO pojawiły się w którejś z nazw twoich tabel itp. Chyba miałem tam szczęście!

Andy Dove
źródło
2

Jeśli nie chcesz używać SMO (co jest lepsze niż poniższe rozwiązanie, ale chcę podać alternatywę ...), możesz podzielić zapytanie za pomocą tej funkcji.

To jest:

  • Dowód komentarza (przykład --GO lub / * GO * /)
  • Działa tylko na nowej linii, tak jak w SSMS (przykład / * test / * GO działa i wybierz 1 jako idź nie
  • Dowód tekstowy (przykładowy wydruk „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;
    }
    
Bigjim
źródło
1

użyj następującej metody, aby podzielić ciąg i wykonać partię po partii

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
        }

    }
}
Sriwantha Attanayake
źródło
1

Aby uniknąć stron trzecich, wyrażeń regularnych, narzutów pamięci i szybkiej pracy z dużymi skryptami, stworzyłem własny parser oparty na strumieniu. To

  • sprawdza składnię przed
  • rozpoznaje komentarze zawierające - lub / ** /

    -- some commented text
     /*
    drop table Users;
    GO
       */
    
  • potrafi rozpoznać literały łańcuchowe za pomocą „lub”

    set @s =
        'create table foo(...);
        GO
        create index ...';
    
  • zachowuje formatowanie LF i CR
  • zachowuje blok komentarzy w treściach obiektów (procedury składowane, widoki itp.)
  • i inne konstrukcje, takie jak

          gO -- commented text
    

Jak używać

    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) { }

Czytnik skryptów SQL oparty na strumieniu

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;
    }
}
Yargo
źródło
0

Miałem ten sam problem w Javie i rozwiązałem go z odrobiną logiki i wyrażenia regularnego. Wydaje mi się, że można zastosować tę samą logikę: najpierw wczytuję plik slq do pamięci. Następnie stosuję następującą logikę. To prawie to, co zostało powiedziane wcześniej, ale uważam, że użycie wyrażenia regularnego związanego ze słowem jest bezpieczniejsze niż oczekiwanie na znak nowej linii.

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

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

To w zasadzie dzieli łańcuch sql na tablicę ciągów sql. Wyrażenie regularne zasadniczo służy do wykrywania pełnych słów „go”, zarówno małych, jak i dużych. Następnie kolejno wykonujesz różne zapytania.

jbrunodomingues
źródło
1
Ostrożnie: jak to podzielisz? insert into books values ('1478355824', 'An Introduction To Programming in Go (paperback)', 9.00)
Blorgbeard wychodzi
Słuszna uwaga :-) Moja sytuacja nie zawierała danych. Po prostu tworzyłem tabele, procedury składowane i funkcje. Słowo związane było bardziej przydatne w moim konkretnym przypadku, ponieważ uwzględniło również „idź” w ostatniej linii.
jbrunodomingues
0

Trafiłem na ten sam problem i ostatecznie rozwiązałem go prostą zamianą ciągu znaków, zastępując słowo GO średnikiem (;)

Wydaje się, że wszystko działa dobrze podczas wykonywania skryptów z komentarzami w linii, komentarzami blokowymi i poleceniami 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;
}
Morvael
źródło
1
Nie będzie działać w przypadku poleceń DDL, które muszą znajdować się we własnej partii. Np.
Create
Poza tym wydaje się, że usuwasz komentarze bez powodu ... co mogłoby zepsuć jakiekolwiek ciągi zawierające --na przykład.
Blorgbeard wychodzi
Cześć Blorgbeard - SQL Server 2012 wydaje się obsługiwać instrukcje DDL OK. Skrypty, których używałem, miały mi pozwolić na przebudowanie całej struktury bazy danych, wyczyszczenie obecnej struktury, zbudowanie tabel, dodanie indeksów itp. Pomyślałem; też skończył partię?
Morvael
Usunięcie komentarzy było również spowodowane tym, że spowoduje to powstanie pojedynczej linii kodu SQL, a zatem każdy kod SQL po komentarzu zostanie zakomentowany, ale rozumiem, że jeśli istnieje ciąg, który zawierał - to nie był komentarz.
Morvael
1
Ach ok, po prostu sprawdziłem: „CREATE DEFAULT, CREATE FUNCTION, CREATE PROCEDURE, CREATE RULE, CREATE SCHEMA, CREATE TRIGGER i CREATE VIEW nie mogą być łączone z innymi instrukcjami w partii. Instrukcja CREATE musi rozpocząć wsad. Wszystko inne instrukcje występujące w tej partii będą interpretowane jako część definicji pierwszej instrukcji CREATE. Nie można zmienić tabeli, a następnie nowych kolumn w tej samej partii. "
Blorgbeard wychodzi
-1

Dla każdego, kto nadal ma problem. Możesz użyć oficjalnego SMO firmy Microsoft

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);
}
Sprot
źródło
To nie dodaje niczego ponad przegłosowaną, zaakceptowaną odpowiedzią, co również sugeruje SMO (opublikowane 10 lat temu!).
Blorgbeard wyszedł
-4

Zbyt trudne :)

Utwórz tablicę ciągów znaków [], zastępując GO przez „, @”:

            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());
                }

            }

To wszystko, baw się dobrze.

grv
źródło