【问题标题】:T-SQL query gone wrong exec not in tableT-SQL 查询出错 exec 不在表中
【发布时间】:2017-03-07 20:14:30
【问题描述】:

我正在尝试构建一个查询,将他的输出放在一个表中。 exec(@inloop_query) 不知道之前声明的表。 (------------------之间的那部分 这是可能的还是我尝试做一些不起作用的事情? 请指教。

(我遇到的错误是:必须声明表变量“@inloop_table”。严重性 15 状态 2)

DECLARE @frame_db_name VARCHAR(max)
DECLARE @frame_db_id INT
DECLARE @frame_table TABLE (
    db_id INT , 
    names VARCHAR(max))
DECLARE @frame_count INT
DECLARE @frame_count_max INT
SET @frame_count = 1
SET @frame_count_max = 0
SELECT @frame_count_max = count (name) FROM sys.databases WHERE Name LIKE     'B%' and state_desc = 'online' 
INSERT INTO @frame_table SELECT database_id , name FROM sys.databases     WHERE Name LIKE 'B%' and state_desc = 'online' ORDER BY database_id

DECLARE @inloop_query VARCHAR(max)
DECLARE @Inloop_table TABLE (
    IL_SchemaName VARCHAR(max) , 
    IL_TableName VARCHAR(max) , 
    IL_IndexName VARCHAR(max) , 
    IL_IndexID INT , 
    IL_Fragment INT) 

IF @frame_count_max  <= 0  
    PRINT '@count_max (<=0) = ' + CAST(@frame_count_max AS VARCHAR)
ELSE 
    WHILE @frame_count <= @frame_count_max
        BEGIN
            SELECT @frame_db_name = names , @frame_db_id = db_id FROM     @frame_table WHERE db_id IN (SELECT TOP 1 db_id FROM @frame_table ORDER BY db_id) 
        PRINT '@count_max (>=0) = ' + CAST(@frame_count_max AS VARCHAR)
        PRINT '@count = ' + CAST(@frame_count AS VARCHAR(max)) 
        PRINT 'current DB name = ' + CAST(@frame_db_name AS VARCHAR(max))
        PRINT 'current DB ID = ' + CAST(@frame_db_id AS VARCHAR(max))
        ------------------------------------------------------------ 
        SET @inloop_query = '
            USE ' + CAST(@frame_db_name AS VARCHAR(max)) + 
            ' INSERT INTO @inloop_table 
                SELECT SCHEMA_NAME(o.schema_id)     AS SchemaName,
                OBJECT_NAME(a.object_id)     AS TableName,
                i.name                       AS IndexName,
                a.index_id                   AS IndexID,
                convert(tinyint,a.avg_fragmentation_in_percent) AS [Fragment]
            FROM   sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL,NULL, ''LIMITED'') AS a
                INNER JOIN sys.indexes i ON i.index_id = a.index_id
                    AND i.object_id = a.object_id 
                INNER JOIN sys.objects o ON a.object_id = o.object_id
            ORDER BY SchemaName, TableName, IndexID'
        EXEC(@inloop_query)
        ------------------------------------------------------------ 
        SET @frame_count = @frame_count + 1
        DELETE FROM @frame_table WHERE db_id IN (SELECT TOP 1 db_id FROM @frame_table ORDER BY db_id) 
    END

