web-dev-qa-db-fra.com

VARCHAR à DECIMAL

Je veux convertir une colonne varchar(max) en decimal(10,4)

Lorsque j'essaie d'utiliser cast ou convert, je reçois une exception de dépassement arithmétique. Le problème est que les données stockées dans la colonne varchar peuvent contenir différentes précisions et différentes échelles. Par exemple, 123456789.1234567 ', 1.12345678 ou 123456.1234.

Pour des valeurs telles que 123456.1234, la conversion se fait sans problème, mais pour d’autres valeurs, j’ai des problèmes.

Toute aide serait appréciée.

21
Biju Thomas

Mis en œuvre à l'aide de la fonction personnalisée. Cela vérifiera si la valeur de la chaîne peut être convertie en décimal en toute sécurité

CREATE FUNCTION [dbo].[TryParseAsDecimal]
(
    @Value      NVARCHAR(4000)
    ,@Precision INT
    ,@Scale     INT
)

RETURNS BIT
AS
BEGIN

    IF(ISNUMERIC(@Value) =0) BEGIN
        RETURN CAST(0 AS BIT)
    END
    SELECT @Value = REPLACE(@Value,',','') --Removes the comma

    --This function validates only the first part eg '1234567.8901111111'
    --It validates only the values before the '.' ie '1234567.'
    DECLARE @Index          INT
    DECLARE @Part1Length    INT 
    DECLARE @Part1          VARCHAR(4000)   

    SELECT @Index = CHARINDEX('.', @Value, 0)
    IF (@Index>0) BEGIN
        --If decimal places, extract the left part only and cast it to avoid leading zeros (eg.'0000000001' => '1')
        SELECT @Part1 =LEFT(@Value, @Index-1);
        SELECT @Part1=SUBSTRING(@Part1, PATINDEX('%[^0]%', @Part1+'.'), LEN(@Part1));
        SELECT @Part1Length = LEN(@Part1);
    END
    ELSE BEGIN
        SELECT @Part1 =CAST(@Value AS DECIMAL);
        SELECT @Part1Length= LEN(@Part1)
    END 

    IF (@Part1Length > (@Precision-@Scale)) BEGIN
        RETURN CAST(0 AS BIT)
    END

    RETURN CAST(1 AS BIT)

END
1
Biju Thomas

Après des tests, j'ai constaté que ce n'était pas la décimale qui causait le problème, mais bien la précision (10).

Cela ne fonctionne pas: erreur de débordement arithmétique lors de la conversion de varchar en type de données numérique.

DECLARE @TestConvert VARCHAR(MAX) = '123456789.12343594'

SELECT CAST(@TestConvert AS DECIMAL(10, 4))

Cela a fonctionné

DECLARE @TestConvert VARCHAR(MAX) = '123456789.12343594'

SELECT CAST(@TestConvert AS DECIMAL(18, 4))
21
codingbiz

Il vous manque le fait que 6.999,50 n'est pas une décimale valide. Vous ne pouvez sûrement pas avoir une virgule et un point décimal dans une valeur décimale? Quel nombre est-il censé être?

En supposant que votre locale spécifie. en tant que regroupement et en tant que séparateur décimal: Pour supprimer les chiffres du regroupement:

SELECT CONVERT (décimal (11,2), REMPLACER ('6.999,50', '.', '')) 

donnera 6999,50 sous forme décimale

4
user2714866

Mon explication est dans le code. :)

DECLARE @TestConvert VARCHAR(MAX) = '123456789.1234567'
BEGIN TRY
    SELECT CAST(@TestConvert AS DECIMAL(10, 4))
END TRY
BEGIN CATCH
    SELECT 'The reason you get the message "' + ERROR_MESSAGE() + '" is because DECIMAL(10, 4) only allows for 4 numbers after the decimal.'
END CATCH

-- Here's one way to truncate the string to a castable value.
SELECT CAST(LEFT(@TestConvert, (CHARINDEX('.', @TestConvert, 1) + 4)) AS DECIMAL(14, 4))

-- If you noticed, I changed it to DECIMAL(14, 4) instead of DECIMAL(10, 4) That's because this number has 14 digits, as proven below.
-- Read this for a better explanation as to what precision, scale and length mean: http://msdn.Microsoft.com/en-us/library/ms190476(v=sql.105).aspx
SELECT LEN(LEFT(@TestConvert, (CHARINDEX('.', @TestConvert, 1) + 4)))
4
Jeremy Pridemore

Vous allez devoir tronquer les valeurs vous-même en tant que chaînes avant de les placer dans cette colonne.

Sinon, si vous souhaitez plus de décimales, vous devrez modifier votre déclaration de la colonne décimale.

3
dodexahedron

Je suis venu avec la solution suivante:

SELECT [Str], DecimalParsed = CASE 
WHEN ISNUMERIC([Str]) = 1 AND CHARINDEX('.', [Str])=0 AND LEN(REPLACE(REPLACE([Str], '-', ''), '+', '')) < 29 THEN CONVERT(decimal(38,10), [Str])
WHEN ISNUMERIC([Str]) = 1 AND (CHARINDEX('.', [Str])!=0 AND CHARINDEX('.', REPLACE(REPLACE([Str], '-', ''), '+', ''))<=29) THEN 
    CONVERT(decimal(38,10), 
            CASE WHEN LEN([Str]) - LEN(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE([Str], '0', ''), '1', ''), '2', ''), '3', ''), '4', ''), '5', ''), '6', ''), '7', ''), '8', ''), '9', '')) <= 38 
                 THEN [Str] 
                 ELSE SUBSTRING([Str], 1, 38 + LEN(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE([Str], '0', ''), '1', ''), '2', ''), '3', ''), '4', ''), '5', ''), '6', ''), '7', ''), '8', ''), '9', ''))) END)
