วิธีการเปลี่ยนข้อยกเว้นเดียวกันใน SQL Server


86

ฉันต้องการลบข้อยกเว้นเดียวกันใน SQL Server ที่เพิ่งเกิดขึ้นในบล็อกลองของฉัน ฉันสามารถส่งข้อความเดียวกันได้ แต่ฉันต้องการแสดงข้อผิดพลาดเดียวกัน

BEGIN TRANSACTION
    BEGIN TRY
        INSERT INTO Tags.tblDomain (DomainName, SubDomainId, DomainCode, Description)
            VALUES(@DomainName, @SubDomainId, @DomainCode, @Description)
        COMMIT TRANSACTION
    END TRY
    
    BEGIN CATCH
        declare @severity int; 
        declare @state int;

        select @severity=error_severity(), @state=error_state();

        RAISERROR(@@Error,@ErrorSeverity,@state);
        ROLLBACK TRANSACTION
    END CATCH

RAISERROR(@@Error, @ErrorSeverity, @state);

บรรทัดนี้จะแสดงข้อผิดพลาด แต่ฉันต้องการฟังก์ชันแบบนั้น นี้ทำให้เกิดข้อผิดพลาดที่มีจำนวนข้อผิดพลาด 50000 แต่ฉันต้องการหมายเลขข้อผิดพลาดจะถูกโยนว่าผมผ่าน@@error,

ฉันต้องการจับข้อผิดพลาดนี้ไม่ที่ส่วนหน้า

กล่าวคือ

catch (SqlException ex)
{
    if ex.number==2627
    MessageBox.show("Duplicate value cannot be inserted");
}

ฉันต้องการฟังก์ชันนี้ ซึ่งไม่สามารถทำได้โดยใช้raiseerror. ฉันไม่ต้องการให้ข้อความแสดงข้อผิดพลาดที่กำหนดเองที่ส่วนหลัง

RAISEERROR ควรส่งคืนข้อผิดพลาดที่กล่าวถึงด้านล่างเมื่อฉันผ่าน ErrorNo ที่จะถูกโยนเข้าจับ

Msg 2627, Level 14, State 1, Procedure spOTest_DomainInsert,

บรรทัดที่ 14 การละเมิดข้อ จำกัด UNIQUE KEY 'UK_DomainCode' ไม่สามารถแทรกคีย์ที่ซ้ำกันในออบเจ็กต์ 'Tags.tblDomain' คำสั่งยุติแล้ว

แก้ไข:

อะไรคือข้อเสียเปรียบของการไม่ใช้ try catch block หากฉันต้องการให้มีการจัดการข้อยกเว้นที่ส่วนหน้าโดยพิจารณาจากขั้นตอนการจัดเก็บที่มีแบบสอบถามหลายรายการที่ต้องดำเนินการ

คำตอบ:


120

นี่คือตัวอย่างโค้ดทั้งหมดที่ทำงานได้อย่างสมบูรณ์เพื่อย้อนกลับชุดคำสั่งหากมีข้อผิดพลาดเกิดขึ้นและรายงานข้อความแสดงข้อผิดพลาด

begin try
    begin transaction;

    ...

    commit transaction;
end try
begin catch
    if @@trancount > 0 rollback transaction;
    throw;
end catch

ก่อน SQL 2012

begin try
    begin transaction;
    
    ...
    
    commit transaction;
end try
begin catch
    declare @ErrorMessage nvarchar(max), @ErrorSeverity int, @ErrorState int;
    select @ErrorMessage = ERROR_MESSAGE() + ' Line ' + cast(ERROR_LINE() as nvarchar(5)), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE();
    if @@trancount > 0 rollback transaction;
    raiserror (@ErrorMessage, @ErrorSeverity, @ErrorState);
end catch

8
ฉันใช้สิ่งนี้ในระหว่างขั้นตอนที่เก็บไว้และพบว่ามันจะดำเนินการต่อหลังจากraiserrorนั้นซึ่งแตกต่างจากวิธีที่ c # ออกหลังจากไฟล์throw. ดังนั้นฉันจึงเพิ่มreturnภายในcatchเพราะฉันต้องการให้ตรงกับพฤติกรรมนั้น
Brian J

@BogdanBogdanov ฉันย้อนการแก้ไขของคุณเนื่องจากจุดของรหัสนี้ต้องน้อยที่สุดและไม่เบี่ยงเบนไปจากรหัสจริงที่ป้อนแทน ...
Ben Gripka