【问题讨论】:

    标签: sql exec declare


    【解决方案1】:

    @inloop_table 在 @inloop_query 之外声明;当后者被执行时,它不知道这个变量。使用实际的表格怎么样?

    /* comment this out:
    DECLARE @inloop_query VARCHAR(max)
    DECLARE @Inloop_table TABLE (
        IL_SchemaName VARCHAR(max) , 
        IL_TableName VARCHAR(max) , 
        IL_IndexName VARCHAR(max) , 
        IL_IndexID INT , 
        IL_Fragment INT)
    */
    -- Create an auxiliary table
    CREATE TABLE InLoop_Table (
            IL_SchemaName VARCHAR(max) , 
        IL_TableName VARCHAR(max) , 
        IL_IndexName VARCHAR(max) , 
        IL_IndexID INT , 
        IL_Fragment INT
    );
    -- ... And use this table in your dynamic sql:
    SET @inloop_query = '
            USE ' + CAST(@frame_db_name AS VARCHAR(max)) + 
            ' INSERT INTO InLoop_Table ...
    
    -- Finally, clean up:
    DROP TABLE InLoop_Table;
    

    【讨论】:

    • 感谢您的回复。我也想到了这一点,但问题是我无法在我正在处理的数据库上创建任何表。我也尝试过(并且确实有效)在@inloop_query 中创建表,但我需要在另一个时间输出。我真的希望有办法让它发挥作用。
    • 那么 GarethD 的解决方案怎么样?
    【解决方案2】:

    表变量的范围是特定于批处理的,因此由于您的动态 sql 作为新批处理执行,因此它超出了范围并且无法识别。您当然可以在动态 sql 中声明它,但这毫无意义,因为您以后无法访问它。你有两个不错的选择:

    你可以把insert放到sql外面,例如

    DECLARE @inloop_query NVARCHAR(MAX) = 'USE Master; SELECT 1, 2, 3;';
    DECLARE @inloop_table TABLE (A INT, B INT, C INT);
    
    INSERT @inloop_table
    EXEC(@inloop_query);
    
    SELECT * FROM @inloop_table;
    

    或者您可以使用临时表,而不是表变量。临时表具有会话范围,因此仍可通过EXEC() 识别:

    CREATE TABLE #inloop_table (A INT, B INT, C INT);
    DECLARE @inloop_query NVARCHAR(MAX) = 'USE Master; INSERT #inloop_table SELECT 1, 2, 3;';
    
    EXEC(@inloop_query);
    
    SELECT * FROM #inloop_table;
    

    我还建议使用正确声明的游标,而不是使用WHILE 循环遍历表变量。这里的关键方面是正确定义的。人们通常只使用DECLARE .. CURSOR FOR SELECT..,与告诉光标您不会进行更新、不会向后移动等相比,默认选项要慢得多且占用更多内存。

    DECLARE DBCursor CURSOR LOCAL STATIC FORWARD_ONLY READ_ONLY
    FOR
    SELECT  database_id , name 
    FROM sys.databases     
    WHERE Name LIKE 'B%' and state_desc = 'online' 
    ORDER BY database_id;
    
    OPEN DBCursor;
    FETCH NEXT FROM DBCursor INTO @frame_db_id, @frame_db_name;
    
    WHILE @@FETCH_STATUS = 0
    BEGIN
        -- DO WHATEVER YOU NEED WITH EACH DB
    
        FETCH NEXT FROM DBCursor INTO @frame_db_id, @frame_db_name;
    END
    
    CLOSE DBCursor;
    DEALLOCATE DBCursor;
    

    最后一条评论是,我总是更喜欢sp_executesql 而不是EXEC() 和this article pretty much covers why,在这种情况下并没有太大区别,但值得注意。

    【讨论】:

    • 实际上,如果我们使用sp_executesql,我们可以访问一个在动态sql中声明的变量,但它有点涉及:stackoverflow.com/a/42528011/1465748
    • @GiorgosAltanis 这不适用于表变量,只能输出标量变量。因此,您需要序列化结果,并将其作为标量变量返回,XML 将是显而易见的候选者,然后您需要将 XML 反序列化回表中以对结果执行任何操作。所以是的,这是可能的,但它需要付出太多的努力,以至于它可能永远不是比仅使用临时表更好的选择。
    • 感谢您对 table 变量的澄清,我同意这是一个困难的方法。
    • 谢谢你的回复,我早上试试。 (对我来说似乎很难让它工作)但我还是想学习它,所以所有的 cmets 和信息都很受欢迎:)
    猜你喜欢
    • 2023-03-11
    • 1970-01-01
    • 2010-09-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-22
    • 1970-01-01
    相关资源
    最近更新 更多