SQL Server 2008: Varchar Conversion to Numeric Data Overflow, Probably Because Some Are Ranges - sql

I'm working on a query with a varchar column called ALCOHOL_OZ_PER_WK. Part of the query includes:
where e.ALCOHOL_OZ_PER_WK >= 14
and get the errors:
Arithmetic overflow error converting varchar to data type numeric.
and:
Error converting data type varchar to numeric.
Looking into the values actually stored in the column, the largest look close to 100, but some of the entries are ranges:
9 - 12
1.5 - 2.5
I'd like to get the upper limit (or maybe the midpoint of the range) from rows with entries like this and have it be the value being compared to 14.
What would be the (or an) easy way to do this?
As always, thank you!

Your DB is obviously result of some survey, and it seems to contain the original survey data. The usual way is to run this through an ECCD (Extract, Clean, Conform, Deliver) process and store clean and standardized data into a separate database (maybe a warehouse) which can then be used for analytics and reporting.
If you have SSIS use data profiling task to get an idea of types of strings you have in there. The Column Pattern Profile reports a set of regular expressions on the string column, so you will get an idea of what's inside those strings. If you do not have SSIS, you can use eobjects DataCleaner to do the same.
If you can not spare a new database or at least a new table -- at minimum add a numeric column to this table and then extract numeric values form those strings into the new column. You may want to use "something else" (SSIS, Pentaho Kettle, Python, VB, C#) to do this -- in general T-SQL in not very good at string processing.
My guess is that this is not the only column that has garbage inside, so any analysis that you may run on this may be worthless.
And if you still think that the ranges are the only problem, this example may help:
First some data
DECLARE #myTable TABLE (
AlUnits varchar(10)
) ;
INSERT INTO #myTable
(AlUnits )
VALUES ( '10' )
, ( '15' )
, ( '20' )
, ( '7 - 12' )
, ( '3 - 5' )
;
The query splits records into two groups, numeric and not numeric -- assumed ranges.
;
WITH is_num
AS ( SELECT CAST(AlUnits AS decimal(6, 2)) AS Units_LO
,CAST(AlUnits AS decimal(6, 2)) AS Units_HI
FROM #myTable
WHERE ISNUMERIC(AlUnits) = 1
),
is_not_num
AS ( SELECT CAST( RTRIM(LTRIM(LEFT(AlUnits,
CHARINDEX('-', AlUnits) - 1)))
AS decimal(6,2)) AS Units_LO
,CAST(RTRIM(LTRIM(RIGHT(AlUnits,
LEN(AlUnits)
- CHARINDEX('-', AlUnits))))
AS decimal(6,2)) AS Units_HI
FROM #myTable
WHERE ISNUMERIC(AlUnits) = 0
)
SELECT Units_LO
,Units_HI
,CAST(( Units_LO + Units_HI ) / 2.0 AS decimal(6, 2)) AS Units_Avg
FROM is_num
UNION ALL
SELECT Units_LO
,Units_HI
,CAST(( Units_LO + Units_HI ) / 2.0 AS decimal(6, 2)) AS Units_Avg
FROM is_not_num ;
Returns:
Units_LO Units_HI Units_Avg
----------- ----------- ----------
10.00 10.00 10.00
15.00 15.00 15.00
20.00 20.00 20.00
7.00 12.00 9.50
3.00 5.00 4.00

Not sure about easy ways.
A proper way is to store the numbers in two columns, ALCOHOL_OZ_PER_WK_MIN and ALCOHOL_OZ_PER_WK_MAX.

As you say you need to calculate numeric values, which you can then use in your query.
Probably the easiest way is to use some simple logic to calculate the average or upper limit using string functions, and string to numeric functions.
If all you want is the upper limit, just get the characters after the '-' and use that.

"probably because some are ranges" - do you get that "range" is not a SQL Server Data type? You've got non-numeric data you're trying to convert into numeric data, and you've got a scalar value you're comparing to a non-scalar value.
This database has some issues.

Related

SQL Convert & Cast Nvarchar Time to a decimal

