Cách báo cáo lỗi từ hàm do người dùng SQL Server xác định


146

Tôi đang viết một hàm do người dùng định nghĩa trong SQL Server 2008. Tôi biết rằng các hàm không thể phát sinh lỗi theo cách thông thường - nếu bạn cố gắng đưa vào câu lệnh RAISERROR mà SQL trả về:

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

Nhưng thực tế là, hàm lấy một số đầu vào, có thể không hợp lệ và, nếu có, không có giá trị có ý nghĩa mà hàm có thể trả về. Tôi phải làm gì sau đó?

Tất nhiên tôi có thể trả về NULL, nhưng sẽ khó cho bất kỳ nhà phát triển nào sử dụng chức năng để khắc phục sự cố này. Tôi cũng có thể gây ra sự phân chia bằng 0 hoặc đại loại như thế - điều này sẽ tạo ra một thông báo lỗi, nhưng là một thông báo sai lệch. Có cách nào tôi có thể có thông báo lỗi của riêng mình được báo cáo bằng cách nào đó không?

Câu trả lời:


223

Bạn có thể sử dụng CAST để ném lỗi có ý nghĩa:

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

Sau đó, Sql Server sẽ hiển thị một số thông tin trợ giúp:

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

112
Câu trả lời tuyệt vời, nhưng JEEZ sẽ hack. > :(
JohnL4

5
Đối với một hàm có giá trị bảng nội tuyến trong đó RETURN là một lựa chọn đơn giản, một mình nó không hoạt động vì không có gì được trả về - thậm chí không có giá trị và trong trường hợp của tôi, tôi muốn ném lỗi khi không tìm thấy gì. Tôi không muốn chia nhỏ chức năng nội tuyến thành một đa mục đích vì lý do hiệu suất rõ ràng. Thay vào đó tôi đã sử dụng giải pháp của bạn cộng với ISNULL và MAX. Hiện tại, thống kê RETURN trông như thế này: CHỌN ISNULL (MAX (E.EntityID), CAST ('Tra cứu (' + @LookupVariable + ') không tồn tại.' Như Int)) [EntityID] TỪ Thực thể như E WHERE E. Tra cứu = @ Tra cứu Biến đổi
MikeTeeVee

Có, bạn có thể ném lỗi, nhưng dường như bạn không thể ném lỗi một cách có điều kiện. Hàm được thực thi bất kể đường dẫn mã.
satnhak

10
Giải pháp tuyệt vời, nhưng đối với những người đang sử dụng TVF, điều này không thể dễ dàng trở thành một phần của sự trở lại. Dành cho những người:declare @error int; set @error = 'Error happened here.';
Tim Lehner

20
Tôi ghét điều này với sức mạnh của một ngàn mặt trời đang cháy. Không có lựa chọn nào khác? Khỏe. Nhưng làm tê liệt ...
Remi Despres-Smyth

18

Thủ thuật thông thường là buộc chia cho 0. Điều này sẽ gây ra lỗi và làm gián đoạn câu lệnh hiện tại đang đánh giá hàm. Nếu nhà phát triển hoặc người hỗ trợ biết về hành vi này, việc điều tra và khắc phục sự cố khá dễ dàng vì lỗi chia cho 0 được hiểu là một triệu chứng của một vấn đề khác, không liên quan.

Tệ như điều này nhìn từ bất kỳ quan điểm nào, thật không may, thiết kế các hàm SQL tại thời điểm này cho phép không có lựa chọn nào tốt hơn. Sử dụng RAISERROR hoàn toàn nên được cho phép trong các chức năng.


7

Theo sau câu trả lời của Vladimir Korolev, thành ngữ để ném lỗi một cách có điều kiện là

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

Tôi nghĩ cách sạch nhất là chỉ chấp nhận rằng hàm có thể trả về NULL nếu các đối số không hợp lệ được thông qua. Miễn là điều này được ghi lại rõ ràng thì điều này sẽ ổn chứ?

-- =============================================
-- 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

RAISEERRORhoặc @@ERRORkhông được phép trong UDFs. Bạn có thể biến UDF thành một thủ tục strored không?

Từ xử lý lỗi bài viết của Erland Sommarskog trong SQL Server - một Nền tảng :

Các hàm do người dùng định nghĩa thường được gọi như là một phần của câu lệnh SET, SELECT, INSERT, UPDATE hoặc DELETE. Những gì tôi đã tìm thấy là nếu một lỗi xuất hiện trong hàm có giá trị bảng đa câu lệnh hoặc trong hàm vô hướng, thì việc thực thi hàm bị hủy bỏ ngay lập tức và do đó, câu lệnh là một phần của. Thực thi tiếp tục trên dòng tiếp theo, trừ khi lỗi đã hủy bỏ lô. Trong cả hai trường hợp, lỗi @@ là 0. Do đó, không có cách nào để phát hiện ra rằng lỗi xảy ra trong một hàm từ T-SQL.

Vấn đề không xuất hiện với các hàm bảng nội tuyến, vì một hàm có giá trị bảng nội tuyến về cơ bản là một macro mà bộ xử lý truy vấn dán vào truy vấn.

Bạn cũng có thể thực thi các hàm vô hướng với câu lệnh EXEC. Trong trường hợp này, việc thực thi tiếp tục nếu xảy ra lỗi (trừ khi đó là lỗi hủy bỏ hàng loạt). Lỗi @@ được đặt và bạn có thể kiểm tra giá trị của lỗi @@ trong hàm. Nó có thể có vấn đề để giao tiếp lỗi cho người gọi mặc dù.


4

Câu trả lời hàng đầu nói chung là tốt nhất, nhưng không hoạt động đối với các hàm có giá trị trong bảng nội tuyến.

MikeTeeVee đã đưa ra một giải pháp cho điều này trong nhận xét của mình về câu trả lời hàng đầu, nhưng nó yêu cầu sử dụng một hàm tổng hợp như MAX, không hoạt động tốt cho hoàn cảnh của tôi.

Tôi đã loay hoay với một giải pháp thay thế cho trường hợp bạn cần một bảng nội tuyến có giá trị udf trả về một cái gì đó như select * thay vì tổng hợp. Mã mẫu giải quyết trường hợp cụ thể này là dưới đây. Như ai đó đã chỉ ra ... "JEEZ wotta hack" :) Tôi hoan nghênh mọi giải pháp tốt hơn cho trường hợp này!

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
Đối với bất kỳ ai đọc, tôi đã không nhìn vào các hiệu ứng hiệu suất tiềm năng ... tôi sẽ không ngạc nhiên nếu tuyên bố hack union + case làm mọi thứ chậm lại ...
davec

