TSQL - replace isnumeric = 0 - tsql

I have a select statement and in that select statement I have a few columns on which I perform basic calculations (e.g. [Col1] * 3.14). However, occasionally I run into non-numeric values and when that happens, the whole stored procedure fails because of one row.
I've thought about using a WHERE ISNUMERIC(Col1) <> 0, but then I would be excluding information in the other columns.
Is there a way in TSQL to somehow replace all stings with NULL or 0??

Something like...
SELECT blah1, blah2, blah3
CASE WHEN ISNUMERIC(Col1) = 1 THEN [Col1] * 3.14 ELSE NULL END as whatever
FROM your_table
A case can also be made that..
The non-numeric values should be converted to numeric or NULL if that's what's expected in the column, and
If numbers are expected then the column should be a numeric data type in the first place and not a character data type, which allows for these types of errors.

I prefer Try_Cast:
SELECT
someValue
,TRY_CAST(someValue as int) * 3.14 AS TRY_CAST_to_int
,TRY_CAST(someValue as decimal) * 3.14 AS TRY_CAST_to_decimal
,IIF(ISNUMERIC(someValue) = 1, someValue, null) * 3.14 as IIF_IS_NUMERIC
FROM (values
( 'asdf'),
( '2' ),
( '1.55')
) s(someValue)

ISNUMERIC is a terrible way to do this, as there are far too many things that identify as NUMERIC which are not able to be multiplied by a non-MONEY data type.
https://www.brentozar.com/archive/2018/02/fifteen-things-hate-isnumeric/
This fails miserably, as '-' is a numeric...
DECLARE #example TABLE (numerics VARCHAR(10));
INSERT INTO #example VALUES ('-')
SELECT CASE WHEN ISNUMERIC(numerics) = 1 THEN numerics * 3.14 ELSE NULL END
FROM #example;
Try TRY_CAST instead (albeit amend your DECIMAL precision to suit your needs):
DECLARE #example TABLE (numerics VARCHAR(10));
INSERT INTO #example VALUES ('-')
SELECT TRY_CAST(numerics AS decimal(10,2)) * 3.14 FROM #example;

trycast will test for a specfic type
declare #T table (num varchar(20));
insert into #T values ('12'), ('3.14'), ('5.6E12'), ('$120'), ('-'), (''), ('cc'), ('aa'), ('bb'), ('1/5');
select t.num, ISNUMERIC(t.num) as isnumeric
, isnull(TRY_CONVERT(smallmoney, t.num), 0) as smallmoney
, TRY_CONVERT(float, t.num) as float
, TRY_CONVERT(decimal(18,4), t.num) as decimal
, isnull(TRY_CONVERT(smallmoney, t.num), TRY_CONVERT(float, t.num)) as mix
from #T t
num isnumeric smallmoney float decimal
-------------------- ----------- --------------------- ---------------------- ---------------------------------------
12 1 12.00 12 12.0000
3.14 1 3.14 3.14 3.1400
5.6E12 1 0.00 5600000000000 NULL
$120 1 120.00 NULL NULL
- 1 0.00 NULL NULL
0 0.00 0 NULL
cc 0 0.00 NULL NULL
aa 0 0.00 NULL NULL
bb 0 0.00 NULL NULL
1/5 0 0.00 NULL NULL
interesting the last still fails

Related

Replace calculated negative values with 0 in PostgreSQL

