SQL: Cannot convert nvarchar to numeric in complex query - sql

I need to get the nearest airport in my database table from the current users position. I found this formula: https://de.scribd.com/presentation/2569355/Geo-Distance-Search-with-MySQL#page=7
So there are a few differences between the formula described in the link above and my current situation: The example was in MySQL, I'm using MS SQL (not a problem, I guess). lat and lon are considered to be database columns with numeric data type, but for some reason the database table was created with two corresponding columns of type varchar.
My problem is: When I want to use an ORDER BY clause, it throws Error converting data type nvarchar to numeric, without it, it works. I did some research on what rubbish was inserted as string and migrated it so that I just have some empty values.
I can't take all because I only need one. But if I do TOP 1 without ORDER BY I don't get any airport rather than the nearest airport. Does anyone know how to fix the query?
Thanks in advance!
SELECT TOP 1
temp.Distance
FROM (
SELECT
(
3956 * 2 * ASIN(
SQRT(
POWER(
SIN((53.6349994 - abs(CAST(latitude_deg AS numeric))) * pi() / 180 / 2), 2) + COS(53.6349994 * pi()/180) * COS(abs(CAST(latitude_deg AS numeric)) * pi()/180) * POWER(SIN((10.0117336 - CAST(longitude_deg AS numeric)) * pi()/180 / 2), 2) ))) AS Distance
FROM Airport_Airports
WHERE
isnumeric(longitude_deg) = 1 AND isnumeric(latitude_deg) = 1 AND
longitude_deg LIKE '%[^0-9.]%' AND latitude_deg LIKE '%[^0-9.]%'
) AS temp
WHERE
temp.Distance < 50000
Order BY
temp.Distance

First, this logic doesn't make sense:
WHERE isnumeric(longitude_deg) = 1 AND
isnumeric(latitude_deg) = 1 AND
longitude_deg LIKE '%[^0-9.]%' AND
latitude_deg LIKE '%[^0-9.]%'
The like is looking for non-numeric characters. I think you intend:
WHERE isnumeric(longitude_deg) = 1 AND
isnumeric(latitude_deg) = 1 AND
longitude_deg NOT LIKE '%[^0-9.]%' AND
latitude_deg NOT LIKE '%[^0-9.]%'
This ensures that the values are numeric.
The solution to your problem -- at least in SQL Server 2012+ -- is to use try_convert() or try_cast():
(3956 * 2 * ASIN(
SQRT(
POWER(
SIN((53.6349994 - abs(try_convert(numeric, latitude_deg))) * pi() / 180 / 2), 2) + COS(53.6349994 * pi()/180) * COS(abs(try_convert(numeric, latitude_deg)) * pi()/180) * POWER(SIN((10.0117336 - try_convert(numeric, longitude_deg)) * pi()/180 / 2), 2) ))) AS Distance
This will prevent any conversion errors.
You shouldn't use just numeric. Use either a floating point representation or something with decimal places, say numeric(20, 10).
The reason this occurs with the order by is because of the SQL optimizer. You clearly have some lat/long values that do not convert correctly to a numeric. SQL Server allows itself to re-arrange operations, so the conversion might take place before the filtering by the where clause. This is part of the overall query optimization.

Related

SQL error: BCD overflow caused by too many coalesce