4

Một vài người đã hỏi về việc tăng lỗi trong các hàm Giá trị Bảng, vì bạn không thể sử dụng loại " RETURN [cast không hợp lệ] ". Việc gán cast không hợp lệ cho một biến cũng hoạt động tốt.

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

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

RETURN

END

Kết quả này trong:

Msg 245, Cấp 16, Trạng thái 1, Chuyển đổi Dòng 14 không thành công khi chuyển đổi giá trị varchar 'booooom!' để kiểu dữ liệu int.


2

Tôi không thể nhận xét theo câu trả lời của davec về chức năng có giá trị của bảng, nhưng theo ý kiến ​​khiêm tốn của tôi thì đây là giải pháp dễ dàng hơn:

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

Một cách (hack) là có một chức năng / thủ tục được lưu trữ thực hiện một hành động không hợp lệ. Ví dụ: SQL giả sau đây

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;

Trong trường hợp trên bảng tbl_throw_error, có một ràng buộc duy nhất trên cột err_msg. Một tác dụng phụ của điều này (ít nhất là trên MySQL), là giá trị của err_msg được sử dụng làm mô tả ngoại lệ khi nó được sao lưu vào đối tượng ngoại lệ ở cấp ứng dụng.

Tôi không biết liệu bạn có thể làm điều gì đó tương tự với SQL Server không, nhưng đáng để thử.


5
Ý tưởng thú vị, nhưng INSERT cũng không được phép trong một chức năng.
EMP
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.