โอเคไม่มีปัญหา @ Ben Gripka ฉันพยายามทำให้อ่านได้มากขึ้นบนหน้าจอ ขอขอบคุณที่ชี้เหตุผลในการย้อนกลับ
Bogdan Bogdanov

1
@BrianJ: โดยปกติการดำเนินการจะหยุดหรือไม่ขึ้นอยู่กับความรุนแรงของข้อผิดพลาดเดิม หากความรุนแรงเท่ากับ> = 11 การดำเนินการควรหยุดลง มันแปลกจริงๆเพราะการจู่โจมในบล็อกจับด้วยความรุนแรง> = 11 ไม่หยุดการดำเนินการอีกต่อไป การสังเกตของคุณดีมากและแสดงให้เห็นว่าเซิร์ฟเวอร์ sql เป็นอย่างไรอย่างน้อยก็ 2008r2 เวอร์ชันที่ใหม่กว่าดูเหมือนดีกว่า
costa

1
@costa ดูRAISERROR()เอกสารของ ความรุนแรงของ≥11จะกระโดดไปที่CATCHบล็อกหากอยู่ในTRYบล็อกเท่านั้น ดังนั้นคุณต้องมีBEGIN TRY…END CATCHโค้ดรอบ ๆ หากคุณต้องการRAISERROR()ให้มีผลต่อการควบคุมโฟลว์
binki

137

SQL 2012 แนะนำคำสั่ง throw:

http://msdn.microsoft.com/en-us/library/ee677615.aspx

หากระบุคำสั่ง THROW โดยไม่มีพารามิเตอร์คำสั่งนั้นจะต้องปรากฏในบล็อก CATCH สิ่งนี้ทำให้ข้อยกเว้นที่ถูกจับได้ถูกยกขึ้น

BEGIN TRY
    BEGIN TRANSACTION
    ...
    COMMIT TRANSACTION
END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION;
    THROW
END CATCH

2
ระวังดูเหมือนว่าโซลูชันนี้ใช้ได้เฉพาะกับ sql server 2012 ขึ้นไป: msdn.microsoft.com/en-us/library/ee677615.aspx
Adi

3
@BogdanBogdanov เพื่อให้คุณสามารถบันทึกข้อผิดพลาดอาจจัดการกับสถานการณ์บางอย่างได้ แต่ถ้าคุณทำไม่ได้คุณต้องการสร้างข้อผิดพลาดอีกครั้งเพื่อให้ได้ลอง / จับที่สูงขึ้นจะมีโอกาสจัดการได้
Robert McKee

ใช่ @ Robert McKee ฉันคิดออกว่า ขออภัยที่ลืมล้างความคิดเห็นนี้
Bogdan Bogdanov

4
อัฒภาคบนROLLBACKเส้นนั้นสำคัญ! หากไม่มีคุณอาจได้รับไฟล์SQLException: Cannot roll back THROW.
idontevenseethecode

5

การย้อนกลับภายในบล็อก CATCH (โค้ดก่อน SQL2012 ใช้คำสั่ง THROW สำหรับ SQL2012 และใหม่กว่า):

DECLARE
    @ErrorMessage nvarchar(4000) = ERROR_MESSAGE(),
    @ErrorNumber int = ERROR_NUMBER(),
    @ErrorSeverity int = ERROR_SEVERITY(),
    @ErrorState int = ERROR_STATE(),
    @ErrorLine int = ERROR_LINE(),
    @ErrorProcedure nvarchar(200) = ISNULL(ERROR_PROCEDURE(), '-');
SELECT @ErrorMessage = N'Error %d, Level %d, State %d, Procedure %s, Line %d, ' + 'Message: ' + @ErrorMessage;
RAISERROR (@ErrorMessage, @ErrorSeverity, 1, @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine)

4

ฉันคิดว่าตัวเลือกของคุณคือ:

  • อย่าจับข้อผิดพลาด (ปล่อยให้ฟองขึ้น)
  • เพิ่มแบบกำหนดเอง

ในบางจุด SQL อาจแนะนำคำสั่ง reraise หรือความสามารถในการตรวจจับข้อผิดพลาดบางอย่างเท่านั้น แต่สำหรับตอนนี้ให้ใช้วิธีแก้ปัญหาชั่วคราว ขออภัย.


7
ใน sql 2012 คุณสามารถเพิ่มข้อยกเว้นอีกครั้งโดยใช้คำหลัก THROW ใหม่
sergiom

5
ใช่. แน่นอนว่าไม่สามารถใช้ได้เมื่อถามคำถามนี้
Rob Farley