To eliminate null values from fields for some reason the use of too many coalesce results in BCD Overflow errors
I eliminated the error to 1 line in the select part.
If any (of the 4) coalesces are replaced by a fixed value it's ok
If any coalesce is removed, the result is also ok
If for any field another is used, the error remains
Replacing the '/' with '-' also gives a result but not the correct value.
All fields are numeric(10,4).
It's Firebird version 2.5.8
select
coalesce(Field1, 0) * coalesce(Field2, 0) * ((100 - coalesce(Field3, 0)) / 100) * ((100 + coalesce(Field4, 0)) / 100)
from Table
A calculated column is expected, the actual result is a BCD overflow
Not quite an answer, just a test.
Firebird 2.1.7, IBExpert, SQL Dialect 3
create table t58096187 (
f1 numeric(10,4),
f2 numeric(10,4),
f3 numeric(10,4),
f4 numeric(10,4)
);
insert into t58096187 values ( 50, 50, 50, 50 );
select
coalesce(F1, 0) * coalesce(F2, 0) * ((100 - coalesce(F3, 0)) / 100) * ((100 + coalesce(F4, 0)) / 100)
from t58096187;
ERROR: Unsuccesful execution ... integer overflow ... cause the most significant bit of the result to carry
•Dialect 3 databases allow numbers (DECIMAL and NUMERIC data types) to be stored as INT64 when the precision is greater than 9
https://firebirdsql.org/manual/gfix-dialect.html
https://firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-datatypes-fixedtypes.html
Same error with Numeric(9,4) which should avoid use if int64 internal datatype.
If I declare fields as float though, the select yields result 1875, which is well within Numeric(10,4) datatype.
Maybe some INTERMEDIATE result in reverse-polish goes out of bounds?
This also works if to avoid use of INT64 using
recreate table t58096187 (
f1 numeric(6,2),
f2 numeric(6,2),
f3 numeric(6,2),
f4 numeric(6,2)
)
It seems Firebird 3.0.5 is also affected - https://dbfiddle.uk/?rdbms=firebird_3.0&fiddle=57729b31e0a5019aea68a136638d9f50
There is no error - but no results either!
Numeric-as-Int32 works: https://dbfiddle.uk/?rdbms=firebird_3.0&fiddle=c4a4230e855b0ce4fd2b0c7b3b697cda
Reported as https://www.sql.ru/forum/1317439-a/
Mark's assumption of fractional part accuracy as causing this is probably correct.
recreate table t58096187 (
f1 numeric(10,4),
f2 numeric(10,4),
f3 numeric(10,4),
f4 numeric(10,3)
)
Still works.
https://dbfiddle.uk/?rdbms=firebird_3.0&fiddle=29a6c15d6e1854be230d29aea30307cf
Coalesce seems to have nothing with this my problem, dunno if it is relevant to the topic starter's problem, as it is ambiguous what he meant by "removing coalesce". After I removed coalesce it becomes like that
select
F1 * F2 * F3 * F4
from t58096187
And likely the same error
https://dbfiddle.uk/?rdbms=firebird_3.0&fiddle=1f29aff4102ace57e8fa27d83e59b93f
Apparently the problem is a little more specific: The result of an integer operation caused the most significant bit of the result to carry.
So applying the suggested solution (define the format for the resulting number we want to use) we would do:
select
cast( (cast(coalesce(f1, 0) as numeric(10,4)) * cast(coalesce(f2, 0) as numeric(10,4)) ) as numeric(10,4)) * cast(((100 - coalesce(f3, 0)) / 100) as numeric(10,4)) * cast(((100 + coalesce(f4, 0)) / 100) as numeric(10,4))
-- cast(coalesce(f1, 0) as numeric(10,4)) * cast(coalesce(f2, 0) as numeric(10,4)) * cast(((100 - coalesce(f3, 0)) / 100) as numeric(10,4)) * cast(((100 + coalesce(f4, 0)) / 100) as numeric(10,4)) -- sigue dando el mismo error
-- coalesce(F1, 0) * coalesce(F2, 0) * ((100 - coalesce(F3, 0)) / 100) * ((100 + coalesce(F4, 0)) / 100)
from t58096187;
Thanks to Arioch 'The for the great analysis and example to reproduce the error!

Rounding of the numeric values

