执行大型 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,其中包含挂起的事务。 此解决方案有效,我只想补充一点,如果您想将 transactions 与TransactionScope
对象一起使用,您只需要注册与当前环境事务的连接即可。在这里查看我的答案:***.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 命令)的主要内容,如果未能解决你的问题,请参考以下文章