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

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.

Related

How to update multiple rows by keeping some column values the same and updating others?

I am trying to bulk update some rows in postgres. Now not all of the rows need to update the same column values. For example, row 1 needs to update column 1 and 3 whereas row 2 needs to update column 2 and 4. so row 1 column 2 and 4 should not change and row 2 column 1 and 3 should not change.
I have tried using CASEs to conditionally SET the correct column values but it doesn't work with multiple rows. It DOES work if I only try to update 1 row at a time.
update topic as tmp set
"from" = (CASE WHEN tmp2."from2"::text = 'OLD_VALUE' THEN tmp."from"::int2 ELSE tmp2."from2"::int2 end),
"text_search" = (CASE WHEN tmp2."text_search2"::text = 'OLD_VALUE' THEN tmp."text_search"::text ELSE tmp2."text_search2"::text end),
"weight" = (CASE WHEN tmp2."weight2"::text = 'OLD_VALUE' THEN tmp."weight"::numeric ELSE tmp2."weight2"::numeric end)
from (values
(1051,1,'Electronic Devices',3),
(1052,'OLD_VALUE','OLD_VALUE',100)
) as tmp2("id2","from2","text_search2","weight2")
where tmp2."id2" = tmp."id"
This is the error message i get
SQL Error [22P02]: ERROR: invalid input syntax for type integer: "OLD_VALUE"
When I try with only 1 FROM value
from (values (1051,1,'Electronic Devices',3))
or
from (values (1052,'OLD_VALUE','OLD_VALUE',100))
it works correctly.
It even works correctly if the same columns need to be updated eg.
from (values
(1051,1,'Electronic Devices',3),
(1052,2,'Topic 2',100)
)
Why is it not working correctly when I need to update different columns for each row?
When you provide the list of values as values (1051,1,'Electronic Devices',3),(1052,'OLD_VALUE','OLD_VALUE',100)), the first set of values is interpreted as the "template" of data types, and in this case (1051,1,'Electronic Devices',3), it's int, int, text, int. Then, any subsequent values provided, will be expected to have the same data type signature. When it parses (1052,'OLD_VALUE','OLD_VALUE',100), it sees int,text,text,int, which doesn't match the data type signature it expects, so it reports an error.
When you omit the first value and provide only (1052,'OLD_VALUE','OLD_VALUE',100), then it identifies int,text,text,int as the "template" data type signature, and it proceeds without complaint.

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.

Update with ISNULL and operation