I want to round of the values of two columns:
select a.region as "Regions",
a.suminsured,2 as "SumInsured" ,
a.suminsured/b.sum*100 as pct
from (
SELECT region, sum(suminsured) as suminsured
FROM "Exposure_commune" group by region
) a,
(select sum(suminsured) FROM "Exposure_commune") b
I want the suminsured and pct columns to come with 2 decimal places. Can someone tell me what I should do?
You can use directly numeric with two parameters. Second parameter for round decimal.
select sum(column_name::numeric(10,2)) from tablename
Use round() with two parameters, which only works for the data type numeric.
While being at it, your query can be simpler and faster:
SELECT region
, round(sum(suminsured), 2) AS suminsured
, round((sum(suminsured) * 100) / sum(sum(suminsured)) OVER (), 2) AS pct
FROM "Exposure_commune"
GROUP BY 1;
You can use sum() as window function to get the total without additional subquery, which is cheaper. Related:
Postgres window function and group by exception
Multiplying first is typically cheaper and more exact (although that barely matters with numeric).
Data type is not numeric
For data types double precision of real
You can ...
just cast to numeric to use the same function.
multiply by 100, cast to integer and divide by 100.0.
multiply by 100 and use the simple round() and devide by 100.
The simple round() with just one parameter works for floating point types as well.
Demonstrating all three variants:
SELECT region
, round(sum(suminsured), 2) AS suminsured
, (sum(suminsured) * 100)::int / 100.0 AS suminsured2
, round(sum(suminsured) * 100) / 100 AS suminsured3
, round((sum(suminsured) * 100) / sum(sum(suminsured)) OVER (), 2) AS pct
, ((sum(suminsured) * 10000) / sum(sum(suminsured)) OVER ())::int / 100.0 AS pct2
, round((sum(suminsured) * 10000) / sum(sum(suminsured)) OVER ()) / 100 AS pct3
FROM "Exposure_commune"
GROUP BY 1;
SQL Fiddle.

SQL Server case statement altering value accuracy

We have a fairly complicated SQL Server 2008 r2 sp2 query with this as one of the lines :-
SUM((t.Quantity * contract.ValueOfOnePoint) * ((
CASE contract.Style
WHEN 3
THEN 1 / (1.0 + ((100.0 - Val) / 100.0 * 90.0 / 365.0))
WHEN 2
THEN 1000 * (6.0 * (1.0 - (POWER((1.0 / (1.0 + ((100.0 - Val) / 200.0))), 20.0))) / ((100.0 - Val) / 200.0) + (100.0 * (POWER((1.0 / (1.0 + ((100.0 - Val) / 200.0))), 20.0))))
END
) - (
CASE contract.Style
WHEN 3
THEN 1.0 / (1.0 + ((100.0 - t.Price) / 100.0 * 90.0 / 365.0))
WHEN 2
THEN 1000 * (6.0 * (1.0 - (POWER((1.0 / (1.0 + ((100.0 - t.Price) / 200.0))), 20.0))) / ((100.00 - t.Price) / 200.00) + (100.0 * (POWER((1.0 / (1.0 + ((100.0 - t.Price) / 200.0))), 20.0))))
END
)
)) AS NativeAmount
I am testing this on a single row which has a style of 3 so only the first line in the case statement should have any affect yet leaving the "WHEN 2" clause in it reduces the accuracy of the formula.
Eg. if I remove both WHEN 2 conditions I get an answer such 123.45678 but with the WHEN 2 line left in I get 123.46. It seems to be rounding for some reason even though the second WHEN should never be in-play.
Any thoughts would be really appreciated - going mad!
Thanks.
James.
You need to combine two things. The return type of the case statement is the same for all the then and else clauses. This is a quote from the documentation:
[The case statement] returns the highest precedence type from the set of types in
result_expressions and the optional else_result_expression. For more
information, see Data Type Precedence (Transact-SQL).
So, the SQL Engine does care about all the clauses in the query (which is the answer to your question).
I don't fully understand what is happening in this case. When you call the power() function, the compiler has to decide on the precision of the numeric value, based on the constants and column types. Based on this SQL Fiddle, it chooses a precision of 38 and a scale of 1. However, simple arithmetic on the values produces a precision of 36 and a scale of 23. I'm not sure why, in the end, this results in rounding the value to two decimal places. Perhaps the logic for assigning types for the into clause doesn't quite match the logic for typing of expressions.

