如何在打印行时不使用t-sql游标?

时间:2010-09-07 20:52:36

标签: sql-server tsql

一位同事和我编写了这个存储过程,它记录了wiki标记中的数据库表,用于ScrewTurn wiki系统。最初,我没有光标就写了它,因为直到今天我才知道如何使用它!

我从基本上是你在下面看到的内容的组合开始。我会为每一行选择一列,其中该列是该行的整个wikimarkup。这完美地运作,但我想在结果之前和之后打印文本。我通过使用一些工会来攻击它。我会将标题与结果集合并,然后将所有这些与页脚结合起来。但是,我必须在每一行之间插入一行文本,这就是我不知道没有使用光标的部分。简而言之:

如何在每个结果行之前选择一堆带有硬编码行的记录?

在我的情况下,每行需要以|-行开头。

set ansi_nulls on
go
set quoted_identifier on
go

alter procedure DocTable
    @TableName varchar(256)
as
begin
    set nocount on;

    declare @WikiDocData table
    (
        Name nvarchar(256),
        [Type] nvarchar(256),
        Nullable nvarchar(256),
        [Default] nvarchar(256),
        [Identity] nvarchar(256),
        [Description] nvarchar(max)
    )

    insert into @WikiDocData
        select
            c.name as Name,
            tp.name + 
                ' (' + 
                (case when c.max_length = -1 then 'MAX' else convert(nvarchar(256),c.max_length) end) +
                ', ' +
                convert(nvarchar(256), c.scale) +
                ', ' +
                convert(nvarchar(256), c.[precision]) + ')'
                as [Type (L,S,P)],
            (case when c.is_nullable = 1 then 'Yes' else '' end) as Nullable,
            isnull(d.[definition], '') as [Default],
            (case when c.is_identity = 1 then 'Yes' else '' end) as [Identity],
            convert(nvarchar(max),isnull(p.value, '')) as [Description]
        from
            sys.tables t 
            inner join sys.columns c on t.object_id = c.object_id
            left join sys.extended_properties p on c.object_id = p.major_id and c.column_id = p.minor_id
            inner join sys.types tp on c.system_type_id = tp.system_type_id
            left join sys.default_constraints d on c.default_object_id = d.object_id and c.column_id = d.parent_column_id
        where
            t.[name] = @TableName 
            and tp.name <> 'sysname'
        order by
            t.object_id,
            c.column_id

    /* Dear reader, if you know how to do this without a cursor, please let me know! */

    -- Output header
    print '{| cellpadding="4" cellspacing="0" border="1"'
    print '! Name !! Type (L,S,P) !! Nullable !! Default !! Identity !! Description'

    -- Output each row and row separator
    declare @WikiRow nvarchar(max)
    declare @GetWikiRow cursor

    set @GetWikiRow = cursor for
        select
            '| ' +
            Name + ' || ' +
            [Type] + ' || ' +
            Nullable + ' || ' +
            [Default] + ' || ' +
            [Identity] + ' || ' +
            [Description]
        from
            @WikiDocData

    open @GetWikiRow fetch next from @GetWikiRow into @WikiRow while @@fetch_status = 0
    begin
        print '|-'
        print @WikiRow
        fetch next from @GetWikiRow into @WikiRow
    end
    close @GetWikiRow
    deallocate @GetWikiRow

    -- Output footer
    print '|}'

end
go

目前正在运作。在aspnet_Membership上运行时,它只打印出以下内容:

{| cellpadding="4" cellspacing="0" border="1"
! Name !! Type (L,S,P) !! Nullable !! Default !! Identity !! Description
|-
| ApplicationId || uniqueidentifier (16, 0, 0) ||  ||  ||  || 
|-
| UserId || uniqueidentifier (16, 0, 0) ||  ||  ||  || 
|-
| Password || nvarchar (256, 0, 0) ||  ||  ||  || 
|-
| PasswordFormat || int (4, 0, 10) ||  || ((0)) ||  || 
|-
| PasswordSalt || nvarchar (256, 0, 0) ||  ||  ||  || 
|-
| MobilePIN || nvarchar (32, 0, 0) || Yes ||  ||  || 
|-
| Email || nvarchar (512, 0, 0) || Yes ||  ||  || 
|-
| LoweredEmail || nvarchar (512, 0, 0) || Yes ||  ||  || 
|-
| PasswordQuestion || nvarchar (512, 0, 0) || Yes ||  ||  || 
|-
| PasswordAnswer || nvarchar (256, 0, 0) || Yes ||  ||  || 
|-
| IsApproved || bit (1, 0, 1) ||  ||  ||  || 
|-
| IsLockedOut || bit (1, 0, 1) ||  ||  ||  || 
|-
| CreateDate || datetime (8, 3, 23) ||  ||  ||  || 
|-
| LastLoginDate || datetime (8, 3, 23) ||  ||  ||  || 
|-
| LastPasswordChangedDate || datetime (8, 3, 23) ||  ||  ||  || 
|-
| LastLockoutDate || datetime (8, 3, 23) ||  ||  ||  || 
|-
| FailedPasswordAttemptCount || int (4, 0, 10) ||  ||  ||  || 
|-
| FailedPasswordAttemptWindowStart || datetime (8, 3, 23) ||  ||  ||  || 
|-
| FailedPasswordAnswerAttemptCount || int (4, 0, 10) ||  ||  ||  || 
|-
| FailedPasswordAnswerAttemptWindowStart || datetime (8, 3, 23) ||  ||  ||  || 
|-
| Comment || ntext (3000, 0, 0) || Yes ||  ||  || 
|}

LittleBobbyTables回答的新代码(它更短但涉及大量字符串连接,并且当标记中有超过8000个字符时无法打印):

