วิธีการรายงานข้อผิดพลาดจากฟังก์ชั่นที่ผู้ใช้กำหนดของ SQL Server


146

ฉันกำลังเขียนฟังก์ชั่นที่ผู้ใช้กำหนดใน SQL Server 2008 ฉันรู้ว่าฟังก์ชั่นไม่สามารถเพิ่มข้อผิดพลาดได้ตามปกติ - ถ้าคุณพยายามรวมคำสั่ง RAISERROR คำสั่ง SQL จะคืนค่า:

Msg 443, Level 16, State 14, Procedure ..., Line ...
Invalid use of a side-effecting operator 'RAISERROR' within a function.

แต่ความจริงก็คือฟังก์ชั่นรับอินพุตบางส่วนซึ่งอาจไม่ถูกต้องและหากเป็นเช่นนั้นจะไม่มีค่าที่มีความหมายที่ฟังก์ชันสามารถส่งคืนได้ ฉันจะทำอย่างไรดี

แน่นอนว่าฉันสามารถส่งคืน NULL ได้ แต่มันจะเป็นเรื่องยากสำหรับนักพัฒนาที่ใช้ฟังก์ชั่นในการแก้ไขปัญหานี้ ฉันสามารถทำให้เกิดการหารด้วยศูนย์หรืออะไรทำนองนั้น - นี่จะสร้างข้อความแสดงข้อผิดพลาด แต่เป็นการทำให้เข้าใจผิด มีวิธีใดบ้างที่ฉันสามารถแจ้งข้อความแสดงข้อผิดพลาดของตัวเองได้

คำตอบ:


223

คุณสามารถใช้นักแสดงเพื่อโยนข้อผิดพลาดที่มีความหมาย:

create function dbo.throwError()
returns nvarchar(max)
as
begin
    return cast('Error happened here.' as int);
end

จากนั้น SQL Server จะแสดงข้อมูลช่วยเหลือบางอย่าง:

Msg 245, Level 16, State 1, Line 1
Conversion failed when converting the varchar value 'Error happened here.' to data type int.