original query looks like this :
UPDATE reponse_question_finale t1, reponse_question_finale t2 SET
t1.nb_question_repondu = (9-(ISNULL(t1.valeur_question_4)+ISNULL(t1.valeur_question_6)+ISNULL(t1.valeur_question_7)+ISNULL(t1.valeur_question_9))) WHERE t1.APPLICATION = t2.APPLICATION;
I know you cannot update 2 tables in a single query so i tried this :
UPDATE reponse_question_finale t1
SET nb_question_repondu = (9-(COALESCE(t1.valeur_question_4,'')::int+COALESCE(t1.valeur_question_6,'')::int+COALESCE(t1.valeur_question_7)::int+COALESCE(t1.valeur_question_9,'')::int))
WHERE t1.APPLICATION = t1.APPLICATION;
But this query gaves me an error : invalid input syntax for integer: ""
I saw that the Postgres equivalent to MySQL is COALESCE() so i think i'm on the good way here.
I also know you cannot add varchar to varchar so i tried to cast it to integer to do that. I'm not sure if i casted it correctly with parenthesis at the good place and regarding to error maybe i cannot cast to int with coalesce.
Last thing, i can certainly do a co-related sub-select to update my two tables but i'm a little lost at this point.
The output must be an integer matching the number of questions answered to a backup survey.
Any thoughts?
Thanks.
coalesce() returns the first non-null value from the list supplied. So, if the column value is null the expression COALESCE(t1.valeur_question_4,'') returns an empty string and that's why you get the error.
But it seems you want something completely different: you want check if the column is null (or empty) and then subtract a value if it is to count the number of non-null columns.
To return 1 if a value is not null or 0 if it isn't you can use:
(nullif(valeur_question_4, '') is null)::int
nullif returns null if the first value equals the second. The IS NULL condition returns a boolean (something that MySQL doesn't have) and that can be cast to an integer (where false will be cast to 0 and true to 1)
So the whole expression should be:
nb_question_repondu = 9 - (
(nullif(t1.valeur_question_4,'') is null)::int
+ (nullif(t1.valeur_question_6,'') is null)::int
+ (nullif(t1.valeur_question_7,'') is null)::int
+ (nullif(t1.valeur_question_9,'') is null)::int
)
Another option is to unpivot the columns and do a select on them in a sub-select:
update reponse_question_finale
set nb_question_repondu = (select count(*)
from (
values
(valeur_question_4),
(valeur_question_6),
(valeur_question_7),
(valeur_question_9)
) as t(q)
where nullif(trim(q),'') is not null);
Adding more columns to be considered is quite easy then, as you just need to add a single line to the values() clause

Can you do a sub select within a Case statement

Probably something really trivial but I haven't quite found the answer I am looking for on the internet and I get syntax errors with this. What I want/need to do is to provide a special case in my where clause where the doctype is 1. If it is, then it needs to match the claimID from a sub select of a temp table. If the doctype is not a 1 then we just need to continue on and ignore the select.
AND
CASE
WHEN #DocType = 1 THEN (c.ClaimID IN (SELECT TNE.ClaimID FROM TNE)
END
I have seen some for if statements but I didn't seem to get that to work and haven't found anything online as of yet that shows a case statement doing what I would like. Is this even possible?
You don't need a case statement, you could do:
AND (#DocType <> 1 or c.ClaimID in (SELECT TNE.ClaimID FROM TNE))
A CASE expression (not statement) returns a single value. SQL Server supports the bit data type. (Valid values are 0, 1, 'TRUE' and 'FALSE'.) There is a boolean data type (with values TRUE, FALSE and UNKNOWN), but you cannot get a firm grip on one. Your CASE expression attempts to return a boolean, give or take the unmatched parenthesis, which is not supported in this context.
You could use something like this, though Luc's answer is more applicable to the stated problem:
and
case
when #DocType = 1 and c.ClaimId in ( select TNE.ClaimId from TNE ) then 1
when #DocType = 2 and ... then 1
...
else 0
end = 1
Note that the CASE returns a value which you must then compare (= 1).

SQL invalid conversion return null instead of throwing error

I have a table with a varchar column, and I want to find values that match a certain number. So lets say that column contains the following entries (except with millions of rows in real life):
123456789012
2345678
3456
23 45
713?2
00123456789012
So I decide I want all the rows which are numerically 123456789012 write a statement that looks something like this:
SELECT * FROM MyTable WHERE CAST(MyColumn as bigint) = 123456789012
It should return the first and last row, but instead the whole query blows up because it can't convert the "23 45" and "713?2" to bigint.
Is there another way to do the conversion that will return NULL for values that can't convert?
SQL Server does NOT guarantee boolean operator short-circuit, see On SQL Server boolean operator short-circuit. So all solution using ISNUMERIC(...) AND CAST(...) are fundamentally flawed (they may work, but hey can arbitrarily fail later dependiong on the generated plan). A better solution is using CASE, as Thomas suggests: CASE ISNUMERIC(...) WHEN 1 THEN CAST(...) ELSE NULL END. But, as gbn pointed out, ISNUMERIC is notoriously finicky in identifying what 'numeric' means and many cases where one would expect it to return 0 it returns 1. So mixing the CASE with the LIKE:
CASE WHEN MyRow NOT LIKE '%[^0-9]%' THEN CAST(MyRow as bigint) ELSE NULL END
But the real problem is that if you have millions of rows and you have to search them like this, you'll always end up scanning end-to-end since the expression is not SARG-able (no matter how we rewrite it). The real issue here is data purity, and should be addressed at the appropriate level, where the data is populated. Another thing to consider is if is possible to create a persisted computed column with this expression and create a filtered index on it which eliminates NULL (ie. non-numeric). That would speed up things a little.
If you are using SQL Server 2012 you can use the 2 new methods:
TRY_CAST()
TRY_CONVERT()
Both methods are equivalent. They return a value cast to the specified data type if the cast succeeds; otherwise, returns null. The only difference is that CONVERT is SQL Server specific, CAST is ANSI. using CAST will make your code more portable (although not sure if any other database provider implements TRY_CAST)
ISNUMERIC will accept empty string and values like 1.23 or 5E-04 so could be unreliable.
And you don't know what order things will be evaluated in so it could still fail (SQL is declarative, not procedural, so the WHERE clause probably won't be evaluated left to right)
So:
you want to accept value that consist only of the characters 0-9
you need to materialise the "number" filter so it's applied before CAST
Something like:
SELECT
*
FROM
(
SELECT TOP 2000000000 *
FROM MyTable
WHERE MyColumn NOT LIKE '%[^0-9]%' --double negative rejects anything except 0-9
ORDER BY MyColumn
) foo
WHERE
CAST(MyColumn as bigint) = 123456789012 --applied after number check
Edit: quick example that fails.
CREATE TABLE #foo (bigintstring varchar(100))
INSERT #foo (bigintstring )VALUES ('1.23')
INSERT #foo (bigintstring )VALUES ('1 23')
INSERT #foo (bigintstring )VALUES ('123')
SELECT * FROM #foo
WHERE
ISNUMERIC(bigintstring) = 1
AND
CAST(bigintstring AS bigint) = 123
SELECT *
FROM MyTable
WHERE ISNUMERIC(MyRow) = 1
AND CAST(MyRow as float) = 123456789012
The ISNUMERIC() function should give you what you need.
SELECT * FROM MyTable
WHERE ISNUMERIC(MyRow) = 1
AND CAST(MyRow as bigint) = 123456789012
And to add a case statement like Thomas suggested:
SELECT * FROM MyTable
WHERE CASE(ISNUMERIC(MyRow)
WHEN 1 THEN CAST(MyRow as bigint)
ELSE NULL
END = 123456789012
http://msdn.microsoft.com/en-us/library/ms186272.aspx
SELECT *
FROM MyTable
WHERE (ISNUMERIC(MyColumn) = 1) AND (CAST(MyColumn as bigint) = 123456789012)
Additionally you can use a CASE statement in order to get null values.
SELECT
CASE
WHEN (ISNUMERIC(MyColumn) = 1) THEN CAST(MyColumn as bigint)
ELSE NULL
END AS 'MyColumnAsBigInt'
FROM tableName
If you require additional filtering, for numerics which are not valid to be cast to bigint, you can use the following instead of ISNUMERIC:
PATINDEX('%[^0-9]%',MyColumn)) = 0
If you need decimal values instead of integers, cast to float instead and change the regex to '%[^0-9.]%'