3

I am creating a varchar(max) string for submission to 'EXEC' and it is less than 1000 bytes long, yet the 'exec' errors out - seems to not get the entire string.

Below is the code that errors out:

create or alter procedure usp_utility_ListFullTextCatalog_items @Database varchar(100)

as

declare @sql varchar(max), @DBName varchar(max)

set @DBName = @Database

set @sql =  cast('' as varchar(max))
             + 'SELECT  t.name as TableName, 
                    c.name as FTCatalogName,
                    i.name as UniqueIdxName,
                    cl.name as ColumnName,
                    cdt.name as DataTypeColumnName '
             + 'from    ' + @DBName + '.sys.tables t inner join '
             + '        ' + @DBName + '.sys.fulltext_indexes fi on '
             + '        ' + 't.[object_id] = fi.[object_id] inner join '
             + '        ' + @DBName + '.sys.fulltext_index_columns ic on '
             + '        ic.[object_id] = t.[object_id] inner join '
             + '        ' + @DBName + '.sys.columns cl on '
             + '            ic.column_id = cl.column_id and '
             + '            ic.[object_id] = cl.[object_id] inner join '
             + '        ' + @DBName + '.sys.fulltext_catalogs c on '
             + '        fi.fulltext_catalog_id = c.fulltext_catalog_id inner join '
             + '        ' + @DBName + '.sys.indexes i on '
             + '            fi.unique_index_id = i.index_id and '
             + '            fi.[object_id] = i.[object_id] left join ' 
             + '        ' + @DBName + '.sys.columns cdt on '
             + '        ic.type_column_id = cdt.column_id and '
             + '        fi.object_id = cdt.object_id'
select len(@sql)
select right(@sql,50)
execute @sql

go

execute usp_utility_ListFullTextCatalog_items 'AdventureWorks2022'

When I run the above, the first 'select' at the bottom of the procedure shows the length of @sql to be 900.

The 'execute @sql' statement at the end of the stored procedure produces the below error:

Msg 203, Level 16, State 2, Procedure usp_utility_ListFullTextCatalog_items, Line 33 [Batch Start Line 34]
The name 'SELECT    t.name as TableName, 
                    c.name as FTCatalogName,
                    i.name as UniqueIdxName,
                    cl.name as ColumnName,
                    cdt.name as DataTypeColumnName from AdventureWorks2022.sys.tables t           inner join        AdventureWorks2022.sys.fulltext_indexes fi on       t.[object_id] = fi.[object_id] inner join         AdventureWorks2022.sys.fulltext_index_columns ic on       ic.[object_id] = t.[object_id] inner join         AdventureWorks2022.sys.columns cl on          ic.column_id = cl.column_id and             ic.[object_id] = cl.[object_id] inner join         AdventureWorks2022.sys.fulltext_catalogs c on        fi.fulltext_catalog_id = c.fulltext_catalog_id inne' **_is not a valid identifier._**

The error message does not show the entire value of @sql, yet the second 'select' at the bottom of the procedure shows the last 50 bytes of @sql to be "= cdt.column_id and fi.object_id = cdt.object_id" - which is what they are supposed to be.

4
  • 1
    Well for a start when executing a string you need brackets e.g. execute (@sql); Think thats your complete issue tbh dbfiddle.uk/n3NChKxD Commented Nov 24 at 4:00
  • 1
    This is all it needed. The error message called my string a 'name' which is a clue I ignored. Commented Nov 24 at 18:59
  • The suggestions on the other answer should all be taken into account too. Commented Nov 24 at 19:05
  • 1
    If I were you I would accept that answer given ifs far more comprehensive. Commented Nov 24 at 21:12

2 Answers 2

5

There are a number of issues with your code:

  • Firstly as mentioned by others: use either EXEC(@sql); or EXEC sp_executesql @sql;. The latter is better as you can also pass parameters.
  • Dynamic SQL should be in nvarchar(max) not varchar(max).
  • Database/object names should be stored in sysname which is an alias for nvarchar(128).
  • @DBName should be escaped using QUOTENAME.
  • You don't need all those + signs, just use a multi-line string.
  • object_id does not need escaping.
create or alter procedure usp_utility_ListFullTextCatalog_items
    @Database sysname
as

declare @sql nvarchar(max), @DBName nvarchar(max);

set @DBName = QUOTENAME(@Database);

set @sql = cast(N'' as nvarchar(max)) + '
SELECT
  t.name as TableName, 
  c.name as FTCatalogName,
  i.name as UniqueIdxName,
  cl.name as ColumnName,
  cdt.name as DataTypeColumnName
from ' + @DBName + '.sys.tables t
inner join ' + @DBName + '.sys.fulltext_indexes fi
    on t.object_id = fi.object_id
inner join ' + @DBName + '.sys.fulltext_index_columns ic
    on ic.object_id = t.object_id
inner join ' + @DBName + '.sys.columns cl
    on ic.column_id = cl.column_id
    and ic.object_id = cl.object_id
inner join ' + @DBName + '.sys.fulltext_catalogs c
    on fi.fulltext_catalog_id = c.fulltext_catalog_id
inner join ' + @DBName + '.sys.indexes i
    on fi.unique_index_id = i.index_id
    and fi.object_id = i.object_id
left join ' + @DBName + '.sys.columns cdt
    on ic.type_column_id = cdt.column_id
    and fi.object_id = cdt.object_id;
';

exec sp_executesql @sql;

However, a much better idea is to just use EXEC @proc @sql; where @proc is DatabaseName.sys.sp_executesql, which will execute the dynamic SQL in the context of that database. That simplifies the whole dynamic part as you don't need to use three-part naming.

create or alter procedure usp_utility_ListFullTextCatalog_items
    @Database sysname
as

declare @proc nvarchar(1000), @sql nvarchar(max);

set @proc = QUOTENAME(@Database) + '.sys.sp_executesql';

set @sql = N'
SELECT
  t.name as TableName, 
  c.name as FTCatalogName,
  i.name as UniqueIdxName,
  cl.name as ColumnName,
  cdt.name as DataTypeColumnName
from sys.tables t
inner join sys.fulltext_indexes fi
    on t.object_id = fi.object_id
inner join sys.fulltext_index_columns ic
    on ic.object_id = t.object_id
inner join sys.columns cl
    on ic.column_id = cl.column_id
    and ic.object_id = cl.object_id
inner join sys.fulltext_catalogs c
    on fi.fulltext_catalog_id = c.fulltext_catalog_id
inner join sys.indexes i
    on fi.unique_index_id = i.index_id
    and fi.object_id = i.object_id
left join sys.columns cdt
    on ic.type_column_id = cdt.column_id
    and fi.object_id = cdt.object_id;
';

exec @proc @sql;
Sign up to request clarification or add additional context in comments.

4 Comments

If final join is 'inner join' then I get one row output. If 'left outer join' I get 4 rows output - 3 of which have 'null' in DataTypeColumnName.
Ah sorry missed it was joining on ic.type_column_id not ic.column_id because you write your join conditions backwards
Your code is much cleaner and simpler than mine. I was surprised to see that using DatabaseName.sys.sp_executesql meant that the system procedures executed (those in @sql) were those residing on DatabaseName even though the stored procedure was on my 'Utility' database.
After further research I'm no longer surprised... Thanks again.
3

You are calling EXECUTE @sql instead of EXEC (@sql).

2 Comments

Should be a duplicate
Or even EXECUTE (@sql); exec is just shorthand for execute.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.