112
คำตอบที่ดี แต่ JEEZ ไม่แฮ็ค > :(
JohnL4

5
สำหรับฟังก์ชั่นอินไลน์ตารางมูลค่าซึ่ง RETURN เป็นตัวเลือกที่เรียบง่ายสิ่งนี้ไม่ทำงานเพราะไม่มีอะไรถูกส่งกลับ - ไม่เป็นโมฆะและในกรณีของฉันฉันต้องการโยนข้อผิดพลาดเมื่อไม่พบสิ่งใด ฉันไม่ต้องการแยกฟังก์ชั่นอินไลน์เป็นหนึ่งในหลายสถานะด้วยเหตุผลด้านประสิทธิภาพที่ชัดเจน ฉันใช้โซลูชันของคุณพร้อมกับ ISNULL และ MAX แทน ตอนนี้ RETURN statment มีลักษณะดังนี้: SELECT ISNULL (MAX (E.EntityID), CAST ('The Lookup (' + @LookupVariable + ') ไม่มีอยู่' as Int)) [EntityID] จาก EntityID เป็น E WHERE E Lookup = @ LookupVariable
MikeTeeVee

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

10
ทางออกที่ดี แต่สำหรับผู้ที่ใช้ TVF สิ่งนี้ไม่สามารถเป็นส่วนหนึ่งของการตอบแทนได้อย่างง่ายดาย สำหรับผู้ที่:declare @error int; set @error = 'Error happened here.';
Tim Lehner

20
ฉันเกลียดสิ่งนี้ด้วยพลังแห่งดวงอาทิตย์ที่แผดเผานับพันดวง ไม่มีตัวเลือกอื่น ๆ ? ละเอียด. แต่ cripes ...
Remi Despres-Smyth

18

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

ไม่ดีเท่าที่ดูจากมุมมองใด ๆ แต่น่าเสียดายที่การออกแบบฟังก์ชั่น SQL ในขณะนี้ทำให้ไม่มีทางเลือกที่ดีกว่า การใช้ RAISERROR ควรได้รับอนุญาตในฟังก์ชั่นอย่างแน่นอน


7

จากคำตอบของ Vladimir Korolev สำนวนที่ใช้ในการโยนข้อผิดพลาดตามเงื่อนไขคือ

CREATE FUNCTION [dbo].[Throw]
(
    @error NVARCHAR(MAX)
)
RETURNS BIT
AS
BEGIN
    RETURN CAST(@error AS INT)
END
GO

DECLARE @error NVARCHAR(MAX)
DECLARE @bit BIT

IF `error condition` SET @error = 'My Error'
ELSE SET @error = '0'

SET @bit = [dbo].[Throw](@error)    

6

ฉันคิดว่าวิธีที่สะอาดที่สุดคือการยอมรับว่าฟังก์ชันสามารถคืนค่า NULL ได้หากมีการส่งอาร์กิวเมนต์ที่ไม่ถูกต้อง ตราบใดที่มีการบันทึกไว้อย่างชัดเจนจึงควรจะเป็นเช่นนั้น

-- =============================================
-- Author: AM
-- Create date: 03/02/2010
-- Description: Returns the appropriate exchange rate
-- based on the input parameters.
-- If the rate cannot be found, returns NULL
-- (RAISEERROR can't be used in UDFs)
-- =============================================
ALTER FUNCTION [dbo].[GetExchangeRate] 
(
    @CurrencyFrom char(3),
    @CurrencyTo char(3),
    @OnDate date
)
RETURNS decimal(18,4)
AS
BEGIN

  DECLARE @ClosingRate as decimal(18,4)

    SELECT TOP 1
        @ClosingRate=ClosingRate
    FROM
        [FactCurrencyRate]
    WHERE
        FromCurrencyCode=@CurrencyFrom AND
        ToCurrencyCode=@CurrencyTo AND
        DateID=dbo.DateToIntegerKey(@OnDate)

    RETURN @ClosingRate 

END
GO

5

RAISEERRORหรือ@@ERRORไม่ได้รับอนุญาตใน UDF คุณสามารถเปลี่ยน UDF ให้เป็นกระบวนงานที่เก็บไว้ได้หรือไม่?

จากบทความของ Erland Sommarskog การจัดการข้อผิดพลาดใน SQL Server - พื้นหลัง :

ฟังก์ชั่นที่ผู้ใช้กำหนดมักจะถูกเรียกใช้ซึ่งเป็นส่วนหนึ่งของคำสั่ง SET, SELECT, INSERT, UPDATE หรือ DELETE สิ่งที่ฉันได้พบคือหากมีข้อผิดพลาดปรากฏขึ้นในฟังก์ชั่นตารางมูลค่าหลายคำสั่งหรือในฟังก์ชันสเกลาร์การดำเนินการของฟังก์ชั่นจะถูกยกเลิกทันทีและเป็นคำสั่งฟังก์ชั่นที่เป็นส่วนหนึ่ง การดำเนินการต่อเนื่องในบรรทัดถัดไปยกเว้นว่าข้อผิดพลาดถูกยกเลิกแบทช์ ในทั้งสองกรณีข้อผิดพลาด @@ คือ 0 ดังนั้นจึงไม่มีวิธีการตรวจสอบว่ามีข้อผิดพลาดเกิดขึ้นในฟังก์ชันจาก T-SQL

ปัญหาไม่ปรากฏขึ้นพร้อมกับฟังก์ชั่นตารางแบบอินไลน์เนื่องจากฟังก์ชั่นมูลค่าตารางแบบอินไลน์นั้นเป็นแมโครที่ตัวประมวลผลแบบสอบถามวางลงในแบบสอบถาม

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


4

คำตอบยอดนิยมโดยทั่วไปจะดีที่สุด แต่ไม่สามารถใช้ได้กับฟังก์ชั่นที่มีค่าในตาราง

MikeTeeVee ให้วิธีแก้ปัญหานี้ในความคิดเห็นของเขาในคำตอบยอดนิยม แต่มันจำเป็นต้องใช้ฟังก์ชั่นรวมเช่น MAX ซึ่งไม่ได้ทำงานได้ดีสำหรับสถานการณ์ของฉัน

ฉันยุ่งกับโซลูชันอื่นสำหรับกรณีที่คุณต้องการตารางแบบอินไลน์ที่มีมูลค่า udf ที่ให้ผลตอบแทนเช่นselect *แทนที่จะเป็นผลรวม โค้ดตัวอย่างที่แก้ปัญหากรณีนี้อยู่ด้านล่าง อย่างที่ใครบางคนชี้ไปแล้ว ... "JEEZ wotta hack" :) ฉันยินดีต้อนรับทางออกที่ดีกว่าสำหรับคดีนี้!

create table foo (
    ID nvarchar(255),
    Data nvarchar(255)
)
go

insert into foo (ID, Data) values ('Green Eggs', 'Ham')
go

create function dbo.GetFoo(@aID nvarchar(255)) returns table as return (
    select *, 0 as CausesError from foo where ID = @aID

    --error checking code is embedded within this union
    --when the ID exists, this second selection is empty due to where clause at end
    --when ID doesn't exist, invalid cast with case statement conditionally causes an error
    --case statement is very hack-y, but this was the only way I could get the code to compile
    --for an inline TVF
    --simpler approaches were caught at compile time by SQL Server
    union

    select top 1 *, case
                        when ((select top 1 ID from foo where ID = @aID) = @aID) then 0
                        else 'Error in GetFoo() - ID "' + IsNull(@aID, 'null') + '" does not exist'
                    end
    from foo where (not exists (select ID from foo where ID = @aID))
)
go

--this does not cause an error
select * from dbo.GetFoo('Green Eggs')
go

--this does cause an error
select * from dbo.GetFoo('Yellow Eggs')
go

drop function dbo.GetFoo
go

drop table foo
go

1
สำหรับทุกคนที่อ่านฉันไม่ได้ดูผลการปฏิบัติงานที่อาจเกิดขึ้น ... ฉันจะไม่แปลกใจถ้าคำสั่งแฮ็คยูเนี่ยน + กรณีช้าลง ...
davec

4

มีคนถามเกี่ยวกับการเพิ่มข้อผิดพลาดในฟังก์ชั่น Table-Valued เนื่องจากคุณไม่สามารถใช้สิ่งต่าง ๆ" RETURN [cast ไม่ถูกต้อง] " การกำหนด cast ที่ไม่ถูกต้องให้กับตัวแปรใช้งานได้เช่นกัน

CREATE FUNCTION fn()
RETURNS @T TABLE (Col CHAR)  
AS
BEGIN

DECLARE @i INT = CAST('booooom!' AS INT)  

RETURN

END

ผลลัพธ์นี้ใน:

ข่าวสารเกี่ยวกับ 245, ระดับ 16, สถานะ 1, การแปลงบรรทัดที่ 14 ล้มเหลวเมื่อแปลงค่า varchar 'booooom!' ไปยังชนิดข้อมูล int


2

ฉันไม่สามารถแสดงความคิดเห็นภายใต้คำตอบของ davec เกี่ยวกับฟังก์ชั่นการประเมินค่าตาราง แต่ในความเห็นที่ต่ำต้อยของฉันนี่เป็นวิธีแก้ปัญหาที่ง่ายกว่า:

CREATE FUNCTION dbo.ufn_test (@a TINYINT)
RETURNS @returns TABLE(Column1 VARCHAR(10), Value1 TINYINT)
BEGIN
    IF @a>50 -- if @a > 50 - raise an error
    BEGIN
      INSERT INTO @returns (Column1, Value1)
      VALUES('error','@a is bigger than 50!') -- reminder Value1 should be TINYINT
    END

    INSERT INTO @returns (Column1, Value1)
    VALUES('Something',@a)
    RETURN;
END

SELECT Column1, Value1 FROM dbo.ufn_test(1) -- this is okay
SELECT Column1, Value1 FROM dbo.ufn_test(51) -- this will raise an error

-3

วิธีหนึ่ง (แฮ็ค) คือการมีฟังก์ชั่น / ขั้นตอนการจัดเก็บที่ดำเนินการกระทำที่ไม่ถูกต้อง ตัวอย่างเช่นหลอก SQL ต่อไปนี้

create procedure throw_error ( in err_msg varchar(255))
begin
insert into tbl_throw_error (id, msg) values (null, err_msg);
insert into tbl_throw_error (id, msg) values (null, err_msg);
end;

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

ฉันไม่รู้ว่าคุณสามารถทำสิ่งที่คล้ายกับ SQL Server แต่คุ้มค่ากับการยิง


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