I have a table my_table:
case_id first_created last_paid submitted_time
3456 2021-01-27 2021-01-29 2021-01-26 21:34:36.566023+00:00
7891 2021-08-02 2021-09-16 2022-10-26 19:49:14.135585+00:00
1245 2021-09-13 None 2022-10-31 02:03:59.620348+00:00
9073 None None 2021-09-12 10:25:30.845687+00:00
6891 2021-08-03 2021-09-17 None
I created 2 new variables:
select *,
first_created-coalesce(submitted_time::date) as create_duration,
last_paid-coalesce(submitted_time::date) as paid_duration
from my_table;
The output:
case_id first_created last_paid submitted_time create_duration paid_duration
3456 2021-01-27 2021-01-29 2021-01-26 21:34:36.566023+00:00 1 3
7891 2021-08-02 2021-09-16 2022-10-26 19:49:14.135585+00:00 -450 -405
1245 2021-09-13 null 2022-10-31 02:03:59.620348+00:00 -412 null
9073 None None 2021-09-12 10:25:30.845687+00:00 null null
6891 2021-08-03 2021-09-17 null null null
My question is how can I replace new variables' value with 0, if it is smaller than 0?
The ideal output should look like:
case_id first_created last_paid submitted_time create_duration paid_duration
3456 2021-01-27 2021-01-29 2021-01-26 21:34:36.566023+00:00 1 3
7891 2021-08-02 2021-09-16 2022-10-26 19:49:14.135585+00:00 0 0
1245 2021-09-13 null 2022-10-31 02:03:59.620348+00:00 0 null
9073 None None 2021-09-12 10:25:30.845687+00:00 null null
6891 2021-08-03 2021-09-17 null null null
My code:
select *,
first_created-coalesce(submitted_time::date) as create_duration,
last_paid-coalesce(submitted_time::date) as paid_duration,
case
when create_duration < 0 THEN 0
else create_duration
end as QuantityText
from my_table
greatest(yourvalue,0)
Given yourvalue lower than 0, 0 will be returned as the greater value:
select *,
greatest(0,first_created-coalesce(submitted_time::date)) as create_duration,
greatest(0,last_paid-coalesce(submitted_time::date)) as paid_duration
from my_table
This will also change null values to 0.
case statement
If you wish to keep the null results, you can resort to a regular case statement. In order to alias your calculation you'll have to put it in a subquery or a cte:
select *,
case when create_duration<0 then 0 else create_duration end as create_duration_0,
case when paid_duration<0 then 0 else paid_duration end as paid_duration_0
from (
select *,
first_created-coalesce(submitted_time::date) as create_duration,
last_paid-coalesce(submitted_time::date) as paid_duration
from my_table ) as subquery;
(n+abs(n))/2
If you sum a number with its absolute value, then divide by two (average them out), you'll get that same number if it was positive, or you'll get zero if it was negative because a negative number will always balance itself out with its absolute value:
(-1+abs(-1))/2 = (-1+1)/2 = 0/2 = 0
( 1+abs( 1))/2 = ( 1+1)/2 = 2/2 = 1
select *,
(create_duration + abs(create_duration)) / 2 as create_duration_0,
(paid_duration + abs(paid_duration) ) / 2 as paid_duration_0
from (
select *,
first_created-coalesce(submitted_time::date) as create_duration,
last_paid-coalesce(submitted_time::date) as paid_duration
from my_table ) as subquery;
Which according to this demo, is slightly faster than case and about as fast as greatest(), without affecting null values.
Note that select * pulls everything from below, so you'll end up seeing create_duration as well as create_duration_0 - you can get rid of it by listing your desired output columns explicitly in the outer query. You can also rewrite it without subquery/cte, repeating the calculation, which will look ugly but in most cases planner will notice the repetition and make evaluate it only once
select *,
case when first_created-coalesce(submitted_time::date) < 0
then 0
else first_created-coalesce(submitted_time::date)
end as create_duration,
(abs(last_paid-coalesce(submitted_time::date))+last_paid-coalesce(submitted_time::date))/2 as paid_duration
from my_table ) as subquery;
or using a scalar subquery
select *,
(select case when a<0 then 0 else a end
from (select first_created-coalesce(submitted_time::date)) as alias(a) )
as create_duration,
(select case when a<0 then 0 else a end
from (select last_paid-coalesce(submitted_time::date)) as alias(a) )
as paid_duration
from my_table ) as subquery;
Neither of which help with anything in this case but are good to know.
If you are planning on attaching your SQL Database to an ASP.NET app, you could create a c# script to query your database, and use the following:
Parameters.AddWithValue(‘Data You want to change’ ‘0’);
However, if your not using your SQL database with a ASP.NET app, this will not work.