I'm working on a legacy database and need to parse info from one database to another, parsing it into the new database is easy enough but first I need to create the query to convert and cast the following in the legacy SQL Server database:
WorkedHours(NVARCHAR(10)) is in text format 07:30
I need to convert and cast this as a decimal ie 7.5
I have searched around for the answer to this but can not find anything that has worked, so thought I would put it out there to see if any of you has any ideas.
Edit - What I should of asked is. What is causing an error converting to an int from a character with a value of 0 when trying to trying to convert and cast a time to a decimal?
DATEDIFF(
MINUTE,
0,
CAST('07:30' AS TIME)
)
/
60.0
Works up to '23:59' only
EDIT:
Based on a comment elsewhere, you have some 'bad' values.
This may find them...
SELECT
*
FROM
yourTable
WHERE
TRY_CONVERT(TIME, worked_hours) IS NULL
And as such, this is a safer version of my expression....
DATEDIFF(
MINUTE,
0,
TRY_CONVERT(TIME, worked_hours)
)
/
60.0
(Returns NULL for values that failed to parse.)
There's no reason to pull out the date/time types. Just do some simple string parsing:
cast(left(right('0' + WorkedHours, 5), 2) as int)
+ cast(right(WorkedHours, 2) as int) / 60.00
This won't have any limitations on 24 hours or anything like that. It just assumes that you've got one or two digits before a colon and two digits after.
This should work in SQL Server and an example-string "1101:56" (1101h & 56 minutes) | in general from 0h to >24h:
-- Take all hours before ":" and all Minutes (2 digits) after ":" and convert it to decimal.
select convert(decimal,left('1101:56',CHARINDEX(':','1101:56')-1)) + ( convert(decimal,right('1101:56',2))/60 );
-- with column-placeholder "time_str_from_table"
select convert(decimal,left(time_str_from_table,CHARINDEX(':',time_str_from_table)-1)) + ( convert(decimal,right(time_str_from_table,2))/60 );
If the source table have NULL-Values, than use "ISNULL" with substitution-value "0.0":
-- with column-placeholder "time_str_from_table"
select isnull( ( convert(decimal,left(time_str_from_table,CHARINDEX(':',time_str_from_table)-1)) + ( convert(decimal,right(time_str_from_table,2))/60) ), 0.0);

Convert date to number data type

