เรียกใช้สคริปต์ SQL ขนาดใหญ่ (ด้วยคำสั่ง GO)


89

ฉันต้องการเรียกใช้ชุดคำสั่ง SQL จำนวนมาก (สร้างตารางจำนวนมากมุมมองและขั้นตอนการจัดเก็บ) จากภายในโปรแกรม C #

ข้อความเหล่านี้จำเป็นต้องแยกด้วยGOงบ แต่SqlCommand.ExecuteNonQuery()ไม่ชอบGOงบ วิธีแก้ปัญหาของฉันซึ่งฉันคิดว่าฉันจะโพสต์เพื่อการอ้างอิงคือการแยกสตริง SQL ในGOบรรทัดและดำเนินการแต่ละชุดแยกกัน

มีวิธีที่ง่ายกว่า / ดีกว่านี้ไหม?

คำตอบ:


108

ใช้ SQL Server Management Objects (SMO) ซึ่งเข้าใจตัวคั่น GO ดูโพสต์บล็อกของฉันที่นี่: 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


2
จะรวมเข้ากับธุรกรรมได้อย่างไร? รหัสจะพ่น InvalidOperationException เมื่อสร้าง ServerConnection ด้วย SqlConnection ซึ่งมีธุรกรรมที่รอดำเนินการอยู่
benPearce

1
วิธีนี้ใช้งานได้ฉันแค่ต้องการเพิ่มว่าหากคุณต้องการใช้ธุรกรรมกับTransactionScopeวัตถุคุณเพียงแค่ต้องขอการเชื่อมต่อกับธุรกรรมรอบข้างปัจจุบัน ตรวจสอบคำตอบของฉันที่นี่: stackoverflow.com/a/18322938/1268570
Jupaol

ใช้งานได้ดี แต่เราสามารถใช้ได้SqlConnection.InfoMessage) เพื่อดูผลลัพธ์ในแอปพลิเคชัน C # หรือบันทึกผลลัพธ์ในtxtไฟล์เพียงเพื่อให้ทราบว่าสคริปต์ทำงานสำเร็จหรือไม่เพราะเมื่อเร็ว ๆ นี้ใช้sqlcmdเมื่อฉันเรียกใช้ไฟล์สคริปต์ 150 mb บนโฮสต์ระยะไกลหลังจาก 55 นาที TCP Provider: An existing connection was forcibly closed by the remote host.แถวที่ได้รับผลกระทบที่มีข้อผิดพลาดนี้communication link failure, ไม่สามารถทราบแถวที่ได้รับผล แต่ฉันกังวลเกี่ยวกับข้อความแสดงข้อผิดพลาดขณะเรียกใช้ไฟล์สคริปต์ที่สร้างฐานข้อมูล
Shaiju T

5
โซลูชันนี้ทำให้รหัสของคุณล้มเหลวเมื่อไม่ได้ติดตั้ง SQL Dlls บางตัวในเครื่อง .NET ใช้ dll บางตัวที่สร้างขึ้นใน Windows การไม่มีชุดคุณลักษณะ SQL บางชุด (รวมถึงออบเจ็กต์การจัดการ) อาจป้องกันข้อผิดพลาดบางอย่างเช่นไม่พบ 'Microsoft.SqlServer.SqlClrProvider.dll' แก้ไข (ไม่ใช่เรื่องง่าย) ข้อผิดพลาดถัดไปจะเป็น 'Microsoft.SqlServer.BathParser.dll' เป็นต้นค้นหาโซลูชันอื่นเพื่อให้แน่ใจว่ามีความยืดหยุ่นสำหรับแอปพลิเคชันของคุณ
Alexandr Sargsyan

สิ่งนี้ไม่ได้ผลโดยเฉพาะอย่างยิ่งหากมีคำสั่งเปลี่ยนแปลงขั้นตอนอยู่ในนั้น เพียงแค่บ่นว่าจำเป็นต้องเป็นคำสั่งแรกในชุดงานซึ่งเป็นเพราะมีตัวคั่นชุดงานอยู่ข้างหน้า แต่สิ่งนี้ยังคงแสดงข้อผิดพลาด
Triynko

36

นี่คือสิ่งที่ฉันเคาะร่วมกันเพื่อแก้ปัญหาเฉพาะหน้า

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 เพื่อให้อยู่ในบรรทัดของตัวเองและจะไม่ตรวจพบบล็อกคอมเมนต์ดังนั้นสิ่งนี้จะถูกแยกออกและทำให้เกิดข้อผิดพลาด:

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