How to divide COUNT(CASE ) by COUNT()

I am trying to calculate a % by doing the following
(COUNT(CASE
WHEN col1 > 0
THEN my_id
ELSE null
END)/COUNT(my_id))*100 AS my_percent
The column, my_percent, which is output is a column of all zeros.
Individually both COUNTs return non-negative integers as expected, almost all are > 0.
COUNT(CASE
WHEN col1 > 0
THEN my_id
ELSE null
END) AS count_case
COUNT(my_id) AS simple_count
Why does the % function return zeros rather than positive numbers? How can I modify the code to give the expected output (positive numbers not zeros)?
count has a bigint return value, and PostgreSQL uses integer division that truncates fractional digits:
SELECT 7 / 3;
?column?
══════════
2
(1 row)
To avoid that, cast to double precision or numeric:
CAST(count(CASE WHEN col1 > 0 THEN my_id ELSE null END) AS double precision)
/
CAST(COUNT(my_id) AS double precision)
* 100 AS my_percent

Update Numeric field without Decimal point and Zeros

I am trying to update a numeric field. But the field can not have zeros after decimal point. But the table that I am trying to pull values contain data as 87.00,90.00,100.00 etc.. How do I update without decimal point and zeros?
Example :percentage is a numeric field.
Update value available 100.00,90.00 etc.
update table1
set percent =(tmpercent as integer)
from table2
where table2.custid=table1.custoid;
;
gives error.
Table1:
CustID Percent(numeric)
1 90
2 80
Table2:
CustomID tmpPercent(varchar)
1 87.00
2 90.00
i often use typecasting ::FLOAT::NUMERIC to get rid of extra fraction zeros of numerics
or you can use TRUNC() function to force fraction truncation
try
update table1
set percent = tmpercent::FLOAT::NUMERIC
from table2
where table2.custid=table1.custoid;
or
update table1
set percent = TRUNC(tmpercent::NUMERIC)
from table2
where table2.custid=table1.custoid;
It is going to depend on how the numeric field is specified in the table. From here:
https://www.postgresql.org/docs/current/datatype-numeric.html
We use the following terms below: The precision of a numeric is the total count of significant digits in the whole number, that is, the number of digits to both sides of the decimal point. The scale of a numeric is the count of decimal digits in the fractional part, to the right of the decimal point. So the number 23.5141 has a precision of 6 and a scale of 4. Integers can be considered to have a scale of zero.
NUMERIC(precision, scale)
So if your field has a scale > 0 then you will see 0 to the right of the decimal point, unless you set scale to 0. As example:
create table numeric_test (num_fld numeric(5,2), num_fld_0 numeric(5,0));
insert into numeric_test (num_fld, num_fld_0) values ('90.0', '90.0');
select * from numeric_test ;
num_fld | num_fld_0
---------+-----------
90.00 | 90
insert into numeric_test (num_fld, num_fld_0) values ('90.5', '90.5');
select * from numeric_test ;
num_fld | num_fld_0
---------+-----------
90.00 | 90
90.50 | 91
insert into numeric_test (num_fld, num_fld_0) values ('90.0'::float, '90.0'::float);
select * from numeric_test ;
num_fld | num_fld_0
---------+-----------
90.00 | 90
90.50 | 91
90.00 | 90
Using scale 0 means you have basically created an integer field. If you have a scale > 0 then you are going to get decimals in the field.

LIKE operator in Postgresql

