Search code examples
sql-servert-sqlcastingtry-catch

Converting a VARCHAR to an INT; is TRY_CAST or TRY/CATCH better?


I'm looking at some T-SQL code (in a SQL Server 2019 environment) where a parameter in a stored procedure is an "overloaded" VARCHAR (15) parameter, and later is meant to be converted to an INT. I pondered how this could go wrong -- it may not actually be a numeric string, or it might represent a number too big to fit in an INT variable. I want something efficient, and I began pondering using a TRY_CAST instead of just CAST, or using a TRY/CATCH block, to do the conversion on success, or allow "a way out" if not. To simplify...

-- given these...
declare @AParamter varchar (15) = '214748364700'; -- 100x bigger than max INT
declare @AVariable int;

-- ...approach 1...
set @AVariable = try_cast( @AParamter as int );

if (@AVariable is null) begin
    print 'ERROR: invalid parameter';
end

-- ...approach 2...
begin try
    set @AVariable = cast( @AParamter as int );
end try
begin catch
    print 'ERROR: invalid parameter';
end catch

Both work, but I'm wondering which is more "efficient", i.e. requires less resources (and admittedly, either is probably somewhat minimal compared to everything else going on), or if either of these has any hidden "gotchas".

I'm using the sys.dm_exec_*_stats views to gather metrics for the stored procedure I'm testing, and I'm assuming the Total_Worker_Time and Total_Elapsed_Time metrics are the most relevant, but they can vary so much (within the context of the proc) that it's hard to see which approach may be having a bigger (or any) effect, for better or for worse. So, I'm asking this mostly out of curiosity as to which approach is a better practice, or what else might be even better yet.

I tried to search the web and StackOverflow for any info, but I couldn't seem to find any results referring to performance in comparison.

Note: my preference would be to not overload this parameter; me personal feeling is that it should be multiple parameters, each of the correct type, and only one of them not null at a time, with specific checks to assure that we have one and only one filled in, but... that's not really up to me.


Solution

  • TRY_CAST is far and away faster, as well as being much clearer.

    The following script will show you that:

    declare @v varchar(10) = 'hello';
    
    declare @i int = 1;
    declare @x int;
    declare @dt datetime2 = sysdatetime();
    while @i < 10000
    begin
        set @x = try_cast(@v as int);
        set @i += 1;
    end;
    select datediff(mcs, @dt, sysdatetime());
    
    
    set @i = 1;
    set @dt = sysdatetime();
    
    while @i < 10000
    begin
        begin try
            set @x = cast(@v as int);
        end try
        begin catch
        end catch;
        set @i += 1;
    end;
    select datediff(mcs, @dt, sysdatetime());
    

    db<>fiddle

    10k loops takes about 1 second if you are catching, whereas TRY_CAST takes around 30ms.

    The reason is probably simple: BEGIN CATCH involves a lot of gubbins with exception handling, such as walking the stack and unwinding. Whereas TRY_CAST can just return a null.