เป็นเรื่องดีที่ฉันสามารถปรับให้เข้ากับ SqlCe ได้อย่างง่ายดายหากจำเป็น - โค้ดอื่น ๆ ใช้คลาสและคำสั่งการเชื่อมต่อ Sql
Blue Toque

ฉันต้องการเรียกใช้โค้ดนี้ด้วยสคริปต์ SQL ที่มีโพรซีเดอร์ที่เก็บไว้มากมายภายใน แต่ฉันสับสนเล็กน้อยมันอ่าน SQL ได้ที่ไหน เมื่อคุณอ้างถึง 'ชุดสุดท้าย' คุณหมายถึงรหัส SQL หรือไม่? แล้วถ้าเป็นเช่นนั้นคุณจะกำหนดชุดสุดท้ายได้อย่างไรและถ้าฉันต้องการรันแบทช์ทั้งหมดไม่ใช่แค่ชุดสุดท้ายล่ะ? ฉันรู้คำถามมากเกินไป แต่ขอบคุณถ้าคุณมีเวลาตอบ
user1676874

คุณส่ง SQL ไปยังฟังก์ชันเป็นสตริง: string sql- นั่นคือสคริปต์ทั้งหมด เมื่อฉันอ้างถึง "แบตช์" ฉันหมายถึงส่วนของโค้ด SQL ระหว่างสองคำสั่ง "GO" โค้ดจะเพิ่ม a GOไว้ที่ส่วนท้ายของสคริปต์เพื่อให้โค้ดภายในforeachไม่ข้ามชุดสุดท้ายหากคุณไม่ได้ลงท้ายสคริปต์ด้วยไฟล์GO. ดังนั้นโค้ดที่เขียนจะเรียกใช้ SQL ทั้งหมด
Blorgbeard ออกใน

ฉันสร้างวิธีการขยาย: คลาสคงที่ภายใน SqlCommandHelper {โมฆะคงที่ภายใน ExecuteBatchNonQuery (SqlCommand cmd นี้สตริง sql)
Rob Sedgwick

1
หากคุณต้องการมีประสิทธิภาพมากขึ้นคุณสามารถใช้StringBuilder sqlBatchแทนได้
Lii

11

คุณสามารถใช้SQL Management Objectsเพื่อดำเนินการนี้ สิ่งเหล่านี้เป็นอ็อบเจ็กต์เดียวกับที่ Management Studio ใช้ในการดำเนินการสืบค้น ฉันเชื่อว่าServer.ConnectionContext.ExecuteNonQuery()จะดำเนินการในสิ่งที่คุณต้องการ


6

คีย์เวิร์ดตัวคั่นแบตช์ "GO" ถูกใช้โดย SQL Management Studio เองดังนั้นจึงรู้ว่าจะยุติแบทช์ที่กำลังส่งไปยังเซิร์ฟเวอร์ที่ใดและจะไม่ถูกส่งไปยังเซิร์ฟเวอร์ SQL คุณยังสามารถเปลี่ยนคีย์เวิร์ดใน Management Studio ได้หากต้องการ


6

ฉันดูสิ่งนี้สองสามครั้งในตอนท้ายที่ตัดสินใจด้วยการใช้งาน EF แก้ไขเล็กน้อยสำหรับ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

2
@DipakRiswadkar ใช่ล็อคคำถามนี้สองสามครั้งและไม่มีคำตอบใดที่ตรงกับความต้องการของฉันดังนั้นการมองว่าการใช้งาน EF ดูดีดังนั้นฉันจึงโพสต์คำตอบ
Filip Cordas

คำตอบที่ยอดเยี่ยมมันใช้งานได้อย่างมีเสน่ห์ขอบคุณมาก
cuongle

@ จริงๆควรรายงานให้ทีม Entity Framework ด้วย อย่างที่บอกว่านี่เป็นเพียงสำเนาที่ผ่านมาโดยมีการปรับเปลี่ยนเล็กน้อย
Filip Cordas


4

ขึ้นอยู่กับการแก้ปัญหาของ Blorgbeard

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

3
new [] {"GO", "Go", "go"}
Andrew Veriga

2
new [] {"GO", "Go", "go", "gO"}
Brandon Ward

ใช้งานได้ตราบเท่าที่คุณไม่มีการใช้งานอื่น ๆ สำหรับตัวอักษรสองตัวในรหัสของคุณเช่นคำสั่ง GOTO หรือความคิดเห็น
Patrik

3

หากคุณไม่ต้องการใช้ SMO เช่นเพราะคุณต้องข้ามแพลตฟอร์มคุณยังสามารถใช้คลาส ScriptSplitter จาก SubText

นี่คือการใช้งานใน 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 หลายบรรทัดให้ลบความคิดเห็นด้วย regex:

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

การลบความคิดเห็นบรรทัดเดียวอยู่ที่นี่:

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

ชั้นนี้พิจารณา/* Go */คดีไหม
edgarmtze

@cMinor: ไม่ได้อยู่ในตัวแยก แต่คุณสามารถลบความคิดเห็นหลายบรรทัดด้วย regex ก่อนที่คุณจะแยก
Stefan Steiger

2

ฉันประสบปัญหาเดียวกันเช่นกันและฉันไม่สามารถหาวิธีอื่นใดได้นอกจากการแยกการดำเนินการ SQL เดี่ยวในไฟล์แยกต่างหากจากนั้นดำเนินการทั้งหมดตามลำดับ

เห็นได้ชัดว่าปัญหาไม่ได้อยู่ที่รายการคำสั่ง DML พวกเขาสามารถดำเนินการได้โดยไม่ต้องไประหว่าง เรื่องราวที่แตกต่างกับ DDL (สร้างแก้ไขวาง ... )


2

หากคุณไม่ต้องการไปเส้นทาง SMO คุณสามารถค้นหาและแทนที่ "GO" สำหรับ ";" และแบบสอบถามตามที่คุณต้องการ โปรดทราบว่าเฉพาะชุดผลลัพธ์สุดท้ายจะถูกส่งกลับ


1
พวกเขากำลังดำเนินการ ExecuteNonQuery นี่เป็นวิธีที่ง่ายกว่า
DaveMorganTexas

3
การใช้ "GO" จะช่วยให้คุณกำหนดตัวแปรเดิมได้อีกครั้งในคำสั่งถัดไปในชุดงาน การวางอัฒภาคจะไม่ทำเช่นนั้น
DDRider62

2

ฉันทำได้ในวันนี้โดยการโหลด SQL ของฉันจากไฟล์ข้อความเป็นสตริงเดียว จากนั้นฉันใช้ฟังก์ชันแยกสตริงเพื่อแยกสตริงออกเป็นแต่ละคำสั่งซึ่งจะถูกส่งไปยังเซิร์ฟเวอร์ทีละคำสั่ง ซิมเปิ้ล :)

