case when value contains text or symbols (>, <) - postgresql

I'm trying to write case when statement for a particular field that has three patterns of values, which are normal numeric values (i.e. 0.6, 12.1), numeric values with symbols (i.e. > 14.0, < 1.2) and texts (i.e. canceled).
I'm trying to keep the numeric values (i.e. if 0.6 then leave it as 0.6), convert numeric values with symbols to numeric (i.e. if > 14.0 then convert it to 14.0), and exclude text values. Here is some example of the data, 'original' is the value I have currently and 'I want' is the desired output of case when.
row_id val (original) val(I want)
---------------------------------------
1 1.2 1.2
2 > 0.4 0.4
3 Canceled null
4 < 1.6 1.6
5 22.0 22.0
6 not done null
For the symbols (> or <) I could hardcode each distinct val (i.e. case when val = '> 0.2' then '0.2') but I'm hoping to learn if there is a more general way to approach it without writing when statement for each val.
For the text values, I tried isnumeric() but it doesn't work, it isn't even recognized as a function. I haven' found successful solution in PostgreSQL from other posts, would there be a way to classify text values and exclude it?

You can use the trim and replace functions to eliminate the characters < and > and leading and trailing spaces. Then a regular expression to validate the remaining characters form a legitimate numeric value. So something like:
with test (rnum, val) as
( values (1,' 1.2 ')
, (2,' > .4 ')
, (3,' Canceled ')
, (4,' < 1.6 ')
, (5,' 22.0 ')
, (6,' not done ')
)
select rnum, val
, case when res ~ '^([0-9]+\.?|^\.?)[0-9]*$' then res else null end res
from (select rnum, val, trim(replace(replace(val,'<',''),'>','')) res
from test
) r;
See additional example here
Admittedly this can be done with a single regular expression, but in order to deal with the additional 'valid' non numeric characters and spaces the expression gets complicated fast. And I am not sure the performance would be any better. Regular expressions tend to be slow, trunc and replace are pretty fast.

Related

Why am I getting an error that I cannot concat two different datatypes even after casting the fields datatype

I have a query in postgresql where I want to append a minus sign to the transactions.amount field when the transaction.type = 2 (which refers to withdrawals). I am trying to concat a minus sign and the transactions.amount field which is an int. I casted the transactions.amount field to a text/varchar but no matter what I still get the error, "PostgreSql Error: case types numeric and text cannot be matched"
Here is the query I am running,
SELECT CAST(CASE WHEN "IsVoided" IS TRUE THEN 0
WHEN "Transactions"."TransactionType" = 2
THEN CONCAT('-', CAST("Transactions"."Amount" AS TEXT))
ELSE "Transactions"."Amount" END AS Text) AS "TransAmount"
FROM "Transactions"
LEFT JOIN "DepositSources"
ON "Transactions"."DepositSourceId" =
"DepositSources"."DepositSourceId"
LEFT JOIN "WithdrawalSources"
ON "Transactions"."WithdrawalSourceId" =
"DepositSources"."DepositSourceId"
WHERE "Transactions"."FundId" = 4
AND "Transactions"."ReconciliationId" = 24
What's very perplexing is when i run the below query it works as expected,
SELECT CONCAT('-', CAST("Transactions"."Amount" AS TEXT)) FROM
"Transactions"
All branches of a CASE expression need to have the same type. In this case, you're stuck with making all branches text, because what follows THEN can only be text. Try this version:
CASE WHEN IsVoided IS TRUE
THEN '0'
WHEN Transactions.TransactionType = 2
THEN CONCAT('-', Transactions.Amount::text)
ELSE Transactions.Amount::text END AS TransAmount
Note that it is unusual to be using the logic you have in a CASE expression. Typically, you would just be checking the values of a single column, not multiple different columns.
Edit:
It appears that your call to CONCAT mainly serves to negative a value. Here is one more simple way to do this:
CASE WHEN IsVoided IS TRUE
THEN 0
WHEN Transactions.TransactionType = 2
THEN -1.0 * Transactions.Amount
ELSE Transactions.Amount END AS TransAmount
In this case, we can make the CASE expression just generate numeric output, which might be really what you are after.