การจับและโยนข้อผิดพลาดใหม่จะสำคัญกว่าการไม่จับและปล่อยให้มัน 'เป็นฟอง' เพราะคุณอาจต้องมีกิจกรรมทำความสะอาดแก้ไขและปิดเพื่อจัดการข้อยกเว้นอย่างเหมาะสม ตัวอย่างที่เห็นได้ชัดคือการปิดและกำจัดเคอร์เซอร์ ตัวอย่างอื่น ๆ อาจเป็นการดำเนินการตามขั้นตอนการบันทึกหรือเพื่อรีเซ็ตข้อมูลบางส่วน
Antony Booth

1

คุณทำไม่ได้: มีเพียงเครื่องยนต์เท่านั้นที่สามารถส่งข้อผิดพลาดได้น้อยกว่า 50000 สิ่งที่คุณทำได้คือโยนข้อยกเว้นที่ดูเหมือน ...

โปรดดูคำตอบของฉันที่นี่

ผู้ถามที่นี่ใช้ธุรกรรมฝั่งไคลเอ็นต์เพื่อทำสิ่งที่เขาต้องการซึ่งฉันคิดว่ามันโง่ไปหน่อย ...


0

ตกลงนี่เป็นวิธีแก้ปัญหา ... :-)

DECLARE @Error_Number INT
BEGIN TRANSACTION 
    BEGIN TRY
    INSERT INTO Test(Id, Name) VALUES (newID(),'Ashish') 
    /* Column 'Name' has unique constraint on it*/
    END TRY
    BEGIN CATCH

            SELECT ERROR_NUMBER()
            --RAISERROR (@ErrorMessage,@Severity,@State)
            ROLLBACK TRAN
    END CATCH

หากคุณสังเกตบล็อคการจับจะไม่เพิ่มข้อผิดพลาด แต่ส่งคืนหมายเลขข้อผิดพลาดจริง (และจะย้อนกลับธุรกรรมด้วย) ตอนนี้ในรหัส. NET ของคุณแทนที่จะจับข้อยกเว้นหากคุณใช้ ExecuteScalar () คุณจะได้รับหมายเลขข้อผิดพลาดจริงที่คุณต้องการและแสดงหมายเลขที่เหมาะสม

int errorNumber=(int)command.ExecuteScalar();
if(errorNumber=<SomeNumber>)
{
    MessageBox.Show("Some message");
}

หวังว่านี่จะช่วยได้

แก้ไข: - หมายเหตุหากคุณต้องการรับจำนวนระเบียนที่ได้รับผลกระทบและพยายามใช้ ExecuteNonQuery โซลูชันข้างต้นอาจไม่เหมาะกับคุณ มิฉะนั้นฉันคิดว่ามันจะเหมาะกับสิ่งที่คุณต้องการ แจ้งให้เราทราบ


@Ashish Gupta: ขอบคุณสำหรับความช่วยเหลือ แต่ฉันต้องการข้อยกเว้นที่จะถูกโยนจากฐานข้อมูลไปยังส่วนหน้ามิฉะนั้นฉันมีตัวเลือกมากมายที่เปิดอยู่เช่น print error_number () ส่งคืน error_number และ 1 u ที่แนะนำ
Shantanu Gupta

0

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

If @@ERROR > 0
Return

ฉันรู้สึกประหลาดใจที่พบว่าการดำเนินการในกระบวนงานที่จัดเก็บไว้สามารถดำเนินการต่อได้หลังจากเกิดข้อผิดพลาด - การไม่ตระหนักว่าสิ่งนี้อาจนำไปสู่การติดตามจุดบกพร่องได้ยาก

ประเภทของข้อผิดพลาดในการจัดการคู่ขนาน (pre .Net) Visual Basic 6. รอคอยคำสั่ง Throw ใน SQL Server 2012


0

เนื่องจากคุณยังไม่ได้ย้ายไปที่ปี 2012 วิธีหนึ่งในการใช้รหัสข้อผิดพลาดเดิมคือการใช้ส่วนข้อความของข้อยกเว้นที่คุณกำลัง (อีกครั้ง) โยนจากบล็อก catch โปรดจำไว้ว่าอาจมีโครงสร้างบางอย่างตัวอย่างเช่นข้อความ XML สำหรับรหัสผู้โทรของคุณเพื่อแยกวิเคราะห์ในบล็อก catch


0

คุณยังสามารถสร้างโพรซีเดอร์ที่เก็บ Wrapper สำหรับสถานการณ์เหล่านั้นเมื่อคุณต้องการให้คำสั่ง SQL ดำเนินการภายในธุรกรรมและป้อนข้อผิดพลาดไปยังโค้ดของคุณ