เพิ่งรู้ว่าคุณต้องแยก \ nGO ในกรณีที่ตัวอักษร GO ปรากฏในชื่อตารางของคุณเป็นต้นเดาว่าฉันโชคดีที่นั่น!


2

หากคุณไม่ต้องการใช้ SMO (ซึ่งดีกว่าวิธีแก้ปัญหาด้านล่าง แต่ฉันต้องการให้ทางเลือก ... ) คุณสามารถแยกแบบสอบถามของคุณด้วยฟังก์ชันนี้

มันคือ:

  • หลักฐานความคิดเห็น (ตัวอย่าง --GO หรือ / * GO * /)
  • ใช้งานได้เฉพาะในบรรทัดใหม่เช่นเดียวกับใน SSMS (ตัวอย่าง / * test / * GO ใช้งานได้และเลือก 1 เป็น go not
  • การพิสูจน์สตริง (ตัวอย่างการพิมพ์ 'ไม่ไป')

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

1

ใช้วิธีการต่อไปนี้เพื่อแยกสตริงและดำเนินการแบตช์ตามแบตช์

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
        }

    }
}

1

เพื่อหลีกเลี่ยงบุคคลที่สาม regexes ค่าใช้จ่ายในหน่วยความจำและการทำงานอย่างรวดเร็วกับสคริปต์ขนาดใหญ่ฉันได้สร้างโปรแกรมแยกวิเคราะห์ตามสตรีมของฉันเอง มัน

  • ตรวจสอบไวยากรณ์ก่อน
  • สามารถรับรู้ความคิดเห็นด้วย - หรือ / ** /

    -- some commented text
     /*
    drop table Users;
    GO
       */
    
  • สามารถจดจำตัวอักษรสตริงด้วย "หรือ"

    set @s =
        'create table foo(...);
        GO
        create index ...';
    
  • รักษาการจัดรูปแบบ LF และ CR
  • รักษาบล็อกความคิดเห็นในวัตถุ (ขั้นตอนการจัดเก็บมุมมอง ฯลฯ )
  • และสิ่งปลูกสร้างอื่น ๆ เช่น

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

0

