sp_executesql з визначеним користувачем типом таблиці не веде себе правильно


11

Проблема : чи відома проблема із визначеними користувачем типами таблиць як параметрів sp_executesql ? Відповідь - ні, я ідіот.

Налаштування сценарію

Цей скрипт створює один із таблиць, процедур та визначених користувачем типів таблиці (обмежений лише для SQL Server 2008+).

  • Мета купи - перевірити, що так, дані внесли це в процедуру. Немає жодних обмежень і нічого, що заважає вставляти дані.

  • Процедура приймає за параметр тип таблиці, визначений користувачем. Все, що робиться, вставити в таблицю.

  • Користувальницький тип таблиці також є простою, лише один стовпець

Я запустив наступне проти, 11.0.1750.32 (X64) і 10.0.4064.0 (X64)так, я знаю, що поле може бути зафіксовано, я не контролюю це.

-- this table record that something happened
CREATE TABLE dbo.UDTT_holder
(
    ServerName varchar(200)
,   insert_time datetime default(current_timestamp)
)
GO

-- user defined table type transport mechanism
CREATE TYPE dbo.UDTT
AS TABLE
(
    ServerName varchar(200)
)
GO

-- stored procedure to reproduce issue
CREATE PROCEDURE dbo.Repro
(
    @MetricData dbo.UDTT READONLY
)
AS
BEGIN
    SET NOCOUNT ON

    INSERT INTO dbo.UDTT_holder 
    (ServerName)
    SELECT MD.* FROM @MetricData MD
END
GO

Проблемне відтворення

Цей скрипт демонструє проблему, і на його виконання знадобиться п'ять секунд. Я створюю два екземпляри моїх визначених користувачем типів таблиць, а потім пробую два різні засоби передачі їх у sp_executesql. Зображення параметрів у першій виклику імітує те, що я захопив у SQL Profiler. Потім я називаю процедуру без sp_executesqlобгортки.

SET NOCOUNT ON
DECLARE 
    @p3 dbo.UDTT
,   @MetricData dbo.UDTT
INSERT INTO @p3 VALUES(N'SQLB\SQLB')
INSERT INTO @MetricData VALUES(N'SQLC\SQLC')

-- nothing up my sleeve
SELECT * FROM dbo.UDTT_holder

SELECT CONVERT(varchar(24), current_timestamp, 121) + ' Firing sp_executesql' AS commentary
-- This does nothing
EXECUTE sp_executesql N'dbo.Repro',N'@MetricData dbo.UDTT READONLY',@MetricData=@p3
-- makes no matter if we're mapping variables
EXECUTE sp_executesql N'dbo.Repro',N'@MetricData dbo.UDTT READONLY',@MetricData

-- Five second delay
waitfor delay '00:00:05'
SELECT CONVERT(varchar(24), current_timestamp, 121) + ' Firing proc' AS commentary
-- this does
EXECUTE dbo.Repro @p3

-- Should only see the latter timestamp
SELECT * FROM dbo.UDTT_holder

GO

Результати : нижче мої результати. Таблиця спочатку порожня. Я випромінюю поточний час і здійснюю два дзвінки на sp_executesql. Я чекаю 5 секунд, щоб пройти та випромінювати поточний час з подальшим викликом самої збереженої процедури і, нарешті, скинути таблицю аудиту.

Як видно з мітки часу, запис для запису B відповідає прямому виклику збереженої процедури. Також немає запису SQLC.

ServerName                                       insert_time
------------------------------------------------------------------------

commentary
-------------------------------------------------
2012-02-02 13:09:05.973 Firing sp_executesql

commentary
-------------------------------------------------
2012-02-02 13:09:10.983 Firing proc

ServerName                                       insert_time
------------------------------------------------------------------------
SQLB\SQLB                                        2012-02-02 13:09:10.983

Розрушити сценарій

Цей скрипт видаляє об'єкти у правильному порядку (ти не можеш скинути тип до видалення посилання процедури)

-- cleanup
DROP TABLE dbo.UDTT_holder
DROP PROCEDURE dbo.Repro
DROP TYPE dbo.UDTT
GO

Чому це має значення