I am trying to convert date to number like below, not sure which function works better.
Database used is SQL Server.
Table details
create table test
(
id varchar(255),
call_date varchar(255)
);
insert into test('26203', '14-Aug-2020');
I need output as 4405726203 -- its concatenation of date (14-Aug-2014) + id (26203)
This is too long for a comment.
SQL Server allows you to convert a datetime to a float. That would be:
select cast(dte as float)
from (values (convert(datetime, '14-Aug-2020'))) v(dte)
However, the corresponding floating point value is 44055 (https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=d142a64db0872e7572eb4fbd6d5d5fe7). It is a bit of mystery what your intention is.
You could subtract 2, but that seems arbitrary. You could calculate the number of days since 1899-12-30. But that also seems arbitrary.
In any case, once you figure out how to convert the date to the number you want, just use concat() to combine the values.
I have found the solution:
convert(varchar,CAST(CONVERT(datetime,call_date) as bigint)) + id
Under the hood, a SQL Server DateTime is a tuple of 2 32-bit integers:
The first integer is a count of days since since the epoch, which for SQL Server is 1 January 1900
The second integer is a count of milliseconds since start of day (00:00:00.000). Except that the count ticks up in 3- or 4-milliscond increments. Microsoft only knows why that decision was made.
You can get the count of days since the epoch with
convert( int, convert( date, t.call_date ) )
[wrap that in convert(varchar, ... ) to turn it into a string]
Looks like your id is already a varchar, so you can say:
select compound_key = convert(varchar,
convert(int,
convert(date,
call_date
)
)
)
+ t.id
from test t
I would suggest padding both fields with leading zeros to a fixed length so as to avoid possible collisions (assuming you're trying to generate a key here). Signed 32-bit integer overflows a 2.1 billion-ish, so 9 digits for each field is sufficient.
This works
select concat(datediff(d, 0, cast(call_date as date)), id)
from
(values ('26203','14-Aug-2020')) v(id, call_date);
Results
4405526203

how to increase the max value keeping string lenght

I have a column numbers from 1 to 10000, they are stored like string with 8 characters, so number 1 is stored like 00000001 and 10000 is stored like 00010000.
I need to take the max for this column and increase by 1 in insert operations. but if I do:
Max(column)+1
I lost previous 0, so I have 10001 and not 00010001, how I can keep zeros?
You can use the FORMAT function to format numbers:
SELECT FORMAT(1, '00000000') -- 00000001
Having said that, you should be storing numbers inside the database as-is and format them in the application.
you could try using cast and replicate
replicate('0', 8 - CONVERT(varchar(8), max(CAST(column AS INT)) +1) ) + CONVERT(varchar(8), max(CAST(column AS INT)) +1)
anyway you should take a look for identity in sqlserver for manage autoincrement column automatically

Remove decimal using SQL query

I want to convert my decimal SQL query result in percent. Example I have a 0.295333 I want it to be 30% and if I have a 0.090036 I want it to be 9%.
This is what I have so far.
(100 * (sample1/ sample2) ) as 'Percent'
I also tried this one but the problem is result comes with ".00" and I don't know how to remove the decimal.
cast (ROUND(100 * (sample1 / sample2),0) As int ) as 'Percent'
Try with the below script..
cast (100 * Round((sample1 / sample2),2) As int ) as 'Percent'
So as some of the comments pointed out you may need to pay attention to your datatype if one or both of the original columns that you get your decimal from are integer.
One easy way of dealing with that is something like this:
ColA * ColB * 1.0 which will make sure that your integers are treated as decimals
So if you have SQL Server 2012+ you can use Format and not mess with rounding at all. Like this FORMAT(YourDecimal,'#%'), yep that simple.
;WITH cteValues AS (
SELECT 0.295333 as OriginalValue
UNION ALL
SELECT 0.090036 as OriginalValue
)
SELECT
OriginalValue
,FORMAT(OriginalValue,'#%') as PercentFormat
FROm
cteValues
If you are pre 2012 and do not have format an easy way is to round to the 100th then times by 100 and cast as int CAST(ROUND(YourDecimal,2) * 100 AS INT)
;WITH cteValues AS (
SELECT 0.295333 as OriginalValue
UNION ALL
SELECT 0.090036 as OriginalValue
)
SELECT
OriginalValue
,CAST(ROUND(OriginalValue,2) * 100 AS INT) as PercentInt
FROm
cteValues
Because an INT cannot by definition have decimal places, if you are receiving .00 with the method similar to this or the one you have tried, I would ask the following.
Are you combining (multiplying etc.) the value after casting with another column or value that may be decimal, numeric, or float?
Are you looking at the query results in a program outside of SSMS that could be formatting the results automatically, e.g. Excel, Access?
Address your assumptions first.
How does ROUND work? Does it guarantee return values and if so, how? What is the precedence of the two columns? Does Arithmetic operators influence the results and how?
I only know what I do not know, and any doubt is worth an investigation.
THE DIVIDEND OPERATOR
Since ROUND always returns the higher precedence, this is not the problem. It is in fact the divide operator ( / ) that may be transforming your values to an integer.
Always verify the variables are consistently of one datatype or CAST if either unsure or unable to guarantee (such as insufficiently formatted. I.e. DECIMAL(4,2) instead of required DECIMAL(5,3) ).
DECLARE #Sample1 INT
, #Sample2 DECIMAL(4,2);
SET #Sample1 = 50;
SET #Sample2 =83.11;
SELECT ROUND( 100 * #Sample1 / #Sample2 , 0 )
Returns properly 60.
SELECT ROUND( 100 * #Sample2 / #Sample1 , 0)
Incorrectly turns variables into integers before rounding.
The reason is that DIVIDE - MSDN in SQL may return the higher precedence, but any dividend that is an integer returns another integer.
UPDATE
This also explains why the decimal remains after ROUND...it is of higher precedence. You can add another cast to transform the non-INT datatype to the preferred format.
SELECT CAST( ROUND( <expression>, <Length>) AS INT)
Note that in answering your question I learned something myself! :)
Hope this helps.

Formatting an SQL numeric query result with an arbitrary number of decimal places

I have a database table with these two columns:
Amount: numeric (18,0)
DecimalPlaces: numeric (18,0)
This table can store amounts in various currencies, with the decimal place removed from the amount (I can't change this data model). For example, there might be two rows like this:
1290, 2 (This is £12.90, needs to appear as "12.90")
3400, 0 (This is 3400 Japanese Yen, needs to appear as "3400")
I need an SQL query for both Oracle and SQL Server that will format each amount with the correct number of decimal places, preserving any trailing zeroes as illustrated above. I can't use stored procedures, a reporting tool, or Excel.
Your problem is that there isn't an easy way to do this for both SQLServer and Oracle in one query.
The Correct way to do this for SQLServer is to use STR:
Select STR(Amount, 18, DecimalPlaces) from myTable;
The correct way to do this for Oracle is using to_char:
SELECT to_char (amount, '99999999999999.'||rpad('',DecimalPlaces, '0'))
from MyTable;
The queries presented by jms and Andrew won't work in an Oracle query because Oracle SQL uses LENGTH() not LEN(). And Oracle uses to_char() not Cast().
The best I've been able to come up with so far is:
select Amount/power(10, DecimalPlaces) from MyTable
But it doesn't do exactly what I want:
Oracle: the trailing zeroes are stripped, so US$15.00 looks like "15", not "15.00"
SQL Server: a whole lot of extra trailing zeroes are added, so $23.99 looks like "23.99000000000" instead of "23.99"
How about?
select 12345 amount, 2 decimalPlaces, substr( to_char( 12345 ), 1, length (to_char( 12345 ) ) - 2 )
|| '.' || substr( to_char( 12345 ), -2 ) result from dual
/
amount decimalPlaces result
---------- ------------- ------
12345 2 123.45
This is gross but worked for the current inputs on SQL server.
select
substring(
CAST(
CAST(
(amount * power(-0.100000000000000000,decimalPlaces*1.000000000000000000)) as numeric(36,18)
)as varchar(30)
)
,1,len(cast(amount as varchar(20))) + (CASE WHEN decimalPlaces = 0 THEN 0 ELSE 1 END ))
from
myTable
In SQL server you can :
select stuff(convert(varchar,amount) ,
len(convert(varchar,amount)) - DecimalPlaces - 1, 0, ".")
Martlark's answer for Oracle led me to this solution for SQL Server:
select
left(cast(Amount as varchar), len(cast(Amount as varchar)) - DecimalPlaces) +
left('.', DecimalPlaces) +
right(cast(OriginalCurrencyAmount as varchar), DecimalPlaces
) as FormattedAmount
from MyTable