2013-07-30 7 views
29

Я использую SQL Server 2008 R2 и имею столбец VARCHAR, который я хочу преобразовать в DECIMAL(28,10), используя CONVERT. Но многие из этих строк плохо отформатированы, поэтому их невозможно разобрать на число. В этом случае я просто хочу пропустить их, установив результат в 0 или NULL.Try_Convert для SQL Server 2008 R2

Я знаю, что в SQL Server 2012 есть новый оператор (TRY_CONVERT()), который был бы удобен.

Возможно ли это в 2008 году или я должен подождать, пока мы не перейдем к следующей версии SQL 2012?

EDIT

ISNUMERIC() К сожалению, не является надежным в этом случае. Я попытался

ISNUMERIC(myCol) = 1 

Это возвращает верно для строк, которые CONVERT не в состоянии преобразовать в DECIMAL.

+0

Duplicate: http://stackoverflow.com/a/2000061/2524304 – FSou1

+0

Хотите ли вы преобразовать столбец постоянно в таблицу или просто в результирующий набор? –

+0

Возможный дубликат [TSQL - вставить строку в целое или вернуть значение по умолчанию] (http://stackoverflow.com/questions/2000045/tsql-cast-string-to-integer-or-return-default-value) –

ответ

3

Наконец узнал, как сделать это с помощью от SO и Google.

Оператор обновления:

UPDATE PriceTerm 
SET PercentAddition = CONVERT(decimal(28,10), RTRIM(LTRIM(REPLACE(REPLACE(REPLACE(AdditionalDescription,'%',''), ',','.'), '&', '')))) 
WHERE AdditionalDescription LIKE '%[%]%' AND 
dbo.isreallynumeric(RTRIM(LTRIM(REPLACE(REPLACE(REPLACE(AdditionalDescription,'%',''), ',','.'), '&', '')))) = 1 AND 
PercentAddition = 0 

поиск Сначала я для% полукокса как большую часть времени, которое используется в качестве маркера для percentvalue. Но есть и случайные другие применения. Оказалось, что ISNUMERIC не был надежным в моем случае.

Что действительно имеет значение, так это вызов хранимой процедуры isreallynumeric от here.

Так

CREATE FUNCTION dbo.isReallyNumeric 
( 
    @num VARCHAR(64) 
) 
RETURNS BIT 
BEGIN 
    IF LEFT(@num, 1) = '-' 
     SET @num = SUBSTRING(@num, 2, LEN(@num)) 

    DECLARE @pos TINYINT 

    SET @pos = 1 + LEN(@num) - CHARINDEX('.', REVERSE(@num)) 

    RETURN CASE 
    WHEN PATINDEX('%[^0-9.-]%', @num) = 0 
     AND @num NOT IN ('.', '-', '+', '^') 
     AND LEN(@num)>0 
     AND @num NOT LIKE '%-%' 
     AND 
     ( 
      ((@pos = LEN(@num)+1) 
      OR @pos = CHARINDEX('.', @num)) 
     ) 
    THEN 
     1 
    ELSE 
    0 
    END 
END 
GO 
+0

Это, похоже, работает в том смысле, что оно не даст вам ложного позитива (по крайней мере, мне еще не удалось его сломать). Тем не менее, он * может * дать вам ложные отрицания в некоторых случаях: '' 1'' (пробел перед номером), '' '' '(пробел после номера),' '+ 1''. Разумеется, в зависимости от ваших требований они могут быть ложными негативами. Я называю их только потому, что 1) результаты вашей функции на них несовместимы с 'ISNUMERIC', а 2) эти случаи будут конвертировать в числовой тип без проблем. –

3

Вы можете написать свой собственный парсер в C# и использовать SQLCLR, используя, например, Decimal.Parse(). Do не попытка использовать ISNUMERIC, заведомо incorrect (возвращает TRUE для строк, которые не имеют CAST).

+6

'ISNUMERIC' неверен, он просто отвечает на вопрос, который никто на самом деле не хочет получить ответ (« Могу ли я преобразовать эту строку в * любой * числовых типов данных? ») –

5

Поскольку это должно быть постоянное изменение, я бы сделал это как двухэтапный процесс - сначала удалите недопустимый текст, а затем преобразуйте столбец.

Чтобы удалить неверный текст, я бы что-то вроде:

UPDATE [Table] 
SET [Column] = NULL 
WHERE [Column] LIKE '%[^0-9.]%' or 
LEN([Column]) - LEN(REPLACE([Column],'.','')) > 1 or 
LEN([Column]) > 28 

После этого будет сделано, все, что остается должен быть конвертируемым только путем изменения определения столбца

ALTER TABLE [Table] ALTER COLUMN [Column] decimal(28,10) 
+1

К сожалению, я не хочу писать в столбце varchar. Даже если данные невозможно разобрать, они могут содержать заметки и т. Д., Которые я не хочу удалять. Я знаю, что дизайн очень неправилен для хранения заметок и чисел в одном столбце ... –

+0

@RolandBengtsson - ах, поэтому вы после (скажем) вычисленного столбца, который содержит значение, преобразованное в десятичное, если это возможно конверсия? Я изначально прочитал ваш вопрос, заменив существующий столбец –

38

При использовании XML в SQL Server вы можете попробуйте, чтобы применить к типу данных и получить нулевые значения, при которых выполняется сбой.

declare @T table 
(
    Col varchar(50) 
) 

insert into @T values 
('1'), 
('1.1'), 
('1,1'), 
('1a') 

select cast('' as xml).value('sql:column("Col") cast as xs:decimal ?', 
          'decimal(28,10)') as Col 
from @T 

Результат:

Col 
------------- 
1.0000000000 
1.1000000000 
NULL 
NULL 
+4

[Кажется устойчивым] (http://sqlfiddle.com/#!3/d41d8/17895). –

+0

Полезно! Спасибо. – DaveBoltman

3

Я написал полезную скалярную функцию для имитации функции TRY_CAST в SQL Server 2012 в SQL Server 2008.

dbo.TRY_CAST(Expression, Data_Type, ReturnValueIfErrorCast) 

Два основных отличия от TRY_CAST функции Fo SQL Server 2012 является то, что вы должны пройти 3 параметры и вы необходимо дополнительно выполнить явное CONVERT или CAST в поле. Однако он по-прежнему очень полезен, поскольку он позволяет вернуть значение по умолчанию , если CAST выполняется неправильно.

ФУНКЦИЯ КОД:

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 
------------------------------------------------------------------------------------------------------------------------ 
    Update by:  Javier Eduardo Pardo Moreno 
    Date:   febrero 16/2017 
    Id update:  JEPM20170216 
    Description: Fix ISNUMERIC function makes it unreliable. SELECT dbo.TRY_CAST('+', 'INT', 0) will yield Msg 8114, 
        Level 16, State 5, Line 16 Error converting data type varchar to float. 
        ISNUMERIC() function treats few more characters as numeric, like: – (minus), + (plus), $ (dollar), \ (back slash), (.)dot and (,)comma 
        Collaborator aperiooculus (http://stackoverflow.com/users/3083382/aperiooculus) 

        Fix dbo.TRY_CAST('2013/09/20', 'datetime', DEFAULT) for supporting DATETIME format 

    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 AND @pExpression NOT IN ('-','+','$','.',',','\') --JEPM20170216 
     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 IN ('DATE','DATETIME') 
    BEGIN 
     IF ISDATE(@pExpression) = 1 
     BEGIN 

      DECLARE @pExpressionDATE AS DATETIME = cast(@pExpression AS DATETIME) 

      IF @pData_Type = 'DATE' 
      BEGIN 
       RETURN cast(@pExpressionDATE as DATE) 
      END 

      IF @pData_Type = 'DATETIME' 
      BEGIN 
       RETURN cast(@pExpressionDATE as DATETIME) 
      END 

     END 
     ELSE 
     BEGIN 

      DECLARE @pExpressionDATEReplaced AS VARCHAR(50) = REPLACE(REPLACE(REPLACE(@pExpression,'\',''),'/',''),'-','') 

      IF ISDATE(@pExpressionDATEReplaced) = 1 
      BEGIN 
       IF @pData_Type = 'DATE' 
       BEGIN 
        RETURN cast(@pExpressionDATEReplaced as DATE) 
       END 

       IF @pData_Type = 'DATETIME' 
       BEGIN 
        RETURN cast(@pExpressionDATEReplaced as DATETIME) 
       END 

      END 
      ELSE 
      BEGIN 
       RETURN @pReturnValueIfErrorCast 
      END 
     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 

На данный момент поддерживает только типы данных INT, DATE, DATETIME, NUMERIC, BIT и FLOAT. Вы можете найти последнюю версию этого кода в следующей ссылке ниже, и мы помогаем друг другу улучшать ее. TRY_CAST Функция для SQL Server 2008https://gist.github.com/jotapardo/800881eba8c5072eb8d99ce6eb74c8bb

+2

Зависимость вашей функции от функции ISNUMERIC делает ее ненадежной. 'SELECT dbo.TRY_CAST ('+', 'INT', 0)' будет выдавать _Msg 8114, уровень 16, состояние 5, строка 16 Ошибка преобразования типа данных varchar в float._ – AperioOculus

+1

@AperioOculus Я высоко ценю вашу помощь. Я обновил код! https://gist.github.com/jotapardo/800881eba8c5072eb8d99ce6eb74c8bb – JotaPardo

-2

SQL SERVER 2008 R2 имеет функцию TRY_CAST, но если вы конфигурацию вашего DB с совместимостью с 2005 вы обнаружили, что SQL SERVER не распознайте его.

Я использовал функцию, которая создает @jotapardo, но переименуйте TRY_CAST2, потому что SS в этом случае распознает функцию.

+1

Ваш ответ будет улучшен с примера или двух. –

+0

TRY_CAST недоступен в SQL Server 2008 R2. https://docs.microsoft.com/en-us/sql/t-sql/functions/try-cast-transact-sql –

Смежные вопросы