0

How to use the declared variable @CodeID inside the SQL string? When I run following statement I get the "Invalid object name (..)" error.

WHILE @FolderID <= @FolderMaxID
BEGIN
    SELECT @Db  = Db 
    FROM #Folders 
    WHERE ID =  @FolderID

    SET @Sql = N'

        DECLARE @CodeID NVARCHAR(256)

        SELECT TOP(1)  @CodeID=CodeType
        FROM ' + @Db + '.bla.Field 
        WHERE Name= ''Example''

        SELECT DISTINCT C.Name
        FROM ' + @Db + '.Document 
            INNER JOIN ' + @Db + '.bla.Code_@CodeID  C  ON D.ID = C.ID'
        
    EXEC ( @Sql )
    SET @FolderID = @FolderID + 1
END
11
  • 4
    This is why it's advised to not use syntax such as EXEC (@SQL);. Such statements cannot be parametrised, which promote bad habits that result in security flaws like SQL injection. If you need to run a statement that is within a variable or literal string then use sys.sp_executesql. Then you can easily parametrise the statement if you need to. Commented Feb 3, 2022 at 15:16
  • 1
    Also don't blindly inject your object names, delimit identify them properly with QUOTENAME. Commented Feb 3, 2022 at 15:17
  • 2
    Finally, why are you spamming the NOLOCK hint all over your queries here? Why are you using such a hint? NOLOCK isn't a "magic go faster button", it's a don't care about wrong data button. I suggest having a read of Bad habits : Putting NOLOCK everywhere. Commented Feb 3, 2022 at 15:17
  • 2
    You don't. What are you trying to do in the first place? Commented Feb 3, 2022 at 15:24
  • 2
    @jonas as mentioned, you are likely far better off explaining what you are actually trying to achieve here. Why do you need a dynamic schema reference in the first place? Why do you have multiple tables with the same definition (presumably) on different schemas? Why is the data not all in one table, with a column used to denote the information you are using the schema to represent? The fact you want to do an operation against all these tables suggests that they are related. What do you want to do with @CodeID? You assign its value and then do nothing with it, making it pointless anyway. Commented Feb 3, 2022 at 15:33

1 Answer 1

1

It looks to me that you need two levels of dynamic SQL, with the first level inserting the database name (from #folders), and the second level inserting a constructed table name (based on the CodeType column of the database-local bla.Field table).

I do not know of any way to parameterize database names or table names using sp_executesql, so I'm sticking with build-up dynamic SQL and EXEC (). (If someone makes a case for preferring sp_executesql over EXEC when not useing parameters, then it may be worth the switch.)

Try something like:

WHILE @FolderID <= @FolderMaxID
BEGIN
    SELECT @Db  = Db 
    FROM #Folders 
    WHERE ID =  @FolderID

    SET @Sql = N'

        DECLARE @CodeID NVARCHAR(256)

        SELECT TOP(1)  @CodeID=CodeType
        FROM ' + QUOTENAME(@Db) + '.bla.Field 
        WHERE Name= ''Example''

        DECLARE @Sql2 NVARCHAR(MAX) = N''
            SELECT DISTINCT C.Name
            FROM ' + QUOTENAME(@Db) + '.bla.Document D
                INNER JOIN ' + QUOTENAME(@Db) + '.bla.'' + QUOTENAME(''Code_'' + @CodeID) + '' C  ON D.ID = C.ID
        ''
        EXEC @sql2
        '
    
    EXEC ( @Sql )
    SET @FolderID = @FolderID + 1
END

This implements dynamic SQL within dynamic SQL. Doubled quotes in the outer sql template become single quotes in the inner sql. The original posted code seemed to be missing a schema qualifier and alias for the Document table, so I inserted them ("bla" and "D"). I also added QUOTENAME around the injected names as suggested by Larnu.

The first level of dynamic sql would generate something like:

SELECT TOP(1)  @CodeID=CodeType
FROM [db1].bla.Field 
WHERE Name= 'Example'

DECLARE @Sql2 NVARCHAR(MAX) = N'
    SELECT DISTINCT C.Name
    FROM [db1].bla.Document D
        INNER JOIN [db1].bla.' + QUOTENAME('Code_' + @CodeID) + ' C  ON D.ID = C.ID
'
EXEC @sql2

The second level would generate something like:

SELECT DISTINCT C.Name
FROM [db1].bla.Document D
    INNER JOIN [db1].bla.[Code_Table1] C  ON D.ID = C.ID

Note that each loop iteration will generate a separate result. If you wish to combine results, you will need to define a #temp table, insert the individual results into that table, and then select the combined results at the end of your script.

Note that I haven't tested the specific code above, so it might need some debugging (add "PRINT @sql2" before the EXEC) if it doesn't work straight out.

ADDENDUM

Per @trenton-ftw comments below, an out parameter can be used to capture the result of the first query so that it may be included in the second query without the need for nesting. Two executions are still required. Below is a revised example.


DECLARE @Folders TABLE (ID INT IDENTITY(1,1), Db sysname)
INSERT @Folders VALUES ('db1'), ('db2')

DECLARE @SearchName NVARCHAR(256) = 'Example' 
DECLARE @Db sysname
DECLARE @Sql NVARCHAR(MAX)
DECLARE @CodeID NVARCHAR(256)

DECLARE @FolderMaxID INT = (SELECT MAX(ID) FROM @Folders)
DECLARE @FolderID INT = 1
WHILE @FolderID <= @FolderMaxID
BEGIN
    SELECT @Db = Db 
    FROM @Folders 
    WHERE ID = @FolderID

    SET @Sql = N'
        SET @CodeID = @SearchName + ''-Test''
        --SELECT TOP(1) @CodeID = CodeType
        --FROM ' + QUOTENAME(@Db) + '.bla.Field 
        --WHERE Name = @SearchName'
    PRINT @Sql
    EXEC sp_executesql @Sql,
        N'@SearchName NVARCHAR(256), @CodeID NVARCHAR(256) OUTPUT',
        @SearchName, @CodeID OUTPUT

    SET @Sql = N'
        --SELECT DISTINCT C.Name
        --FROM ' + QUOTENAME(@Db) + '.bla.Document D
        --    INNER JOIN ' + QUOTENAME(@Db) + '.bla.' + QUOTENAME('Code_' + @CodeID) + ' C  ON D.ID = C.ID'
    PRINT @Sql
    EXEC sp_executesql @sql

    SET @FolderID = @FolderID + 1
END

For demo purposes, I also parameterized the search name as an input parameter and added some temporary code to make it stand-alone testable. A final version would uncomment the actual sql, and remove the print statements and the test @CodeID assignemnt.

Sign up to request clarification or add additional context in comments.

3 Comments

Follow-up. If you intend to parameterize the 'Example' value, we would then have a case for using sp_executesql, at least for the outer case.
Beyond the other issues with actually using this code, If someone makes a case for preferring sp_executesql over EXEC when not useing parameters, then it may be worth the switch. How about instead of having to nest dynamic SQL to retrieve the value of @CodeID variable for a second query, you just use OUTPUT parameters in sys.sp_executesql to retrieve the value from your first query and you can then un-nest your dynamic SQL and just run the next statement inline with the rest of your code. Way easier than nesting dynamic SQL.
@trenton-ftw - Excellent observation. I've posted an addendum above. Feel free to suggest other improvements.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.