CREATE PROCEDURE usp_Execute_SQL_Within_Transaction
(
    @SQL nvarchar(max)
)
AS

SET NOCOUNT ON

BEGIN TRY
    BEGIN TRANSACTION
        EXEC(@SQL)
    COMMIT TRANSACTION
END TRY

BEGIN CATCH
    DECLARE @ErrorMessage nvarchar(max), @ErrorSeverity int, @ErrorState int
    SELECT @ErrorMessage = N'Error Number: ' + CONVERT(nvarchar(5), ERROR_NUMBER()) + N'. ' + ERROR_MESSAGE() + ' Line ' + CONVERT(nvarchar(5), ERROR_LINE()), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE()
    ROLLBACK TRANSACTION
    RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState)
END CATCH

GO

-- Test it
EXEC usp_Execute_SQL_Within_Transaction @SQL = 'SELECT 1; SELECT 2'
EXEC usp_Execute_SQL_Within_Transaction @SQL = 'SELECT 1/0; SELECT 2'
EXEC usp_Execute_SQL_Within_Transaction @SQL = 'EXEC usp_Another_SP'

-2

จากมุมมองของการออกแบบประเด็นของการทิ้งข้อยกเว้นด้วยหมายเลขข้อผิดพลาดดั้งเดิมและข้อความที่กำหนดเองคืออะไร? ในระดับหนึ่งมันทำลายสัญญาอินเทอร์เฟซระหว่างแอปพลิเคชันและฐานข้อมูล หากคุณต้องการตรวจจับข้อผิดพลาดดั้งเดิมและจัดการกับข้อผิดพลาดที่สูงขึ้นอย่าจัดการข้อผิดพลาดในฐานข้อมูล จากนั้นเมื่อคุณพบข้อยกเว้นคุณสามารถเปลี่ยนข้อความที่นำเสนอให้กับผู้ใช้ได้เป็นอะไรก็ได้ที่คุณต้องการ ฉันจะไม่ทำเพราะมันทำให้รหัสฐานข้อมูลของคุณ 'ไม่ถูกต้อง' อย่างที่คนอื่นบอกว่าคุณควรกำหนดชุดรหัสข้อผิดพลาดของคุณเอง (สูงกว่า 50000) และโยนรหัสเหล่านั้นแทน จากนั้นคุณสามารถจัดการกับปัญหาด้านความสมบูรณ์ ('ไม่อนุญาตให้ใช้ค่าที่ซ้ำกัน') แยกจากปัญหาทางธุรกิจที่อาจเกิดขึ้น - 'รหัสไปรษณีย์ไม่ถูกต้อง', 'ไม่พบแถวที่ตรงกับเกณฑ์' เป็นต้น


9
อะไรคือจุดสำคัญของการทิ้งข้อยกเว้นด้วยหมายเลขข้อผิดพลาดดั้งเดิมและข้อความที่กำหนดเอง? สมมติว่าคุณต้องการจัดการข้อผิดพลาดเฉพาะ (คาดว่า) หนึ่งหรือสองข้อโดยตรงในบล็อก catch และปล่อยส่วนที่เหลือไว้สำหรับเลเยอร์ที่สูงขึ้น ดังนั้นคุณจะต้องสามารถแก้ไขข้อยกเว้นที่คุณไม่ได้จัดการขึ้นใหม่ ...
Jenda

1
นอกเหนือจากสิ่งที่ @Jenda อธิบายแล้วฉันชอบใช้ try-catch เพื่อให้แน่ใจว่าการเรียกใช้โค้ดจะไม่ดำเนินต่อไปหลังจากเกิดข้อยกเว้นเหมือนกับใน C #: try { code(); } catch (Exception exc) { log(exc); throw; } finally { cleanup(); }ซึ่งthrow;จะเพิ่มข้อยกเว้นเดิมขึ้นพร้อมกับบริบทเดิม
R. Schreurs

ฉันตรวจจับข้อผิดพลาดและส่งข้อความแสดงข้อผิดพลาดที่กำหนดเองใน SQL ซ้ำเพื่อเพิ่มรายละเอียดที่อธิบายว่าบรรทัดใดเกิดข้อผิดพลาดหรือรายละเอียดอื่น ๆ (เช่นข้อมูลที่พยายามแทรก) เพื่อช่วยในการติดตามข้อผิดพลาดในภายหลัง
Russell Hankins
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.