I have a table like this (simplified):
CREATE TABLE #table (Id INT, Field NVARCHAR(MAX))
INSERT INTO #table VALUES (1, 'SomeText')
INSERT INTO #table VALUES (2, '1234')
For some reasons I need to query this table and get the sum of Field if it is numeric and return '' if it is not. I tried it like this:
SELECT CASE WHEN ISNUMERIC(Field) = 1 THEN SUM(CONVERT(MONEY, Field)) ELSE '' END
FROM #table
GROUP BY Field
But this query leads to the following exception:
Cannot convert a char value to money. The char value has incorrect syntax.
I even changed the ELSE case from '' to 0 but I still get the same message.
Why do I get the exception? As far as I know, SUM(...) should not be executed when ISNUMERIC(Field) returns 0.
Select sum(case when ISNUMERIC(Field)=1 then cast(field as money) else 0 end)
from #table
Group By Field
Returns
(No column name)
1234.00
0.00
Working with mixed datatypes can be a real pain. Where possible, consider table designs that avoid this. To further complicate matters, IsNumeric does not always return what you might expect.
Filtering out the non-numerics before aggregating is one way to go:
SELECT
SUM(CONVERT(MONEY, Field))
FROM
#table
WHERE
ISNUMERIC(Field) = 1
GROUP BY
Field
;
Related
This is for SQL Server 2012 database...
I'm reading a varchar data column from a table, and depending on user-selected options the data could be either alphanumeric or numeric. I need to sort by this column so I'm trying to use a case statement, but it doesn't seem to be working. Below is a simplified example of what I'm doing, but as you can see, it's falling through to the Else of the case statement in both scenarios...any ideas on what I'm doing wrong?
Select '1st Grade Math' topic Into #temp
Declare #rptView int
Set #rptView = 1
Select Case #rptView
When 1 Then topic
Else cast(topic as int)
End
From #temp
Order by Case #rptView
When 1 Then topic
Else cast(topic as int)
End
Select Case
When #rptView = 1 Then topic
Else cast(topic as int)
End
From #temp
Order by Case #rptView
When 1 Then topic
Else cast(topic as int)
End
drop table #temp
Consider the following example based on your table:
Select Case 1 When 1 Then topic
Else 5
End
From #temp
It also fails with the following error:
Conversion failed when converting the varchar value '1st Grade Math' to data type int.
Why? Because every expression must have a well-defined data type. SQL Server deduces that the type of your first column is int, since the ELSE clause contains an int. Thus, it tries to convert topic to int as well, which fails.
In other words: You can't do it like that. The field in your result set can be varchar or int, not both.
Adding few more examples which may help..
declare #a int=1
declare #b varchar='b'
--this works
select
case when #a=1 then #a else #b end
--this also works
select
case when #a=2 then #b else #b end
--this fails
select
case when #a=1 then #b else #a end
--this fails
select
case when #a=2 then #a else #b end
Why ..?Because of data type precedence ,SQL tries to convert everything to type with higher precedence
I have a column which has varchars like "172.54". I am trying to insert into another table where this columns datatype is float. I am getting error saying can not convert datatype varchar to float. So I do
SELECT *
FROM TBL
WHERE ISNUMERIC(COLNAME) <> 1
And I get no results. But casting is not working. So I look and I have empty strings in that column. So I try to
SELECT *
FROM TBL
WHERE COLNAME = ''
And also every other different amount of spaces.
I ultimately just want to convert the empty strings to null
Also len(colname) = 1
declare #test varchar(10) = ' ' -- any number of spaces is equivalent to ''
select try_convert( float, #test ) as floatval -- '' gives you 0
select case when #test = '' then NULL else try_convert( float, #test ) end as floatval -- value '' returns NULL instead of 0
I guess you column has some characters other than numeric data. Also empty string will be converted to zero it will not throw error.
To filter Numeric data use
COLNAME not like '%[^0-9]%'
Try something like this
insert into tablename (col1,col2)
SELECT col1,col2 FROM TBL
COLNAME not like '%[^0-9]%'
I am stuck on converting a varchar column UserID to INT. I know, please don't ask why this UserID column was not created as INT initially, long story.
So I tried this, but it doesn't work. and give me an error:
select CAST(userID AS int) from audit
Error:
Conversion failed when converting the varchar value
'1581............................................................................................................................' to data type int.
I did select len(userID) from audit and it returns 128 characters, which are not spaces.
I tried to detect ASCII characters for those trailing after the ID number and ASCII value = 0.
I have also tried LTRIM, RTRIM, and replace char(0) with '', but does not work.
The only way it works when I tell the fixed number of character like this below, but UserID is not always 4 characters.
select CAST(LEFT(userID, 4) AS int) from audit
You could try updating the table to get rid of these characters:
UPDATE dbo.[audit]
SET UserID = REPLACE(UserID, CHAR(0), '')
WHERE CHARINDEX(CHAR(0), UserID) > 0;
But then you'll also need to fix whatever is putting this bad data into the table in the first place. In the meantime perhaps try:
SELECT CONVERT(INT, REPLACE(UserID, CHAR(0), ''))
FROM dbo.[audit];
But that is not a long term solution. Fix the data (and the data type while you're at it). If you can't fix the data type immediately, then you can quickly find the culprit by adding a check constraint:
ALTER TABLE dbo.[audit]
ADD CONSTRAINT do_not_allow_stupid_data
CHECK (CHARINDEX(CHAR(0), UserID) = 0);
EDIT
Ok, so that is definitely a 4-digit integer followed by six instances of CHAR(0). And the workaround I posted definitely works for me:
DECLARE #foo TABLE(UserID VARCHAR(32));
INSERT #foo SELECT 0x31353831000000000000;
-- this succeeds:
SELECT CONVERT(INT, REPLACE(UserID, CHAR(0), '')) FROM #foo;
-- this fails:
SELECT CONVERT(INT, UserID) FROM #foo;
Please confirm that this code on its own (well, the first SELECT, anyway) works for you. If it does then the error you are getting is from a different non-numeric character in a different row (and if it doesn't then perhaps you have a build where a particular bug hasn't been fixed). To try and narrow it down you can take random values from the following query and then loop through the characters:
SELECT UserID, CONVERT(VARBINARY(32), UserID)
FROM dbo.[audit]
WHERE UserID LIKE '%[^0-9]%';
So take a random row, and then paste the output into a query like this:
DECLARE #x VARCHAR(32), #i INT;
SET #x = CONVERT(VARCHAR(32), 0x...); -- paste the value here
SET #i = 1;
WHILE #i <= LEN(#x)
BEGIN
PRINT RTRIM(#i) + ' = ' + RTRIM(ASCII(SUBSTRING(#x, #i, 1)))
SET #i = #i + 1;
END
This may take some trial and error before you encounter a row that fails for some other reason than CHAR(0) - since you can't really filter out the rows that contain CHAR(0) because they could contain CHAR(0) and CHAR(something else). For all we know you have values in the table like:
SELECT '15' + CHAR(9) + '23' + CHAR(0);
...which also can't be converted to an integer, whether you've replaced CHAR(0) or not.
I know you don't want to hear it, but I am really glad this is painful for people, because now they have more war stories to push back when people make very poor decisions about data types.
This question has got 91,000 views so perhaps many people are looking for a more generic solution to the issue in the title "error converting varchar to INT"
If you are on SQL Server 2012+ one way of handling this invalid data is to use TRY_CAST
SELECT TRY_CAST (userID AS INT)
FROM audit
On previous versions you could use
SELECT CASE
WHEN ISNUMERIC(RTRIM(userID) + '.0e0') = 1
AND LEN(userID) <= 11
THEN CAST(userID AS INT)
END
FROM audit
Both return NULL if the value cannot be cast.
In the specific case that you have in your question with known bad values I would use the following however.
CAST(REPLACE(userID COLLATE Latin1_General_Bin, CHAR(0),'') AS INT)
Trying to replace the null character is often problematic except if using a binary collation.
This is more for someone Searching for a result, than the original post-er. This worked for me...
declare #value varchar(max) = 'sad';
select sum(cast(iif(isnumeric(#value) = 1, #value, 0) as bigint));
returns 0
declare #value varchar(max) = '3';
select sum(cast(iif(isnumeric(#value) = 1, #value, 0) as bigint));
returns 3
I would try triming the number to see what you get:
select len(rtrim(ltrim(userid))) from audit
if that return the correct value then just do:
select convert(int, rtrim(ltrim(userid))) from audit
if that doesn't return the correct value then I would do a replace to remove the empty space:
select convert(int, replace(userid, char(0), '')) from audit
This is how I solved the problem in my case:
First of all I made sure the column I need to convert to integer doesn't contain any spaces:
update data set col1 = TRIM(col1)
I also checked whether the column only contains numeric digits.
You can check it by:
select * from data where col1 like '%[^0-9]%' order by col1
If any nonnumeric values are present, you can save them to another table and remove them from the table you are working on.
select * into nonnumeric_data from data where col1 like '%[^0-9]%'
delete from data where col1 like '%[^0-9]%'
Problems with my data were the cases above. So after fixing them, I created a bigint variable and set the values of the varchar column to the integer column I created.
alter table data add int_col1 bigint
update data set int_col1 = CAST(col1 AS VARCHAR)
This worked for me, hope you find it useful as well.
I have the following Query:
create table #Result (Reward varchar(40), Value MONEY);
insert #Result exec GetCurrentCycleQualifierStatusByAccountId #AccountId=76011;
with cteFirstResults as
(select Reward, round(Value,2) as Value from #Result where Reward like '%Balance%'),
cteSecondResults as
(select Reward, convert(INTEGER, Value) as Value from #Result where Reward NOT like '%Balance%')
select * from cteFirstResults
UNION ALL
select * from cteSecondResults;
drop table #Result;
When running a select * individually against each "cte" table, I get the results I want.
But when run all together, I get something like:
Reward Value
------ -----
Daily Balance 4709.00
Value A 1.00
Value B 9.00
I want the Value A/Value B data to show without any decimal values as they do when running a select against the table directly. How do I combine the two queries into one to show this data correctly?
Round(value,0) does nothing.
I can not change the sproc from which I'm gathering the data, but I can make the temp table any way I like.
Thanks,
Jason
The solution:
create table #Result (Reward varchar(40), Value MONEY);
insert #Result exec GetCurrentCycleQualifierStatusByAccountId #AccountId=76011;
With cteFirstResults as
(
Select Reward, Value
From #Result
Where Reward like '%Balance%'
)
, cteSecondResults as
(
Select Reward, cast(Value as int) as Value
From #Result
Where Reward Not like '%Balance%'
)
Select Reward, Cast( Value As varchar(max)) As Value
From cteFirstResults
Union All
Select Reward, Cast( value As varchar(max)) as Value
From cteSecondResults;
drop table #Result;
The problem is the integers are being implicitly cast to decimal because they are being represented in a decimal column.
If you just want the values displayed, cast them both to strings.
CREATE TABLE #test
(
test decimal(9,2)
)
CREATE TABLE #test2
(
test int
)
INSERT INTO #test (test)
SELECT 1.25 UNION ALL
SELECT 172813.99
INSERT INTO #test2 (test)
SELECT 134 UNION ALL
SELECT 41
SELECT CAST(test as varchar(max)) FROM #Test
UNION ALL
SELECT CAST(test as varchar(max)) FROM #Test2
Results:
1.25
172813.99
134
41
In a union SQL Server will assume that the datatype of the second select is the same as the first and where it can convert them, do so. You will have to beat it at its own game and do you own conversion
In the final select (the one with the Union) massage the data in both cases to be a string. Format the output as desired before converting it to a string.
EACH Sql Server column can only have 1 data type.
round(money,4) returns a money(4)*
convert(int) returns an int
Based on data type precedence
#13. money
#16. int
The resultant column is money(4). Therefore ALL values in the column will be formatted using money(4).
Your options are
convert(float) across both - downside: a value of 1.1 is shown as 1.1, not 1.10
convert(varchar) - you have stated you don't want this, and it changes the data type to the receiving program
FWIW
Round(value,0) does nothing.
It does do something. It burns CPU rounding an int value to another int value (of the same value). Incidentally, the resultant type is (still) "int". This has nothing to do with formatting.
REF:
declare #m money
set #m = 12.3233
select SQL_VARIANT_PROPERTY(round(#m,2), 'basetype') -- money
select SQL_VARIANT_PROPERTY(round(#m,2), 'precision') -- 19
select SQL_VARIANT_PROPERTY(round(#m,2), 'scale') -- 4
I want to write a query to see if a category field is within a certain range. The problem is the field can contain null, text or numeric text prefixed by '#' character.
Does anybody know of SQL that will strip the non numerics and allow me to do the following check.
category > 1 and category < 100
Here is a sample of what the field category can contain:
#230.1
#200
Null
text
I am using SQL Server 2000
I appears astander's solution is functional. You should consider however a few points:
If the table holds more than a few thousand rows, and if this type of query is to be run frequently, it may be beneficial to introduce a new column to hold the numeric value of the category (if available, null otherwise). This will be more efficient for two reasons: as written, SQL needs to scan the table, completely, i.e.it needs to review every single row; also it needs to perform all these conversion which are a bit expensive, CPU-wise.
You may consider introducing some extra logic to normalize the category field. For example to get rid of common leading or trailing characters etc. This will "rescue" several category codes which would otherwise translate to null wouldn't be able to participate in these filters.
Try something like this
DECLARE #Table TABLE(
Val VARCHAR(200)
)
INSERT INTO #Table (Val) SELECT '#230.1'
INSERT INTO #Table (Val) SELECT '#200'
INSERT INTO #Table (Val) SELECT '210'
INSERT INTO #Table (Val) SELECT NULL
INSERT INTO #Table (Val) SELECT 'text'
SELECT *
FROM (
SELECT CASE
WHEN ISNUMERIC(Val) = 1
THEN CAST(Val AS FLOAT)
WHEN LEN(Val) > 1 AND ISNUMERIC(RIGHT(Val,LEN(Val)-1)) = 1
THEN CAST(RIGHT(Val,LEN(Val)-1) AS FLOAT)
END Num
FROM #Table
WHERE Val IS NOT NULL
AND (
ISNUMERIC(Val) = 1
OR (
LEN(Val) > 1
AND ISNUMERIC(RIGHT(Val,LEN(Val)-1)) = 1
)
)
) Numbers
WHERE Num BETWEEN 205 AND 230