7

What is the simplest way to get the same output in SQL Server 2008?

SQLServer 2012 :

select
    try_parse(Isnull('123.66',0) as float) as a ,
    try_parse(Isnull('.',0) as float) as b 

Result

a       b
------------
123.66  NULL

SQLServer 2008 : ?

Joe Taras
  • 15,166
  • 7
  • 42
  • 55
Chanaka
  • 81
  • 1
  • 1
  • 2
  • 1
    You must write a UDF to replicate the behaviour of try_parse in Sql Server 2008, because this function has been added in Sql Server 2012 version – Joe Taras Jun 12 '15 at 06:48
  • 1
    If there was a simple way to achieve the same in 2008, they wouldn't have added it to 2012. – Damien_The_Unbeliever Jun 12 '15 at 07:07
  • You can try `ISNUMERIC` to check whether the data is numeric before casting, as [shown here](http://stackoverflow.com/questions/2000045/tsql-cast-string-to-integer-or-return-default-value) `CASE WHEN ISNUMERIC(@text) = 1 THEN CAST(@text AS INT) ELSE NULL END` – Panagiotis Kanavos Jun 12 '15 at 07:36
  • 2
    @PanagiotisKanavos - `ISNUMERIC` answers a question nobody has ever asked - "can this string be converted to *any* of the numeric types, I don't care which?" - so `ISNUMERIC` returning 1 does *not* imply that a cast to a *specific* numeric type will succeed. – Damien_The_Unbeliever Jun 12 '15 at 08:07
  • @Damien_The_Unbeliever no, another check is needed. As you said, it isn't simple but you do need to use a workaround with SQL Server 2008. In fact, the linked question has several attempts to fine-tune this. The safest solution would be to avoid casting and use numeric columns, making the conversion during loading. A UDF with a `float.TryParse` call would be the second safest. Anything else, you have to deal with edge cases. Eg, OLEDB and .NET can parse `"123-"` to `-123` but T-SQL can't – Panagiotis Kanavos Jun 12 '15 at 08:16
  • I would consider writing a CLR function that does what `try-parse` does in 2012, rather than trying to reproduce it in T-SQL. – Vladimir Baranov Jun 12 '15 at 09:04
  • Check it if is useful for you. http://stackoverflow.com/questions/30796861/try-parse-in-sql-server-2008/41432934#41432934 – JotaPardo Jan 04 '17 at 14:54

3 Answers3

7

TRY_PARSE does two things - parse text using a specific culture and return NULL if the cast fails. In SQL Server 2008 you can emulate part of this functionality using the ISNUMERIC function, as shown here:

select 
    CASE
        WHEN ISNUMERIC(@input)=1 and LEFT(@input,1) LIKE'[0-9]' THEN 
            CAST(@input as float)  
        ELSE 
            NULL 
    END,

ISNUMERIC will return 1 even for . or '.5' though, causing the cast to fail. This is covered by the second check LEFT(@input,1) LIKE'[0-9]'

You can create a scalar function so you don't have to type all this each time you want to cast a value:

CREATE FUNCTION try_parse_float(@input varchar(20)) 
returns float
AS
begin
    declare @result float;
    select @result=CASE
                       WHEN ISNUMERIC(@input)=1 and LEFT(@input,1) LIKE'[0-9]' 
                           THEN CAST(@input as float)  
                       ELSE NULL 
                    END;
    return @result;
end

So you can write

SELECT dbo.try_parse_float('123,4'), try_parse_float('.')

-----   ----
123.4   NULL

EDIT

The safest and fastest option would be simply to avoid storing numeric values as text in the database, making sure the data is parsed during loading instead. For example, .NET and OLEDB providers can parse "123-" to -123 while T-SQL can't.

It's also much easier to handle custom formats during loading, eg with a float.TryParseExact call before saving form data to the database, or with a C# Script component in an SSIS ETL script.

Community
  • 1
  • 1
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
2

I wrote a useful scalar function to simulate the TRY_CAST function of SQL SERVER 2012 in SQL Server 2008.

dbo.TRY_CAST(Expression, Data_Type, ReturnValueIfErrorCast)

The two main differences with TRY_CAST Function fo SQL Server 2012 are that you must pass 3 parameters and you must additionally perform an explicit CONVERT or CAST to the field. However, it is still very useful because it allows you to return a default value if CAST is not performed correctly.

FUNCTION CODE:

DECLARE @strSQL NVARCHAR(1000)
IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[TRY_CAST]'))
    BEGIN
        SET @strSQL = 'CREATE FUNCTION [dbo].[TRY_CAST] () RETURNS INT AS BEGIN RETURN 0 END'
        EXEC sys.sp_executesql @strSQL
    END

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

/*
------------------------------------------------------------------------------------------------------------------------
    Description:    
                    Syntax 
                    ---------------
                    dbo.TRY_CAST(Expression, Data_Type, ReturnValueIfErrorCast)
                    +---------------------------+-----------------------+
                    |   Expression              |   VARCHAR(8000)       |
                    +---------------------------+-----------------------+
                    |   Data_Type               |   VARCHAR(8000)       |
                    +---------------------------+-----------------------+
                    |   ReturnValueIfErrorCast  |   SQL_VARIANT = NULL  |
                    +---------------------------+-----------------------+
                    Arguments
                    ---------------
                    expression
                    The value to be cast. Any valid expression.
                    Data_Type
                    The data type into which to cast expression.
                    ReturnValueIfErrorCast
                    Value returned if cast fails or is not supported. Required. Set the DEFAULT value by default.
                    Return Type
                    ----------------
                    Returns value cast to SQL_VARIANT type if the cast succeeds; otherwise, returns null if the parameter @pReturnValueIfErrorCast is set to DEFAULT, 
                    or that the user indicates.
                    Remarks
                    ----------------
                    dbo.TRY_CAST function simulates the TRY_CAST function reserved of SQL SERVER 2012 for using in SQL SERVER 2008. 
                    dbo.TRY_CAST function takes the value passed to it and tries to convert it to the specified Data_Type. 
                    If the cast succeeds, dbo.TRY_CAST returns the value as SQL_VARIANT type; if the cast doesn´t succees, null is returned if the parameter @pReturnValueIfErrorCast is set to DEFAULT. 
                    If the Data_Type is unsupported will return @pReturnValueIfErrorCast.
                    dbo.TRY_CAST function requires user make an explicit CAST or CONVERT in ANY statements.
                    This version of dbo.TRY_CAST only supports CAST for INT, DATE, NUMERIC and BIT types.

                    Examples
                    ====================================================================================================

                    --A. Test TRY_CAST function returns null
                        SELECT   
                            CASE WHEN dbo.TRY_CAST('6666666166666212', 'INT', DEFAULT) IS NULL   
                            THEN 'Cast failed'  
                            ELSE 'Cast succeeded'  
                        END AS Result; 
                    GO
                    --B. Error Cast With User Value
                        SELECT   
                            dbo.TRY_CAST('2147483648', 'INT', DEFAULT) AS [Error Cast With DEFAULT],
                            dbo.TRY_CAST('2147483648', 'INT', -1) AS [Error Cast With User Value],
                            dbo.TRY_CAST('2147483648', 'INT', NULL) AS [Error Cast With User NULL Value]; 
                        GO 
                    --C. Additional CAST or CONVERT required in any assignment statement
                        DECLARE @IntegerVariable AS INT
                        SET @IntegerVariable = CAST(dbo.TRY_CAST(123, 'INT', DEFAULT) AS INT)
                        SELECT @IntegerVariable
                        GO 
                        IF OBJECT_ID('tempdb..#temp') IS NOT NULL
                            DROP TABLE #temp
                        CREATE TABLE #temp (
                            Id INT IDENTITY
                            , FieldNumeric NUMERIC(3, 1)
                            )
                        INSERT INTO dbo.#temp (FieldNumeric)
                        SELECT CAST(dbo.TRY_CAST(12.3, 'NUMERIC(3,1)', 0) AS NUMERIC(3, 1));--Need explicit CAST on INSERT statements
                        SELECT *
                        FROM #temp
                        DROP TABLE #temp

                        GO 
                    --D. Supports CAST for INT, DATE, NUMERIC and BIT types.
                        SELECT dbo.TRY_CAST(2147483648, 'INT', 0) AS [Cast failed]
                            , dbo.TRY_CAST(2147483647, 'INT', 0) AS [Cast succeeded]
                            , SQL_VARIANT_PROPERTY(dbo.TRY_CAST(212, 'INT', 0), 'BaseType') AS [BaseType];
                        SELECT dbo.TRY_CAST('AAAA0101', 'DATE', DEFAULT) AS [Cast failed]
                            , dbo.TRY_CAST('20160101', 'DATE', DEFAULT) AS [Cast succeeded]
                            , SQL_VARIANT_PROPERTY(dbo.TRY_CAST('2016-01-01', 'DATE', DEFAULT), 'BaseType') AS [BaseType];
                        SELECT dbo.TRY_CAST(1.23, 'NUMERIC(3,1)', DEFAULT) AS [Cast failed]
                            , dbo.TRY_CAST(12.3, 'NUMERIC(3,1)', DEFAULT) AS [Cast succeeded]
                            , SQL_VARIANT_PROPERTY(dbo.TRY_CAST(12.3, 'NUMERIC(3,1)', DEFAULT), 'BaseType') AS [BaseType];
                        SELECT dbo.TRY_CAST('A', 'BIT', DEFAULT) AS [Cast failed]
                            , dbo.TRY_CAST(1, 'BIT', DEFAULT) AS [Cast succeeded]
                            , SQL_VARIANT_PROPERTY(dbo.TRY_CAST('123', 'BIT', DEFAULT), 'BaseType') AS [BaseType];
                        GO 
                    --E. B. TRY_CAST return NULL on unsupported data_types

                        SELECT dbo.TRY_CAST(4, 'xml', DEFAULT) AS [unsupported];  

                        GO  
                    ====================================================================================================
    Responsible:    Javier Pardo
    Date:           diciembre 29/2016
    WB tests:       Javier Pardo 
------------------------------------------------------------------------------------------------------------------------
*/

ALTER FUNCTION dbo.TRY_CAST
(
    @pExpression AS VARCHAR(8000),
    @pData_Type AS VARCHAR(8000),
    @pReturnValueIfErrorCast AS SQL_VARIANT = NULL
)
RETURNS SQL_VARIANT
AS
BEGIN
    --------------------------------------------------------------------------------
    --  INT 
    --------------------------------------------------------------------------------

    IF @pData_Type = 'INT'
    BEGIN
        IF ISNUMERIC(@pExpression) = 1
        BEGIN
            DECLARE @pExpressionINT AS FLOAT = CAST(@pExpression AS FLOAT)

            IF @pExpressionINT BETWEEN - 2147483648.0 AND 2147483647.0
            BEGIN
                RETURN CAST(@pExpressionINT as INT)
            END
            ELSE
            BEGIN
                RETURN @pReturnValueIfErrorCast
            END --FIN IF @pExpressionINT BETWEEN - 2147483648.0 AND 2147483647.0
        END
        ELSE
        BEGIN
            RETURN @pReturnValueIfErrorCast
        END -- FIN IF ISNUMERIC(@pExpression) = 1
    END -- FIN IF @pData_Type = 'INT'

    --------------------------------------------------------------------------------
    --  DATE    
    --------------------------------------------------------------------------------

    IF @pData_Type = 'DATE'
    BEGIN
        IF ISDATE(@pExpression) = 1
        BEGIN
            DECLARE @pExpressionDATE AS DATE = cast(@pExpression AS DATE)

            RETURN cast(@pExpressionDATE as DATE)
        END
        ELSE 
        BEGIN
            RETURN @pReturnValueIfErrorCast
        END --FIN IF ISDATE(@pExpression) = 1
    END --FIN IF @pData_Type = 'DATE'

    --------------------------------------------------------------------------------
    --  NUMERIC 
    --------------------------------------------------------------------------------

    IF @pData_Type LIKE 'NUMERIC%'
    BEGIN

        IF ISNUMERIC(@pExpression) = 1
        BEGIN

            DECLARE @TotalDigitsOfType AS INT = SUBSTRING(@pData_Type,CHARINDEX('(',@pData_Type)+1,  CHARINDEX(',',@pData_Type) - CHARINDEX('(',@pData_Type) - 1)
                , @TotalDecimalsOfType AS INT = SUBSTRING(@pData_Type,CHARINDEX(',',@pData_Type)+1,  CHARINDEX(')',@pData_Type) - CHARINDEX(',',@pData_Type) - 1)
                , @TotalDigitsOfValue AS INT 
                , @TotalDecimalsOfValue AS INT 
                , @TotalWholeDigitsOfType AS INT 
                , @TotalWholeDigitsOfValue AS INT 

            SET @pExpression = REPLACE(@pExpression, ',','.')

            SET @TotalDigitsOfValue = LEN(REPLACE(@pExpression, '.',''))
            SET @TotalDecimalsOfValue = CASE Charindex('.', @pExpression)
                                        WHEN 0
                                            THEN 0
                                        ELSE Len(Cast(Cast(Reverse(CONVERT(VARCHAR(50), @pExpression, 128)) AS FLOAT) AS BIGINT))
                                        END 
            SET @TotalWholeDigitsOfType = @TotalDigitsOfType - @TotalDecimalsOfType
            SET @TotalWholeDigitsOfValue = @TotalDigitsOfValue - @TotalDecimalsOfValue

            -- The total digits can not be greater than the p part of NUMERIC (p, s)
            -- The total of decimals can not be greater than the part s of NUMERIC (p, s)
            -- The total digits of the whole part can not be greater than the subtraction between p and s
            IF (@TotalDigitsOfValue <= @TotalDigitsOfType) AND (@TotalDecimalsOfValue <= @TotalDecimalsOfType) AND (@TotalWholeDigitsOfValue <= @TotalWholeDigitsOfType)
            BEGIN
                DECLARE @pExpressionNUMERIC AS FLOAT
                SET @pExpressionNUMERIC = CAST (ROUND(@pExpression, @TotalDecimalsOfValue) AS FLOAT) 

                RETURN @pExpressionNUMERIC --Returns type FLOAT
            END 
            else
            BEGIN
                RETURN @pReturnValueIfErrorCast
            END-- FIN IF (@TotalDigitisOfValue <= @TotalDigits) AND (@TotalDecimalsOfValue <= @TotalDecimals) 

        END
        ELSE 
        BEGIN
            RETURN @pReturnValueIfErrorCast
        END --FIN IF ISNUMERIC(@pExpression) = 1
    END --IF @pData_Type LIKE 'NUMERIC%'

    --------------------------------------------------------------------------------
    --  BIT 
    --------------------------------------------------------------------------------

    IF @pData_Type LIKE 'BIT'
    BEGIN
        IF ISNUMERIC(@pExpression) = 1
        BEGIN
            RETURN CAST(@pExpression AS BIT) 
        END
        ELSE 
        BEGIN
            RETURN @pReturnValueIfErrorCast
        END --FIN IF ISNUMERIC(@pExpression) = 1
    END --IF @pData_Type LIKE 'BIT'


    --------------------------------------------------------------------------------
    --  FLOAT   
    --------------------------------------------------------------------------------

    IF @pData_Type LIKE 'FLOAT'
    BEGIN
        IF ISNUMERIC(REPLACE(REPLACE(@pExpression, CHAR(13), ''), CHAR(10), '')) = 1
        BEGIN

            RETURN CAST(@pExpression AS FLOAT) 
        END
        ELSE 
        BEGIN

            IF REPLACE(@pExpression, CHAR(13), '') = '' --Only white spaces are replaced, not new lines
            BEGIN
                RETURN 0
            END
            ELSE 
            BEGIN
                RETURN @pReturnValueIfErrorCast
            END --IF REPLACE(@pExpression, CHAR(13), '') = '' 

        END --FIN IF ISNUMERIC(@pExpression) = 1
    END --IF @pData_Type LIKE 'FLOAT'

    --------------------------------------------------------------------------------
    --  Any other unsupported data type will return NULL or the value assigned by the user to @pReturnValueIfErrorCast  
    --------------------------------------------------------------------------------

    RETURN @pReturnValueIfErrorCast

END

For now only supports the data types INT, DATE, NUMERIC, BIT and FLOAT. You can find the last versión of this code in the next link below and we help each other to improve it. TRY_CAST Function for SQL Server 2008 https://gist.github.com/jotapardo/800881eba8c5072eb8d99ce6eb74c8bb

JotaPardo
  • 817
  • 9
  • 27
-1

You can use this codesnippet which should do the thing your looking for. It shouldn't be a big deal for you to add this to a select on a bigger table. Just replace the variable with the code column.

Here is the example:

DECLARE @string nvarchar(255)
SET @string = 'Hali€hHalo'

SELECT Substring(
          @string,
          PATINDEX('%[0-9.]%',@string),
          PATINDEX('%[^0-9.]%',
                Substring(
                      @string,
                      PATINDEX(
                            '%[0-9.]%',
                            @string
                      ),
                      LEN(@string)
                )
          )-1
     )
GO

Result: Empty string, but this can be replaced with a CASE WHEN ... IS NULL THEN NULL ELSE ... END if needed to a NULL.

The The same thing with a variable with a number.

DECLARE @string nvarchar(255)
SET @string = 'Hali123.10€hHalo'

SELECT Substring(
          @string,
          PATINDEX('%[0-9.]%',@string),
          PATINDEX('%[^0-9.]%',
                Substring(
                      @string,
                      PATINDEX(
                            '%[0-9.]%',
                            @string
                      ),
                      LEN(@string)
                )
          )-1
     )
GO

Results in: 123.10

Best regards, Ionic

Ionic
  • 3,884
  • 1
  • 12
  • 33
  • `TRY_PARSE` is a bit more than a comfort function, which is evident by the *discomfort* caused by the complex code needed to replicate part of its functionality. What if there are thousand separators? What if eg the decimal separator is a comma, as it is in many European countries? It would probably be simpler/safer (but slower) to try a `cast` and catch the failure, eg in a scalar function than try to replicate the behavior of `TRY_PARSE`. Only a CLR UDF can actually offer the same functionality in SQL Server 2008 – Panagiotis Kanavos Jun 12 '15 at 07:29
  • Well if you have SQL Server 2008 and not SQL Server 2012 then try your TRY_PARSE. But it won't work. And some companies won't be able to upgrade to SQL Server 2012 as there are very bad licence models. This way you just can't use try_parse and need a work around - as the questioner does. It's quite easy to check the separator and adapt probably. You can also use this statement just on rows which can't be converted. – Ionic Jun 12 '15 at 07:31
  • You can use a UDF. You can create your own another scalar function to handle casting failures. `TRY_PARSE` though is not a convenience function – Panagiotis Kanavos Jun 12 '15 at 07:32
  • You don't work on big server systems, don't you? UDF's are extremly bad in performance on big tables. Mostly the query plan escalateds (even on 2012 and 2014) on huge tables with a UDF. – Ionic Jun 12 '15 at 07:36
  • I dunno, I currently work on a rather small datamart, just 20GB but it does have several fact tables with million-to-million joins. Which is why I don't like `PATINDEX` or UDFs, and *don't* consider `TRY_PARSE` a convenience function. Esp. when you have 20-30 measures to display. You'd also be surprise how much better UDFs scale compared to complex scripts, when the proper indexes exist - the optimizer seems to cache the results of deterministic UDFs. – Panagiotis Kanavos Jun 12 '15 at 07:49
  • Well I have many measures too, but on bigger tables (> 1TB / Table). Yes Patindex could be replaced with Charindex which won't really impact in that case. But I'm using SQL Server 2008 on those systems as 2012 would be too expensive (per virt. cor license instead of dim license). But anyway - ok ok Try_Parse can be more than a comfort function. But I'm not so excited about that than obviously you are - as I needed to resolve such issues years before 2012 was in CTP-State. I already removed the "comfort function" passage. But the code works for the thing the questioner needed. ;-) – Ionic Jun 12 '15 at 07:57