代码之家  ›  专栏  ›  技术社区  ›  Christopher

当需要打印行时,如何不使用T-SQL光标?

  •  4
  • Christopher  · 技术社区  · 15 年前

    我和一个同事写了这个存储过程,用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_成员身份上运行时,它只打印以下内容:

    {| 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 回复  |  直到 15 年前
        1
  •  2
  •   LittleBobbyTables    15 年前

    更新:根据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
  •  4
  •   Cade Roux    15 年前

    这里有一个打印长varchar(max)变量的例程(它不需要crlf之间的距离大于打印工作的最大阈值,因为它基本上取字符串并将其以“行”的形式移动到缓冲区中,然后在超过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
    

    我个人更喜欢使用它,因为它使许多其他代码变得更简单,并且在没有光标的情况下更加通用(例如,可以进入视图或内联表值函数的代码更易于重用)。

        3
  •  1
  •   cplommer    10 年前

    这可以使用SQL Server 2012或更高版本中的offset fetch子句来完成。

    使用AdventureWorks产品表…

    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
    
        4
  •  -1
  •   tpdi    15 年前

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

    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中为“+”,而不是“”。