-1

I'm trying to build a query which puts his output in a table. The exec(@inloop_query) doesn't know a declared table from before. (that part between the ------------------ Is this possible or do I try to do something that doesn't work? please advise.

(The error I've is : Must declare the table variable "@inloop_table". Severity 15 State 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
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
hexedecimal
  • 279
  • 1
  • 2
  • 9

2 Answers2

0

@inloop_table is declared outside your @inloop_query; when the latter is executed, it has no idea about this variable. How about using an actual table?

/* 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;
Giorgos Altanis
  • 2,742
  • 1
  • 13
  • 14
  • Thanks for your repley . I thought of that also but the problem is that I can not make any tables on the DB I'm working on. Also I've tried (and it did work) to make the TABLE inside @inloop_query but I need the output in another time. I realy hopes there is a way to make this to work. – hexedecimal Mar 06 '17 at 15:59
  • How about GarethD's solution then? – Giorgos Altanis Mar 06 '17 at 16:00
0

The scope of a table variable is specific to a batch, so since your dynamic sql executes as a new batch it falls out of scope and is unrecognised. You could of course declare it within your dynamic sql, but that would be pretty pointless as you couldn't access it later. You have two decent choices:

You can put the insert outside of the sql, e.g.

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;

Or you can use a temporary table, rather than a table variable. A temporary table has session scope, so is still recognised with 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;

I would also recommend using a properly declared cursor rather than a WHILE loop iterating through a table variable. The key aspect here being properly defined. Often people just use DECLARE .. CURSOR FOR SELECT.. and the default options are much slower and more memory consuming than if you tell the cursor that you won't be making updates, won't be moving backwards etc.

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;

One final comment, is that I always perfer sp_executesql over EXEC() and this article pretty much covers why, in this instance it doesn't make much difference, but it is worth noting.

Community
  • 1
  • 1
GarethD
  • 68,045
  • 10
  • 83
  • 123
  • Actually we **could access** a variable which is declared inside the dynamic sql if we used `sp_executesql` , but it is somewhat involved: http://stackoverflow.com/a/42528011/1465748 – Giorgos Altanis Mar 06 '17 at 16:10
  • @GiorgosAltanis That does not apply to table variables, you can only output scalar variables. So you would need to serialise the result, and return it as a scalar variable, XML would be the obvious candidate, then you would need to deserialise your XML back into a table to do anything with the results. So yes it is possible, but it is so much effort that it is probably never a better option than just using a temporary table. – GarethD Mar 06 '17 at 16:15
  • Thanks for the clarification about the table variable, I do agree that it a difficult approach. – Giorgos Altanis Mar 06 '17 at 16:17
  • Thank you for your reply's, I'll try this in the morning. (seems dufficult to me to make it work) But I want to learn it anyway so all the comments and info are welkom :) – hexedecimal Mar 06 '17 at 16:24