Код здається нерозумним, але я не маю особливого контролю над використанням sp_execute проти "прямого" дзвінка до проц. Вище в ланцюзі викликів я використовую бібліотеку ADO.NET і передаю TVP, як я це робив раніше . Тип параметра правильно встановлено на System.Data.SqlDbType.Structured, а CommandType встановлено на System.Data.CommandType.StoredProcedure .

Чому я нуб (редагувати)

Роб і Мартін Сміт побачили те, чого я не бачив - у заяві, переданому sp_executesql, @MetricDataпараметр не використовується . Неперевершений / зіставлений параметр не збирається використовувати.

Я перекладав свій робочий код параметра C #-значення-параметр в PowerShell, і оскільки він склався, він не міг бути кодом з вини; хіба що було. Під час написання цього питання, я зрозумів , що ні встановити CommandTypeна StoredProcedureтак що я додав цю лінію і знову побіг код , але слід в профайлер не змінилися , тому я припустив , що була проблемою.

Смішна історія - якщо ви не збережете оновлений файл, запуск його не змінить. Я потрапив сьогодні вранці і повторно запустив його (після збереження), і ADO.NET переклав його, EXEC dbo.Repro @MetricData=@p3що працює чудово.

Відповіді:


10

sp_executesqlпризначений для виконання спеціальних T-SQL. Тож вам слід спробувати:

EXECUTE sp_executesql N'exec dbo.Repro @MetricData',N'@MetricData dbo.UDTT READONLY',@MetricData=@p3

1
+1 В даний час сценарій в ОП просто має оператор dbo.Reproі зовсім не використовує передані параметри.
Мартін Сміт

Yesssss, я побачив коментар Роба, і хоча йому не потрібна частина EXEC там, це мало сенс, що причиною, за якою параметр не використовувався при виклику, був параметр, не вказаний у сценарії. Оновлення квитка, щоб відобразити мою дурість
billinkc

@billinkc - А я тільки що додав відповідь з більш детальним поясненням і побачив ваш коментар. Тоді я трохи видалю цю відповідь.
Мартін Сміт

Правильно. Ви не згадали, що робите це з ado.net. sp_executesql еквівалентний .CommandText, а не .StoredProcedure.
Роб Фарлі

3

Я спокусився видалити це питання, але зрозумів, що хтось інший може зіткнутися з подібною обставиною.

Першопричиною було те, що я $updateCommandне встановив CommandType. Значенням за замовчуванням є Text, тому мою збережену процедуру викликали правильно. Мої параметри були створені і пройшли , але , як зазначено в інших відповідях тільки тому , що параметри доступні не означає , що вони в даний час використовуються .

Код, якщо хтось зацікавився моєю помилкою

    $updateConnection = New-Object System.Data.SqlClient.SqlConnection("Data Source=localhost\localsqla;Initial Catalog=baseline;Integrated Security=SSPI;")
    $updateConnection.Open()

    $updateCommand = New-Object System.Data.SqlClient.SqlCommand($updateQuery)
    $updateCommand.Connection = $updateConnection

    # This is what I was missing
    $updateCommand.CommandType = [System.Data.CommandType]::StoredProcedure
    #$updateCommand.CommandType = [System.Data.CommandType]::Text
    #$updateCommand.CommandType = [System.Data.CommandType]::TableDirect

    $DataTransferFormat = $updateCommand.Parameters.AddWithValue("@MetricData", $dataTable)
    $DataTransferFormat.SqlDbType = [System.Data.SqlDbType]::Structured
    $DataTransferFormat.TypeName = $udtt
    $results = $updateCommand.ExecuteNonQuery()

Запуск зі значенням CommandType тексту вимагає рішення @ rob_farley змінити значення $updateQueryна "EXEC dbo.Repro @MetricData". У цей момент sp_executesql буде правильно відображати значення, і життя хороше.

Запуск з CommandTypeОФ TableDirectне підтримує провайдером SqlClient даних, оскільки документація вказує.

Використання CommandTypeв StoredProcedureзрушення в прямий виклик збереженої процедури без sp_executesqlобгортки і прекрасно працює.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.