Переменная конкатенация в T- SQL неожиданный результат - PullRequest
1 голос
/ 16 марта 2020

Это минимальный пример кода, используемого в контексте генерации динамических c SQL, следовательно, замен. Ожидаемый результат состоит в том, что все данные представлены в результате и в одной текстовой переменной (объединение нескольких строк).

Очевидное решение этого примера - использовать найденные нами «хаки», но это код используется в существующем коде в нескольких местах (в производственном коде), так что это НЕОБХОДИМОЕ решение, чтобы решить путем «исправления» кода, в противном случае у нас было бы решение, см. взломы / исправления. Надежда состоит в том, что есть настройка на уровне сервера, которая помогает этой проблеме или другим, у которых были подобные проблемы с их результатами и результатами.

DROP TABLE IF EXISTS #Table1,#Table2,#Table3
GO
SELECT ManagerID,EmployeeID
INTO #Table1
FROM (VALUES(1,1),(1,2)) A(ManagerID,EmployeeID)

SELECT ManagerID,[Name]
INTO #Table2
FROM (VALUES(1,'EvilDoer' )) A(ManagerID,[Name])

SELECT EmployeeID,[Name]
INTO #Table3
FROM (VALUES(1,'SlaveA' ),(2,'SlaveB' )) A(EmployeeID,[Name])

/*
Tested on SQL Server 14.0.3257.3 on both compatibility level 110 and 140;
The issue here is, likely?; the execution plan. 
If you have the two replaces then the plan is "Incorrect", i.e. ComputeScalar is before sort => 1 row (Last)
If you remove the one or two of the replaces then the plan is "Correct", i.e. ComputeScalar is after sort => All rows
Alternative 1, usage of TOP(X) yields a "Correct" result
Alternative 2, Removal of the "ORDER BY"-clause yields a "Correct" result
*/
DECLARE @SQL varchar(max) = ''
SELECT --TOP 1000000
@SQL = @SQL + '
EXECUTE sp_MyProc 
     @Manager = '''+REPLACE(T2.[Name],CHAR(39),CHAR(39) + CHAR(39))+'''
    ,@Worker = '''+REPLACE(T3.[Name],CHAR(39),CHAR(39) + CHAR(39))+'''

'
FROM #Table1 T1
INNER JOIN #Table2 T2
    ON T1.ManagerID = T2.ManagerID
INNER JOIN #Table3 T3
    ON T1.EmployeeID = T3.EmployeeID
ORDER BY  T2.[Name], T3.[Name] 

SELECT @SQL /* EXECUTE (@SQL)*/

Неверный результат (пропущенные данные):

EXECUTE sp_MyProc 
     @Manager = 'EvilDoer'
    ,@Worker = 'SlaveB'

План выполнения с неверным результатом:

enter image description here

Ожидаемый результат (все данные):

EXECUTE sp_MyProc 
     @Manager = 'EvilDoer'
    ,@Worker = 'SlaveA'


EXECUTE sp_MyProc 
     @Manager = 'EvilDoer'
    ,@Worker = 'SlaveB'

План выполнения с TOP (N):

enter image description here

1 Ответ

0 голосов
/ 16 марта 2020

Я бы лично предложил использовать FOR XML PATH (STRING_AGG, если вы находитесь на 2017+) и правильно указать здесь введенные значения (я предполагаю, что [Name] не может быть длиннее 128 символов):

DECLARE @SQL nvarchar(MAX),
        @CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SET @SQL = STUFF((SELECT @CRLF + N'EXECUTE sp_MyProc @Manager = ' + QUOTENAME(T2.[Name], '''') + N', @Worker = ' + QUOTENAME(T3.[Name], '''') + N';'
                  FROM #Table1 T1
                       INNER JOIN #Table2 T2 ON T1.ManagerID = T2.ManagerID
                       INNER JOIN #Table3 T3 ON T1.EmployeeID = T3.EmployeeID
                  ORDER BY T2.[Name],
                           T3.[Name]
                 FOR XML PATH(N''), TYPE).value('.', 'nvarchar(MAX)'),1,2,N'');

--PRINT @SQL;
EXEC sys.sp_executesql @SQL;

Также обратите внимание, что префикс sp_ зарезервирован корпорацией Майкрософт для обозначения «Специальная процедура». Я надеюсь, что ваши настоящие хранимые процедуры не имеют префикса sp_. Если они это сделают, я рекомендую переименовать их; использование sp_ платное. Префикс sp_ по-прежнему запрещен?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...