Cast to int instead of decimal?

I have field that has up to 9 comma separated values each of which have a string value and a numeric value separated by colon. After parsing them all some of the values between 0 and 1 are being set to an integer rather than a numeric as cast. The problem is obviously related to data type but I am unsure what is causing it or how to fix it. The problem only exists in the case statement, the split_part function seems to be working perfect.
Things I have tried:
nvl(split_part(one,':',2),0) = COALESCE types text and integer cannot be matched
nvl(split_part(one,':',2)::numeric,0) => Invalid input syntax for type numeric
numerous other cast/convert variations
(CASE WHEN split_part(one,':',2) = '' THEN 0::numeric ELSE split_part(one,':',2)::numeric END)::numeric => runs but get int value of 0
When using the split_part function outside of case statement it does work correctly. However, I need the result to be zero for null values.
split_part(one,':',2) => 0.02068278096187390979 (expected result)
When running the code above I get zero but expect 0.02068278096187390979
Field "one" has the following value 'xyz: 0.02068278096187390979' before the split_part function.
EXAMPLE:
create table test(one varchar);
insert into test values('XYZ: 0.50000000000000000000')
select
one ,split_part(one,':',2) as correct_value_for_those_that_are_not_null ,
case
when split_part(one,':',2) = '' then null
else split_part(one,':',2)::numeric
end::numeric as this_one_is_the_problem
from test
However, I need the result to be zero for null values.
Your example does not deal with NULL values at all, though. Only addressing the empty string ('').
To replace either with 0 reliably, efficiently and without casting issues:
SELECT part1, CASE WHEN part2 <> '' THEN part2::numeric ELSE numeric '0' END AS part2
FROM (
SELECT split_part(one, ':', 1) AS part1
, split_part(one, ':', 2) AS part2
FROM test
) sub;
See:
Best way to check for "empty or null value"
Also note that all SQL CASE branches must agree on a common data type. There have been minor adjustments in the logic that determines the resulting type in the past, so the version of Postgres may play a role in corner cases. Don't recall the details now.
nvl()is not a Postgres function. You probably meant COALESCE. The manual:
This SQL-standard function provides capabilities similar to NVL and IFNULL, which are used in some other database systems.

JSON building functions produce numeric values with trailing zeros after division

Next json functions produce values with trailing zeros and the question is how to avoid it?
SELECT JSON_BUILD_OBJECT('a', (1::NUMERIC / 10));
SELECT JSONB_SET('{}'::JSONB, '{a}', (1::NUMERIC / 10)::TEXT::JSONB);
The oputput is
{"a": 0.10000000000000000000}
And it is observed only after division, for example, the next function produces the result without zeros
SELECT JSON_BUILD_OBJECT('a', 0.1::NUMERIC); -- {"a" : 0.1}
Division without json function works the same
SELECT 1::NUMERIC / 10;-- 0.1
If it matters, Postgres version is 10.5
It may be weird if you display a json value to the screen. Try convert number to double precision and then back to numeric, like:
SELECT JSON_BUILD_OBJECT('a', (1::NUMERIC / 10)::double precision::numeric);
SELECT JSONB_SET('{}'::JSONB, '{a}', (1::NUMERIC / 10)::double precision::numeric::TEXT::JSONB);

Order By alphanumeric values like numeric