ฉันมีปัญหาเดียวกันใน java และฉันแก้ไขด้วยตรรกะและ regex เล็กน้อย ฉันเชื่อว่าสามารถใช้ตรรกะเดียวกันได้ก่อนอื่นฉันอ่านจากไฟล์ slq ลงในหน่วยความจำ จากนั้นฉันใช้ตรรกะต่อไปนี้ เป็นสิ่งที่เคยพูดมาก่อนหน้านี้มาก แต่ฉันเชื่อว่าการใช้ regex word bound นั้นปลอดภัยกว่าการคาดหวังการสร้างบรรทัดใหม่

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

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

สิ่งนี้จะแยกสตริง sql ออกเป็นอาร์เรย์ของสตริง sql โดยทั่วไปแล้ว regex จะตรวจจับคำว่า 'go' แบบเต็มไม่ว่าจะเป็นตัวพิมพ์เล็กหรือตัวพิมพ์ใหญ่ จากนั้นคุณดำเนินการแบบสอบถามต่างๆตามลำดับ


1
ระวัง: คุณจะแยกสิ่งนี้อย่างไร? insert into books values ('1478355824', 'An Introduction To Programming in Go (paperback)', 9.00)
Blorgbeard ออกใน

จุดดี :-) สถานการณ์ของฉันไม่มีการแทรกข้อมูล เป็นเพียงการสร้างตารางจัดเก็บกระบวนงานและฟังก์ชัน คำว่าผูกพันมีประโยชน์มากกว่าในกรณีเฉพาะของฉันเพราะมันดูแล 'ไป' ในบรรทัดสุดท้ายด้วย
jbrunodomingues

0

ฉันประสบปัญหาเดียวกันนี้และในที่สุดก็แก้ไขได้โดยการแทนที่สตริงอย่างง่ายแทนที่คำว่า GO ด้วยอัฒภาค (;)

ดูเหมือนว่าทั้งหมดจะทำงานได้ดีในขณะที่เรียกใช้สคริปต์ด้วยความคิดเห็นในบรรทัดความคิดเห็นบล็อกและคำสั่ง 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;
}

1
จะใช้ไม่ได้กับคำสั่ง DDL ซึ่งจำเป็นต้องอยู่ในชุดของตัวเอง เช่น create / alter table et al
Blorgbeard จะออกใน

นอกจากนี้ดูเหมือนว่าคุณจะลบความคิดเห็นโดยไม่มีเหตุผล .. ซึ่งจะทำลายสตริงใด ๆ ที่มีอยู่--เช่น
Blorgbeard ออก

สวัสดี Blorgbeard - ดูเหมือนว่า SQL Server 2012 จะจัดการคำสั่ง DDL ได้ดี สคริปต์ที่ฉันใช้คืออนุญาตให้ฉันสร้างโครงสร้างฐานข้อมูลใหม่ทั้งหมดล้างโครงสร้างปัจจุบันสร้างตารางเพิ่มดัชนี ฯลฯ ฉันคิดว่า; ยังทำชุด?
Morvael

นอกจากนี้การลบความคิดเห็นก็เป็นเพราะสิ่งนี้จะสร้าง SQL บรรทัดเดียวดังนั้น SQL ใด ๆ หลังจากความคิดเห็นจะถูกแสดงความคิดเห็น แต่ฉันรับประเด็นของคุณหากมีสตริงที่มีอยู่นั่นไม่ใช่ความคิดเห็น
Morvael

1
โอเคเพิ่งค้นพบ: "สร้างค่าเริ่มต้นสร้างฟังก์ชันสร้างกระบวนการสร้างกฎสร้างแผนผังสร้างทริกเกอร์และสร้างมุมมองคำสั่งไม่สามารถใช้ร่วมกับคำสั่งอื่นในชุดคำสั่ง CREATE ต้องเริ่มต้นชุดงานทั้งหมด คำสั่งอื่น ๆ ที่ตามมาในชุดนั้นจะถูกตีความเป็นส่วนหนึ่งของคำจำกัดความของคำสั่ง CREATE แรกตารางไม่สามารถเปลี่ยนแปลงได้แล้วคอลัมน์ใหม่ที่อ้างอิงในชุดงานเดียวกัน "
Blorgbeard ออก

-1

สำหรับใครที่ยังมีปัญหา. คุณสามารถใช้ Microsoft 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 ปีที่แล้ว!)
Blorgbeard ออก

-4

ยากมาก :)

สร้างอาร์เรย์ของสตริง 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());
                }

            }

แค่นี้ก็สนุกแล้ว

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.