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.
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
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))
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
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)))
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.
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 :
chaque cas est une déclaration WHEN distincte dans le code ci-dessus.
Voici quelques exemples de conversion:
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)
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.
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.
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'