如何在sqlcmd中输出超过4000个字符

use*_*104 8 sql-server stored-procedures sql-server-2008-r2 recovery sqlcmd

我制作了一个存储过程restoredatabase,它返回代码来恢复数据库。输出脚本如下所示:

restore database [DB]  from disk='F:\FULL1.bak' with norecovery
restore database [DB]  from disk='F:\DIFF1.bak' with norecovery
restore log [DB]  from disk='F:\log1.bak' with norecovery
restore log [DB]  from disk='F:\log2.bak' with norecovery
...
restore log [DB]  from disk='F:\logN.bak' with norecovery
restore database [DB] with recovery
Run Code Online (Sandbox Code Playgroud)

在整个 SP 中,这些行被添加到一个参数@sql varchar(max)中,该参数最后以 打印print(@sql),如果用户指定了正确的标志,则以exec(@sql). 两者都完美地工作。但是,当像这样使用 sqlcmd 调用 SP 时:

set restoresql=exec restoredatabase @DB='Databasename', @Execute='y'
sqlcmd -h-1 -S SRV\T2 -U sa -P sa -d master -Q "%restoresql%" -o output.txt
Run Code Online (Sandbox Code Playgroud)

输出文件output.txt将始终最多包含 4002 个字符。

我试过添加,-y 8000但这没有帮助。如何获取输出文件以提供完整生成的脚本?

Sol*_*zky 4

需要明确的是,问题实际上在于命令PRINT而不是SQLCMD实用程序。

我猜你的变量实际上是NVARCHAR(MAX),不是VARCHAR(MAX)因为该命令使用/PRINT限制为仅 4000 个字符。否则,它可以使用/输出最多 8000 个字符。要查看是否超过 4000 个字符,但不超过 8000 个字符,请运行以下命令:NCHARNVARCHARNVARCHARCHARVARCHAR

sqlcmd -Q "DECLARE @Yo VARCHAR(MAX) = REPLICATE(CONVERT(VARCHAR(MAX), '#'), 9000); PRINT @Yo;" -o out.txt
Run Code Online (Sandbox Code Playgroud)

如果需要打印超过 8000 个字符,则需要以最多 8000 个字符的块形式迭代变量(该命令一次PRINT最多只能显示 8000CHAR个字符)。VARCHAR但是您不想简单地将其切成 8000 个字符的块,因为这可能正好位于不应拆分的内容的中间,例如单词/对象名称/数字等。因为该命令将始终包含PRINT换行符字符结尾,最好打印每个 8000 个字符块的最后一个换行符。结果是,唯一被分割成多个显示行的行是那些在下一个换行符之前超过 8000 个字符的行,对此确实无能为力。

尝试使用以下过程代替您的print(@sql)命令(仅供参考:您不需要像 for 一样使用@sql括号)。请注意,查找字符串中的最后一个换行符需要创建一个函数,因为 T-SQL 中没有内置函数。您可以创建 T-SQL UDF 来执行此操作。您还可以使用 SQLCLR,在这种情况下您可以自己编写代码,或者直接获取SQL#库的免费版本(我是该库的作者)。PRINTEXECLastIndexOf

SET ANSI_NULLS, QUOTED_IDENTIFIER ON;

IF (OBJECT_ID(N'dbo.Display') IS NOT NULL)
BEGIN
  DROP PROCEDURE dbo.Display;
END;

GO
CREATE PROCEDURE dbo.Display
(
  @TextToDisplay VARCHAR(MAX)
)
AS
SET NOCOUNT ON;

  DECLARE @Length INT = LEN(@TextToDisplay),
          @Buffer VARCHAR(8000),
          @BufferLength INT,
          @StartIndex INT = 1,
          @LastNewlineIndex INT;

  SET @TextToDisplay = REPLACE(@TextToDisplay, CHAR(13), ''); -- normalize

  WHILE (1 = 1)
  BEGIN
    SET @Buffer = SUBSTRING(@TextToDisplay, @StartIndex, 8000);
    SET @BufferLength = DATALENGTH(@Buffer);

    IF (@BufferLength < 8000)
    BEGIN
      BREAK;
    END;

    SET @LastNewlineIndex =
                   SQL#.String_LastIndexOf(@Buffer, CHAR(10), @BufferLength, 1);

    IF (@LastNewlineIndex > 0)
    BEGIN
      PRINT SUBSTRING(@Buffer, 1, (@LastNewlineIndex - 1));
      SET @StartIndex += @LastNewlineIndex;
    END;
    ELSE
    BEGIN
      PRINT @Buffer;
      SET @StartIndex += @BufferLength;
    END;
  END; -- WHILE (1 = 1)

  -- Don't print empty line if final chunk was 8000 chars leaving final loop with 0
  IF (DATALENGTH(@Buffer) > 0)
  BEGIN
    PRINT @Buffer;
  END;
GO
Run Code Online (Sandbox Code Playgroud)

测试:

DECLARE @Test VARCHAR(MAX);
SET @Test = '';
SET @Test = @Test + '1' + REPLICATE('a', 7998) + '1';
SET @Test = @Test + '2' + REPLICATE('b', 7998) + '2';
SET @Test = @Test + '3' + REPLICATE('c', 7998) + '3';

SELECT @Test;

PRINT '=============================';
EXEC dbo.Display @Test;
PRINT '=============================';
----
SET @Test = '';
SET @Test = @Test + '1' + REPLICATE('a', 7998) + '1';
SET @Test = @Test + '2' + REPLICATE('b', 7998) + '2';
SET @Test = @Test + '3' + REPLICATE('c', 7000) + '3';

SELECT @Test;

PRINT '=============================';
EXEC dbo.Display @Test;
PRINT '=============================';
----
SET @Test = '';
SET @Test = @Test + '1.1' + REPLICATE('a', 7000) + '1.1' + CHAR(13) + CHAR(10);
SET @Test = @Test + '1.2' + REPLICATE('a', 400) + '1.2' + CHAR(13) + CHAR(10);
SET @Test = @Test + '1.3' + REPLICATE('a', 4000) + '1.3' + CHAR(13) + CHAR(10);
SET @Test = @Test + '2' + REPLICATE('b', 1798) + '2' + CHAR(13) + CHAR(10);
SET @Test = @Test + '3' + REPLICATE('c', 7000) + '3';

SELECT @Test;

PRINT '=============================';
EXEC dbo.Display @Test;
PRINT '=============================';
Run Code Online (Sandbox Code Playgroud)

笔记:

  1. 可以使用内置CHARINDEX函数来查找每个下一个换行符,在这方面,它的功能几乎就像使用字符串拆分函数一样。这里需要的和在换行符上进行简单分割之间的区别在于,返回的任何超过 8000 个字符的元素仍然需要分割成不超过 8000 个字符的块。
  2. 为了处理NVARCHAR(MAX)而不是VARCHAR(MAX),使用我发布到 PasteBin 的代码:T-SQL Stored Proc to PRINT NVARCHAR(MAX) 值
  3. Util_PrintSQL# 库中有一个存储过程 ,它可以处理NVARCHAR(MAX)上面显示的 T-SQL 代码并具有一些超出上述功能的功能,但它仅在完整(即付费)版本中可用,在免费版本中不可用。但此处显示的代码可以处理大多数情况。