ELSE NULL END
FROM TestStrToDecimal

Je sais que cela ressemble à un excès et probablement, mais cela fonctionne pour moi (vérifié les nombres positifs, négatifs, grands et petits, de précision et d'échelle différentes - tout est converti en decimal(38,10) ou NULL). 

Il est codé en dur en type decimal(38,10). Si vous avez besoin d'une précision différente, changez les constantes dans le code (38, 10, 29).

Comment ça marche? Le résultat est :

  • si la conversion est simple, sans dépassement de capacité ni perte de précision (par exemple, 123 ou 123,456), elle le convertit simplement.
  • si le nombre n'est pas trop grand, mais comporte trop de chiffres après le point décimal (par exemple, 123.1234567890123456789012345678901234567890), il coupe les chiffres en excès à la fin en ne conservant que 38 premiers chiffres.
  • si le nombre est trop grand et ne peut pas être converti en décimal sans débordement (par exemple 9876543210987654321098765432109876543210), la valeur NULL est renvoyée.

chaque cas est une déclaration WHEN distincte dans le code ci-dessus.

Voici quelques exemples de conversion: enter image description here

3

Vous n'avez toujours pas expliqué pourquoi vous ne pouvez pas utiliser un type de données Float, voici donc un exemple:

DECLARE @StringVal varchar(50)

SET @StringVal = '123456789.1234567'
SELECT @StringVal, CAST(@StringVal AS FLOAT)

SET @StringVal = '1.12345678'
SELECT @StringVal, CAST(@StringVal AS FLOAT)

SET @StringVal = '123456.1234'
SELECT @StringVal, CAST(@StringVal AS FLOAT)
2
Mark Kram

Votre problème majeur n’est pas le contenu à droite de la virgule, mais le contenu à gauche. Les deux valeurs dans votre déclaration de type sont précision et échelle. 

A partir de MSDN: "La précision correspond au nombre de chiffres d’un nombre. Échelle est Le nombre de chiffres situés à droite du séparateur décimal d’un nombre . échelle de 2. "

Si vous spécifiez (10, 4), cela signifie que vous ne pouvez enregistrer que 6 chiffres à gauche de la décimale, ou un nombre maximal de 999999.9999. Tout ce qui est plus grand que cela provoquera un débordement.

2
Bill

Je sais que c'est une vieille question, mais Bill semble être le seul à avoir réellement "expliqué" le problème. Tout le monde semble proposer des solutions complexes à une utilisation abusive d'une déclaration. 

"Les deux valeurs de votre déclaration de type sont précision et échelle."

...

"Si vous spécifiez (10, 4), cela signifie que vous ne pouvez enregistrer que 6 chiffres à gauche de la décimale, ou un nombre maximal de 999999.9999. Toute valeur supérieure à celle-ci provoquera un débordement."

Donc, si vous déclarez DECIMAL(10,4), vous pouvez avoir un total de 10 nombres, avec 4 / venant après la virgule décimale. so 123456.1234 a 10 chiffres, 4 après le point décimal. Cela entrera dans les paramètres de DECIMAL(10,4). 1234567.1234 émettra une erreur. il y a 11 chiffres à insérer dans un espace de 10 chiffres, et 4 chiffres DOIVENT être utilisés APRÈS le point décimal. Découper un chiffre du côté gauche de la décimale n'est pas une option. Si vos 11 caractères étaient 123456.12345, cela ne renverrait pas d'erreur, car le rognage (arrondi) à la fin d'une valeur décimale est acceptable.

Lorsque vous déclarez des décimales, essayez toujours de déclarer le maximum que votre colonne utilisera de manière réaliste et le nombre maximal de décimales que vous souhaitez voir. Donc, si votre colonne n'affiche jamais que des valeurs d'un maximum de 1 million et que vous vous souciez uniquement des deux premières décimales, déclarez-la en tant que DECIMAL(9,2). Cela vous donnera un nombre maximal de 9 999 999,99 avant qu'une erreur ne soit générée. .

Comprendre le problème avant d'essayer de le résoudre vous assurera de choisir le correctif adapté à votre situation et vous aidera à comprendre la raison pour laquelle le correctif est nécessaire/fonctionne.

Encore une fois, je sais que j'ai cinq ans de retard à la fête. Cependant, mes deux sous sur une solution pour cela, (à en juger par vos commentaires que la colonne est déjà définie comme DECIMAL(10,4) et ne peut pas être modifiée) La meilleure façon de le faire serait deux étapes. Vérifiez que votre décimale n’est pas à plus de 10 points de distance, puis coupez à 10 chiffres.

CASE WHEN CHARINDEX('.',CONVERT(VARCHAR(50),[columnName]))>10 THEN 'DealWithIt'
ELSE LEFT(CONVERT(VARCHAR(50),[columnName]),10) 
END AS [10PointDecimalString]

La raison pour laquelle j'ai laissé ceci sous forme de chaîne est que vous pouvez gérer les valeurs de plus de 10 chiffres situées à gauche de la décimale.

Mais c'est un début.

1
DDuffy

Dans le cas où vous devez contourner le résultat, pas tronquer, vous pouvez utiliser ceci:

sélectionnez convertir (décimal (38,4), arrondi (convertir (décimal (38,10), '123456789.1234567'), 4))

Cela renvoie: '123456789.1235' pour '123456789.1234567' '123456789.1234' pour '123456789.1234467'

0
aleb