Is it possible using LIKE operator to write a query to find values that residing in a numeric datatype column?
For example,
Table sample
ID | VALUE(numeric)
1 | 1.00
2 | 2.00
select * from sample where VALUE LIKE '1%'
Please clear my doubt...
If I understood you correctly then following could be a solution for you
consider this sample
create table num12 (id int,VALUE numeric);
insert into num12 values (1,1.00),(2,2.00);
insert into num12 values (3,1.50),(4,1.90);
the table look like
id value
-- -----
1 1.00
2 2.00
3 1.50
4 1.90
select * from num12 where value =1
will return only single row,
id value
-- -----
1 1.00
If you want to select all 1s then use(I guess you're trying to find a solution for this)
select * from num12 where trunc(value) =1
result:
id value
-- -----
1 1.00
3 1.50
4 1.90
Is it possible using LIKE operator to write a query to find values
that residing in a numeric datatype column?
Answer: Yes
You can use select * from num12 where value::text like '1%'
Note : It yields same result as shown above but its not a good method

Convert Varchar to Ascii

I'm trying to convert the contents of a VARCHAR field to be unique number that can be easily referenced by a 3rd party.
How can I convert a varchar to the ascii string equivalent? In TSQL? The ASCII() function converts a single character but what can I do to convert an entire string?
I've tried using
CAST(ISNULL(ASCII(Substring(RTRIM(LTRIM(PrimaryContactRegion)),1,1)),'')AS VARCHAR(3))
+ CAST(ISNULL(ASCII(Substring(RTRIM(LTRIM(PrimaryContactRegion)),2,1)),'')AS VARCHAR(3))
....but this is tedious, stupid looking, and just doesn't really work if I had long strings. Or if it is better how would I do the same thing in SSRS?
try something like this:
DECLARE #YourString varchar(500)
SELECT #YourString='Hello World!'
;WITH AllNumbers AS
(
SELECT 1 AS Number
UNION ALL
SELECT Number+1
FROM AllNumbers
WHERE Number<LEN(#YourString)
)
SELECT
(SELECT
ASCII(SUBSTRING(#YourString,Number,1))
FROM AllNumbers
ORDER BY Number
FOR XML PATH(''), TYPE
).value('.','varchar(max)') AS NewValue
--OPTION (MAXRECURSION 500) --<<needed if you have a string longer than 100
OUTPUT:
NewValue
---------------------------------------
72101108108111328711111410810033
(1 row(s) affected)
just to test it out:
;WITH AllNumbers AS
(
SELECT 1 AS Number
UNION ALL
SELECT Number+1
FROM AllNumbers
WHERE Number<LEN(#YourString)
)
SELECT SUBSTRING(#YourString,Number,1),ASCII(SUBSTRING(#YourString,Number,1)),* FROM AllNumbers
OUTPUT:
Number
---- ----------- -----------
H 72 1
e 101 2
l 108 3
l 108 4
o 111 5
32 6
W 87 7
o 111 8
r 114 9
l 108 10
d 100 11
! 33 12
(12 row(s) affected)
Also, you might want to use this:
RIGHT('000'+CONVERT(varchar(max),ASCII(SUBSTRING(#YourString,Number,1))),3)
to force all ASCII values into 3 digits, I'm not sure if this is necessary based on your usage or not.
Output using 3 digits per character:
NewValue
-------------------------------------
072101108108111032087111114108100033
(1 row(s) affected)
Well, I think that a solution to this will be very slow, but i guess that you could do something like this:
DECLARE #count INT, #string VARCHAR(100), #ascii VARCHAR(MAX)
SET #count = 1
SET #string = 'put your string here'
SET #ascii = ''
WHILE #count <= DATALENGTH(#string)
BEGIN
SELECT #ascii = #ascii + '&#' + ASCII(SUBSTRING(#string, #count, 1)) + ';'
SET #count = #count + 1
END
SET #ascii = LEFT(#ascii,LEN(#ascii)-1)
SELECT #ascii
I'm not in a pc with a database engine, so i can't really test this code. If it works, then you can create a UDF based on this.