SQL - How do i output string with numbers in sql?

I want to get a number 5000.1 and divide it by 1000 before adding an "F" infront of it.
How do i do this? I tried and failed this:
select "F" + round ( acq.store_size_net / 1000, 0) from acq
I suspect your missing the cast of the number to a text data type
Without knowing the exact dialect of sql you're using im gonna hazard a guess at ms-sql
select 'F' + cast(cast(round ( 5000.1 / 1000, 0)as int) as nvarchar(50))
produces output
F5
This will work in Oracle :
select 'F' || round (acq.store_size_net / 1000, 0) from acq

SQL query erroring - need fresh eyes

Ok, first, I'm building a query to search MLS data that has been provided in the form of a MySQL database, So I don't have control over the data format, and thus I believe I have to do a lot of casting to get the data in a manageable form. The SQL error is being thown.
#1064 - You have an error in your SQL syntax; check the manual that corresponds to your
MySQL server version for the right syntax to use near ' DECIMAL(2, 1)) / .5,
CAST(idx1.full_baths, DECIMAL(2, 1))), DECIMAL(2, 1)) AS b' at line 1
Looked up the error code and it sends me to a reserved words page, but I can't identify any reserved words.
and now the sql
(all fields are natively VARCHAR)
SELECT idx_common.mls_no AS mls_no,
CONCAT_WS(" ", idx_common.street_no, idx_common.street_direction, idx_common.street_name) AS address,
idx_common.city AS city,
idx_common.state AS state,
idx_common.total_sqft AS total_sqft,
idx_common.asking_price AS price,
idx1.bedrooms AS bedrooms,
CAST(
SUM(
(CAST(idx1.half_baths, DECIMAL(2, 1)) / .5),
CAST(idx1.full_bath, DECIMAL(2, 1))
),
DECIMAL(2, 1)
) AS bathrooms,
idx1.residential_prop_type AS type,
"Listing Agent" AS agent
FROM (idx_common)
JOIN idx1 ON idx_common.mls_no = idx1.mls_no
WHERE `idx_common`.`mls_no` = 'query'
OR idx_common.zip LIKE '%query%'
OR idx_common.city LIKE '%query%'
I believe you don't need SUM here at all:
SELECT idx_common.mls_no AS mls_no,
CONCAT_WS(" ", idx_common.street_no, idx_common.street_direction, idx_common.street_name) AS address,
idx_common.city AS city,
idx_common.state AS state,
idx_common.total_sqft AS total_sqft,
idx_common.asking_price AS price,
idx1.bedrooms AS bedrooms,
CAST(idx1.half_baths AS DECIMAL(2, 1)) * .5 +
CAST(idx1.full_bath AS DECIMAL(2, 1)) AS bathrooms,
idx1.residential_prop_type AS type,
"Listing Agent" AS agent
FROM idx_common
JOIN idx1
ON idx_common.mls_no = idx1.mls_no
WHERE `idx_common`.`mls_no` = 'query'
OR idx_common.zip LIKE '%query%'
OR idx_common.city LIKE '%query%'
I also changed / 0.5 to * 0.5 since it seems to be more appropriate for this query.
If an apartment has 3 half bathrooms and 2 full bathrooms, this query will output (3 / 2) + 2 = 3.5 bathrooms.
Is it what you wanted?
SUM() takes a single argument. You have SUM( Cast(...), Cast(...) )
I think you meant to do +, not SUM
(CAST(idx1.half_baths, DECIMAL(2, 1)) / .5) +
CAST(idx1.full_bath, DECIMAL(2, 1))
SUM adds all values of the column in the whole table. You can only use it in a GROUP BY query.
I believe that the function CAST works with AS, not a ",". Like this:
CAST(idx1.half_baths AS DECIMAL(2, 1))
You need to replace that on all your CASTs.
Ensure that your MYSQL version is > 5.0.8. The DECIMAL type wasn't added to the CAST function until this version.