【问题标题】:How to remove GO from variable so it could be executed by sp_executesql如何从变量中删除 GO,以便它可以由 sp_executesql 执行
【发布时间】:2023-04-10 14:06:02
【问题描述】:

我需要找到一种 T-SQL 方法来从我从 .sql 文件中读取的脚本中删除 GO 命令。我正在使用类似于这种方法Execute SQL scripts from file,它非常适合我的需求,但是一些文件包含 GO 命令并中断执行,因为sp_executesql 不处理非 T-SQL 命令。 如何创建一个 REPLACE 来删除 GO,它主要是单独坐在一排?或者我可以在这里应用的任何其他方法?请记住,脚本中可能还有其他 GO,它们实际上不是命令。

DECLARE @sql NVARCHAR(1000) = 
    'DECLARE @table AS TABLE(
        [Id] INT,
        [Info] NVARCHAR(100)
        );

    INSERT INTO @table
        ([Id],[Info])
    VALUES
        (1,''Info''),
        (2,''Show must go on''),
        (3,''GO'');

    SELECT * FROM @table;

    GO';

PRINT @sql;
EXEC sp_executesql @sql;

由于服务器安全限制,不能使用xp_cmdshell 执行脚本。 SQLCMD 这次也不是一个选项。

【问题讨论】:

  • 我认为真正的问题是您试图使用sys.sp_executesql 来替代sqlcmd 之类的东西。删除 GOs 很容易成为一项重大更改。
  • 试图区分字符串文字和保留关键字是一个棘手的问题,它将依赖于大量的文本解析。在 SQL Server 之外编辑脚本文件的可行性是什么?
  • 作为棺材上的另一个钉子,T-SQL 是一种用于字符串操作的糟糕语言。您需要解析批次,然后在适当的地方删除GOs。老实说,这对语言来说是一项不可能完成的任务。
  • 如果您需要以编程方式执行带有 GO 批处理终止符的脚本,请考虑使用符合 this answer 的 SMO API。 T-SQL 不是解析脚本的正确工具。
  • 如何将命令从文件中获取到 T-SQL 变量中?

标签: sql-server tsql


【解决方案1】:

好吧,我不会声称这是应该完成的方式,但把它记下来很有趣:

免责声明:这是一个演示为什么 TSQL 是错误的工具 ?

我添加了更多 GO 分隔的语句,并在其中使用了引号以使其更加棘手:

DECLARE @sql NVARCHAR(1000) = 
    'DECLARE @table AS TABLE(
        [Id] INT,
        [Info] NVARCHAR(100)
        );

    INSERT INTO @table
        ([Id],[Info])
    VALUES
        (1,''Info''),
        (2,''Show must go on''),
        (3,''This includes a GO and some "quoted" text''),
        (4,''GO'');

    SELECT * FROM @table;

    GO

    SELECT TOP 10  * FROM sys.objects
    GO
    PRINT ''Each GO will be used to have separate patches''';

--让我们从删除各种换行符开始

SET @sql = REPLACE(REPLACE(STRING_ESCAPE(@sql,'json'),'\r','\n'),'\n\n','\n');

--使用CS_AS-collat​​ion 将避免使用“go”,因为我们(希望!)可以依赖“GO”:

DECLARE @json NVARCHAR(MAX) =  CONCAT('["',REPLACE(REPLACE(@sql COLLATE Latin1_General_CS_AS,'GO' COLLATE Latin1_General_CS_AS,'","GO'),'\n','","'),'"]');

--上面我使用了每个大写的“GO”每个换行符来分隔字符串。
--这样做我们将您的字符串转换为 json 数组
--现在我们可以使用OPENJSON将其填充到一个表中以读取json-array(省略空行)

DECLARE @tbl TABLE(RowIndex INT IDENTITY,fragment NVARCHAR(MAX));

INSERT INTO @tbl(fragment)
SELECT STRING_ESCAPE(A.[value],'json') 
FROM OPENJSON(@json) A
WHERE LEN(TRIM(A.[value]))>0 AND TRIM(A.[value])!=NCHAR(9);

--光标需要这些变量

DECLARE @patch NVARCHAR(MAX);

--现在我打开一个游标
-- 我们通过运行 递归 CTE 再次构建一个 json 数组来做到这一点。
--这次我们将在大写的“GO”单独坐在一行中时将字符串分开。

DECLARE cur CURSOR FOR

WITH cte AS
(
    SELECT RowIndex, CAST(CONCAT('["',fragment) AS NVARCHAR(MAX)) growingString  
    FROM @tbl WHERE RowIndex=1

    UNION ALL

    SELECT n.RowIndex
          ,CONCAT(cte.growingString,CASE WHEN TRIM(n.fragment) COLLATE Latin1_General_CS_AS=N'GO' THEN N'","' ELSE n.fragment END)
    FROM @tbl n 
    INNER JOIN cte ON n.RowIndex=cte.RowIndex+1  
)
,thePatches AS
(
SELECT TOP 1 CONCAT(growingString,'"]') AS jsonArray 
FROM cte ORDER BY RowIndex DESC
)
SELECT A.[value] AS patch
FROM thePatches p
CROSS APPLY OPENJSON(p.jsonArray) A;

--我们可以-终于-遍历补丁并一个一个地执行它们

OPEN cur;
FETCH NEXT FROM cur INTO @patch;
WHILE @@FETCH_STATUS=0
BEGIN
    PRINT @patch; --PRINT out for visual control before execution!
    --EXEC(@patch);
    FETCH NEXT FROM cur INTO @patch;
END
CLOSE cur;
DEALLOCATE cur;

有数以百万计的东西(例如内容中的换行符、注释部分、最大递归)可以破坏这种方法。所以显然不要遵循这个建议:-)

【讨论】:

  • 整洁但(如您所知)易碎,例如GO 10 将在此类部署脚本中很常见,这将打破这个或消除循环,我没有测试。正如您和其他人所建议的那样,T-SQL 不适合这样做。 :-)
  • @AaronBertrand 当然!你是对的 ? 这更有趣,也是一种展示为什么应该用 TSQL 完成的方式
猜你喜欢
  • 2012-08-02
  • 1970-01-01
  • 2020-12-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-29
相关资源
最近更新 更多