there is a table's fields on MSSQL Serrver 2005 as VARCHAR. It contains alphanumeric values like "A,B,C,D ... 1,2,3,...,10,11,12" etc.
When i use below codes;
....
ORDER BY TableFiledName
Ordering result is as follow 11,12,1,2,3 etc.
When i use codes as below,
....
ORDER BY
CASE WHEN ISNUMERIC(TableFiledName) = 0 THEN CAST(TableFiledNameAS INT) ELSE TableFiledName END
I get error message as below;
Msg 8114, Level 16, State 5, Line 1 Error converting data type varchar
to float.
How can get like this ordering result: 1,2,3,4,5,6,7,8,9,10,11,12 etc..
Thanks in advance.
ISNUMERIC returns 1 when the field is numeric.
So your first problem is that it should be ...
CASE WHEN ISNUMERIC(TableFiledName) = 1 THEN
But this alone won't work.
You need to prefix the values with zeroes and take the rightmost
order by
case when ISNUMERIC(FieldName) =1
then right('000000000'+FieldName, 5)
else FieldName
end
Using 5 allows for numbers up to 99999 - if your numbers are higher, increase that number.
This will put the numbers before the letters. If you want the letters before the numbers, then you can add an isnumeric to the sort order - ie:
order by
isnumeric(FieldName),
case...
This won't cope with decimals, but you haven't mentioned them

Test for numeric value?

The vendor data we load in our staging table is rather dirty. One column in particular captures number data but 40% of the time has garbage characters or random strings.
I have to create a report that filters out value ranges in that column. So, I tried playing with a combination of replace/translate like so
select replace(translate(upper(str),' ','all possible char'),' ','')
from table
but it fails whenever it encounters a char I did not code. Therefore, the report can never be automated.
Javascript has the isNaN() function to determine whether a value is an illegal number (True if it is and false if not).
How can I do the same thing with DB2?? Do you have any idea?
Thanks in advance.
A fairly reliable (but somewhat hackish) way is to compare the string to its upper- and lower-case self (numbers don't have different cases). As long as your data that is bringing in characters only includes Latin characters, you should be fine:
SELECT input, CASE
WHEN UPPER(input) = LOWER(input) THEN TO_NUMBER(input)
ELSE 0
END AS output
FROM source
Another option would be to use the TRANSLATE function:
SELECT input,
CASE
WHEN TRANSLATE(CAST(input as CHAR(10)), '~~~~~~~~~~~~~', '0123456789-. ') = '~~~~~~~~~~' THEN CAST(input AS DECIMAL(12, 2))
ELSE 0
END AS num
FROM x
WITH x (stringval) AS
(
VALUES ('x2'),(''),('2.2.'),('5-'),('-5-'),('--5'),('.5'),('2 2'),('0.5-'),(' 1 '),('2 '),('3.'),('-4.0')
)
SELECT stringval,
CASE WHEN (
-- Whitespace must not appear in the middle of a number
-- (but trailing and/or leading whitespace is permitted)
RTRIM(LTRIM( stringval )) NOT LIKE '% %'
-- A number cannot start with a decimal point
AND LTRIM( stringval ) NOT LIKE '.%'
-- A negative decimal number must contain at least one digit between
-- the negative sign and the decimal point
AND LTRIM( stringval ) NOT LIKE '-.%'
-- The negative sign may only appear at the beginning of the number
AND LOCATE( '-', LTRIM(stringval)) IN ( 0, 1 )
-- A number must contain at least one digit
AND TRANSLATE( stringval, '0000000000', '123456789') LIKE '%0%'
-- Allow up to one negative sign, followed by up to one decimal point
AND REPLACE(
TRANSLATE( RTRIM(LTRIM(stringval)), '000000000', '123456789'),
'0', '') IN ('','-','.','-.')
)
THEN 'VALID'
ELSE 'INVALID'
END AS stringisvalidnumber
FROM x
;
Check this out:
SELECT Mobile,
TRANSLATE(Mobile, '~~~~~~~~~~', '0123456789') AS FirstPass,
TRANSLATE(TRANSLATE(Mobile, '~~~~~~~~~~', '0123456789'), '', '~') AS Erroneous,
REPLACE(TRANSLATE(Mobile, '', TRANSLATE(TRANSLATE(Mobile, '~~~~~~~~~~', '0123456789'), '', '~')), ' ', '') AS Corrected
FROM Person WHERE Mobile <> '' FETCH FIRST 100 ROWS ONLY
The table is "Person" and the field that you want to check is "Mobile".
If you work a little bit more on this, you can build an UPDATE to fix the entire table