Question

I have a table with a varchar column, PaymentRef, containing a variety of text and numeric data. Furthermore, there's a column, CurrencyAmount of datatype float:

PaymentRef               CurrencyAmount
----------               --------------
EUR 100,00               100
EUR 50,00                50
USD 25,00                25.2
Auth#: 98103             NULL
Auth#: 98104             NULL
Transferred from 2356    NULL
Transferred to 1356      NULL

Now whenever a record contains a PaymentRef on the form "EUR ##,##", "USD ##,##", etc. I need to compare the numeric value after the currency code, to the value of the CurrencyAmount column.

The following query works just fine:

SELECT CAST(REPLACE(SUBSTRING(PaymentRef, 5, 100), ',', '.') AS FLOAT) AS PaymentRefNumeric,
    CurrencyAmount
FROM MyTable
WHERE LEFT(PaymentRef, 3) IN ('EUR', 'USD', 'SEK')

However, if I try to compare the casted value to the CurrencyAmount column, as in:

WITH CTE AS (
    SELECT CAST(REPLACE(SUBSTRING(PaymentRef, 5, 100), ',', '.') AS FLOAT) AS PaymentRefNumeric,
        CurrencyAmount
    FROM MyTable
    WHERE LEFT(PaymentRef, 3) IN ('EUR', 'USD', 'SEK')
)
SELECT * FROM CTE WHERE PaymentRefNumeric <> CurrencyAmount

I get the following error:

Msg 8114, Level 16, State 5, Line 2
Error converting data type varchar to float.

I know that I can easily put the result of the first query into a temporary table, and then perform my comparison, but I'm wondering what the cause of this error is. I believe it has something to do with the query optimizer attempting to CAST the PaymentRef values to FLOAT, before applying the first WHERE filter. Is there a way to ensure the filter is applied before the values are cast? (That's why I tried using CTE's, but even without CTE's I still get the same error).

Was it helpful?

Solution

here's a workaround as described in my comment

WITH CTE AS (
    SELECT case when LEFT(PaymentRef, 3) IN ('EUR', 'USD', 'SEK')
                then CAST(REPLACE(SUBSTRING(PaymentRef, 5, 100), ',', '.') AS FLOAT) 
                else null
                end AS PaymentRefNumeric,
        CurrencyAmount
    FROM #t WHERE LEFT(PaymentRef, 3) IN ('EUR', 'USD', 'SEK')
)
SELECT * FROM CTE WHERE PaymentRefNumeric <> CurrencyAmount

OTHER TIPS

can't you change the where condition of the CTE to

WHERE CurrencyAmount IS NOT NULL

? your sample data looks like that would be ok ... the query works with the changed condition ...

you could also change the other where condition to

WHERE currencyamount is not null and PaymentRefNumeric <> CurrencyAmount

Use IsNull Function:

WITH CTE AS (
    SELECT CAST(REPLACE(SUBSTRING(PaymentRef, 5, 100), ',', '.') AS FLOAT) AS PaymentRefNumeric,
        CurrencyAmount
    FROM MyTable
    WHERE LEFT(PaymentRef, 3) IN ('EUR', 'USD', 'SEK')
)
SELECT * FROM CTE WHERE PaymentRefNumeric <> IsNull(CurrencyAmount, -1) -- Check if is null so replce it

Use ISNUMERIC to check if the conversion succeeded otherwise return NULL. This will cover all bases.

For SQL2012 users the TRYCAST Function can be use. If the cast fails NULL is returned. Just replace the CASE expression with:

SQL2012 TRY_CAST(REPLACE(SUBSTRING(PaymentRef, 5, 100), ',', '.'))

WITH CTE AS (
SELECT CASE WHEN 
         ISNUMERIC(REPLACE(SUBSTRING(PaymentRef, 5, 100), ',', '.'))=1
       THEN(REPLACE(SUBSTRING(PaymentRef, 5, 100), ',', '.') AS FLOAT) 
       ELSE NULL
       END PaymentRefNumeric
    ,CurrencyAmount
FROM MyTable
WHERE LEFT(PaymentRef, 3) IN ('EUR', 'USD', 'SEK')
)
SELECT * FROM CTE WHERE PaymentRefNumeric <> CurrencyAmount
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top