set ansi_nulls on
go
set quoted_identifier on
go

alter procedure DocTable
    @TableName varchar(256)
as
begin
    set nocount on;

    -- Output header 
    print '{| cellpadding="4" cellspacing="0" border="1"' 

    -- Output each row and row separator 
    declare @WikiRow nvarchar(max) 
    set @WikiRow = '! Name !! Type (L,S,P) !! Nullable !! Default !! Identity !! Description'

    select
        @WikiRow = @WikiRow + 
        char(10) + '|- ' + char(10) + '| ' +
        c.name + ' || ' + 
        tp.name + 
            ' (' + 
            (case when c.max_length = -1 then 'MAX' else convert(nvarchar(256),c.max_length) end) +
            ', ' +
            convert(nvarchar(256), c.scale) +
            ', ' +
            convert(nvarchar(256), c.[precision]) + ')' + ' || ' + 
        (case when c.is_nullable = 1 then 'Yes' else '' end) + ' || ' + 
        isnull(d.[definition], '') + ' || ' + 
        (case when c.is_identity = 1 then 'Yes' else '' end) + ' || ' + 
        convert(nvarchar(max),isnull(p.value, ''))
    from
        sys.tables t 
        inner join sys.columns c on t.object_id = c.object_id
        left join sys.extended_properties p on c.object_id = p.major_id and c.column_id = p.minor_id
        inner join sys.types tp on c.system_type_id = tp.system_type_id
        left join sys.default_constraints d on c.default_object_id = d.object_id and c.column_id = d.parent_column_id
    where
        t.[name] = @TableName 
        and tp.name <> 'sysname'
    order by
        t.object_id,
        c.column_id

    print @WikiRow     

    -- Output footer 
    print '|}' 

end
go

4 个答案:

答案 0 :(得分:4)

这是打印长varchar(max)变量的例程(它要求CRLF之间的距离不要大于PRINT的最大阈值才能工作,因为它基本上接受字符串并将其移动到“行”中的缓冲区中,然后当缓冲区超过4000个字符时打印缓冲区):

CREATE PROCEDURE [usp_PrintLongSQL]
    @sql varchar(max)
AS
BEGIN
    DECLARE @CRLF AS varchar(2)
    SET @CRLF = CHAR(13) + CHAR(10)

    DECLARE @input AS varchar(max)
    SET @input = @sql

    DECLARE @output AS varchar(max)
    SET @output = ''

    WHILE (@input <> '')
    BEGIN
        DECLARE @line AS varchar(max)
        IF CHARINDEX(@CRLF, @input) > 0
            SET @line = LEFT(@input, CHARINDEX(@CRLF, @input) - 1) + @CRLF
        ELSE
            SET @line = @input

        IF LEN(@input) - LEN(@line) > 0
            SET @input = RIGHT(@input, LEN(@input) - LEN(@line))
        ELSE
            SET @input = ''

        SET @output = @output + @line
        IF LEN(@output) > 4000
        BEGIN
            PRINT @output
            SET @output = ''
        END
    END

    IF @output <> ''
        PRINT @output
END

我个人更喜欢使用它,因为它使许多其他代码更简单,更通用而没有游标(例如,可以进入视图或内联表值函数的代码更可重用)。

答案 1 :(得分:2)

更新:Per Cade Roux和Chris,打印超过8000个字符时不起作用。我将此作为警告。

您可以使用变量重复添加行。试试这个:

-- Output header 
print '{| cellpadding="4" cellspacing="0" border="1"' 
print '! Name !! Type (L,S,P) !! Nullable !! Default !! Identity !! Description' 

-- Output each row and row separator 
declare @WikiRow nvarchar(max) 
set @WikiRow = ''

select @WikiRow = @WikiRow + 
        '|- ' + char(10) + '| ' +
        Name + ' || ' + 
        [Type] + ' || ' + 
        Nullable + ' || ' + 
        [Default] + ' || ' + 
        [Identity] + ' || ' + 
        [Description] + char(10) 
    from 
        @WikiDocData 

print left(@WikiRow, len(@WikiRow) - 1)

-- Output footer 
print '|}' 

答案 2 :(得分:1)

可以使用SQL Server 2012或更高版本中的Offset Fetch子句来完成此操作。

使用AdventureWorks Production.Products表...

DECLARE @Output varchar(8000) = '';

-- 'Print' function only prints 8000 non-unicode chars max.  Let's print 10 at a time.  Use Fetch Next with Offset.  (Sql Svr 2012+)
DECLARE @rowNum int = 0;
DECLARE @numRows int;
SELECT @numRows = count(ProductID) from Production.Products;

WHILE @rowNum < @numRows
BEGIN

    SELECT @Output = @Output + '
    IF (@someVariable = ''' + ProductNumber + ''')      BEGIN;      RETURN ''' + ProductName + ''';     END;'
        FROM    Production.Products
        ORDER BY ProductID
        OFFSET @rowNum ROWS FETCH NEXT 10 ROWS ONLY;        -- 10 rows at a time so can print without fear of truncation.

    PRINT @Output;
    SET @Output = '';               -- reset for next set of rows
    SET @rowNum = @rowNum + 10;
END

答案 3 :(得分:-1)

  

如何在每行之前选择一堆带有硬编码字符串的记录?

select '|-I am a hardcoded string with a newline following' 
        + char(10) + a.foo as foo
from bar a;

也就是说,只需将硬编码字符串连接到您已选择的列上即可。用新行字符(char(10))分隔它们,或者对于DOS / Windows,使用回车换行符(char(13)+ char(10))。

编辑:感谢所有指出catenation运算符为“+”的人,而不是“||”在T-SQL中。