sp_executesql กับประเภทตารางที่ผู้ใช้กำหนดไม่ทำงานอย่างถูกต้อง


11

ปัญหา : มีปัญหาที่ทราบเกี่ยวกับประเภทตารางที่ผู้ใช้กำหนดเป็นพารามิเตอร์สำหรับsp_executesqlหรือไม่ คำตอบ - ไม่ฉันเป็นคนงี่เง่า

ตั้งค่าสคริปต์

สคริปต์นี้สร้างหนึ่งในแต่ละตารางขั้นตอนและประเภทตารางที่ผู้ใช้กำหนด (จำกัด SQL Server 2008+ เท่านั้น)

  • จุดประสงค์ของกองคือเพื่อให้การตรวจสอบว่าใช่ข้อมูลทำให้เป็นขั้นตอน ไม่มีข้อ จำกัด ไม่มีสิ่งใดที่จะป้องกันไม่ให้มีการแทรกข้อมูล

  • ขั้นตอนใช้เป็นพารามิเตอร์ที่ผู้ใช้กำหนดประเภทตาราง proc ทั้งหมดจะถูกแทรกลงในตาราง

  • ประเภทตารางที่ผู้ใช้กำหนดนั้นเรียบง่ายเช่นกันเพียงแค่คอลัมน์เดียว

ฉันเรียกใช้สิ่งต่อไปนี้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 เทียบกับการเรียก "ตรง" ไปยัง proc ได้ ที่สูงขึ้นห่วงโซ่โทรผมใช้ห้องสมุด ADO.NET และผ่านใน TVP ที่ผมได้ทำมาก่อนหน้านี้ ประเภทของพารามิเตอร์การตั้งค่าอย่างถูกต้องเพื่อSystem.Data.SqlDbType.Structuredและ CommandType ถูกตั้งค่าให้System.Data.CommandType.StoredProcedure

ทำไมฉันถึงเป็น noob (แก้ไข)

Rob และ Martin Smith เห็นสิ่งที่ฉันไม่เห็น - คำสั่งที่ส่งผ่านไปยัง sp_executesql ไม่ได้ใช้@MetricDataพารามิเตอร์ พารามิเตอร์ที่ไม่ผ่าน / แมปจะไม่ถูกใช้

ฉันแปลโค้ดพารามิเตอร์มูลค่าตารางการทำงาน C # ของฉันเป็น PowerShell และเมื่อรวบรวมมันก็ไม่สามารถเป็นรหัสที่ผิดได้ ยกเว้นมันเป็น ในขณะที่เขียนคำถามนี้ผมรู้ว่าผมไม่ได้ตั้งค่าCommandTypeไปStoredProcedureดังนั้นฉันเพิ่มบรรทัดที่และอีกครั้งวิ่งรหัส แต่ร่องรอยใน Profiler ไม่เปลี่ยนแปลงดังนั้นผมจึงคิดว่าไม่ได้เป็นปัญหา

เรื่องตลก - หากคุณไม่บันทึกไฟล์ที่อัปเดตการรันมันจะไม่สร้างความแตกต่าง ฉันได้รับในเช้านี้และวิ่งอีกครั้ง (หลังจากบันทึก) และ 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 ปัจจุบันสคริปต์ใน OP เพิ่งมีข้อความสั่งdbo.Reproและไม่ใช้พารามิเตอร์ส่งผ่านเลย
Martin Smith

Yesssss ฉันเห็นความคิดเห็นของ Rob และในขณะที่มันไม่ต้องการส่วน EXEC ในนั้นก็ทำให้รู้สึกว่าเหตุผลที่พารามิเตอร์ที่ไม่ได้ใช้ในการโทรด้วยคือพารามิเตอร์ไม่ได้อ้างอิงในสคริปต์ อัปเดตตั๋วเพื่อสะท้อนความโง่ของฉัน
billinkc

@billinkc - อ่าฉันเพิ่งเพิ่มคำตอบพร้อมคำอธิบายที่ละเอียดกว่านี้แล้วเห็นความคิดเห็นของคุณ ฉันจะลบคำตอบนั้นในอีกสักครู่
Martin Smith

ขวา. คุณไม่ได้พูดถึงว่าคุณกำลังทำสิ่งนี้จาก ado.net sp_executesql เทียบเท่ากับ. CommandText ไม่ใช่ .StoredProcedure
Rob Farley

3

ฉันถูกล่อลวงให้ลบคำถามนี้ แต่คิดว่าคนอื่นอาจเจอสถานการณ์ที่คล้ายกัน

สาเหตุหลักคือฉัน$updateCommandไม่ได้ตั้ง CommandType ค่าเริ่มต้นคือข้อความดังนั้นขั้นตอนการจัดเก็บของฉันถูกเรียกอย่างถูกต้อง พารามิเตอร์ของฉันถูกสร้างและส่งผ่าน แต่ตามที่ระบุไว้ในคำตอบอื่น ๆ เพียงเพราะพารามิเตอร์ที่มีอยู่ไม่ได้หมายความว่ากำลังใช้งานอยู่

รหัสในกรณีที่ทุกคนสนใจในความผิดพลาดของฉัน

    $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 of Text จะต้องใช้โซลูชันของ @ rob_farley ในการเปลี่ยนค่า$updateQueryเป็น "EXEC dbo.Repro @MetricData" ณ จุดนั้น sp_executesql จะจับคู่ค่าอย่างถูกต้องและอายุการใช้งานจะดี

เล่นกับCommandTypeของTableDirectไม่ได้รับการสนับสนุนจากผู้ให้บริการ SqlClient ข้อมูลเป็นเอกสารที่บ่งชี้

การใช้CommandTypeของStoredProcedureแปลว่าจะอุทธรณ์โดยตรงของการจัดเก็บโดยไม่ต้องsp_executesqlห่อหุ้มและใช้งานได้ดี

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.