Why FLOAT type data comparison is not working even providing the exact data - sql

In real table with the same value
SELECT * FROM float_value WHERE val = 49640.2473896214 -- No data returns
If I round it upto the total precision then it works
SELECT * FROM float_value WHERE ROUND(val, 10) = ROUND(49640.2473896214, 10) --Returning Data
After that I have created temporaty table with the same value 49640.2473896214 and it works in the first query which is failed above
CREATE TABLE #testing(Vvalue FLOAT)
INSERT INTO #testing VALUES (49640.2473896214)
SELECT * FROM #testing WHERE Vvalue = 49640.2473896214 -- Simply returning row
Would you please help me to figure out this why = comparison is not working in the above ? If I should use ROUND always then it would be another problem to figure out the precision to be rounded and compare.
I want the result if we provide input what exactly visible in the field i.e. = 49640.2473896214 should return value.
Thank you in advance.

Approximate-number data types for use with floating point numeric data. Floating point data is approximate; therefore, not all values in the data type range can be represented exactly.
https://learn.microsoft.com/en-us/sql/t-sql/data-types/float-and-real-transact-sql
This can be very frustrating, but some float values will fail comparisons by the equal operator and it is necessary to fix the decimal precision to enable reliable use of equal.

Floating point numbers cannot represent values exactly as you want, for example:
49640.2473896214321 gets stored as 49640.2473896214287378825247287750244140625
49640.2473896214521 gets stored as 49640.247389621450565755367279052734375
17.56 gets stored as... left as exercise
It is also worth noting that the displayed value of floats is usually an approximation. Some environments allow you to change the precision of the displayed value but I could not find any such setting in SQL server. Having said all that:
SELECT * FROM float_value WHERE val = 49640.2473896214 -- No data returns
That is because 49640.2473896214 does not exist in the database. The value in database could be ...62139... or ...62141..., who knows.
Would you please help me to figure out this why = comparision is not
working in the above?
It should work if you supply the exact value stored in database (used in previous INSERT or UPDATE operation). If you supply the value you see in the database then see notes above.
If I should use ROUND always then it would be another problem to
figure out the precision to be rounded and compare.
ROUNDing returns FLOAT for FLOATs so you could end up with similar issues. The most cited solution for this problem is subtract the two numbers and check if the difference is very small:
select * from #testing WHERE ABS(vvalue - 49640.24738962 ) < 1e-11
-- id | vvalue | actual_value
-- 1 | 49640.24738962 | 49640.24738962
select * from #testing WHERE ABS(vvalue - 49640.2473896214 ) < 1e-11
-- id | vvalue | actual_value
-- 2 | 49640.2473896214 | 49640.2473896214
select * from #testing WHERE ABS(vvalue - 49640.2473896214321) < 1e-11
-- id | vvalue | actual_value
-- 3 | 49640.2473896214 | 49640.2473896214321
select * from #testing WHERE ABS(vvalue - 49640.2473896214521) < 1e-11
-- id | vvalue | actual_value
-- 4 | 49640.2473896215 | 49640.2473896214521
The 1e-11 is referred to as epsilon, the amount of tolerance you can accept. You can set it to something smaller but not smaller than 1e-16 as far as I can tell.

Now, I understand the scenario of FLOAT field values as its storing upto 10 precision by rounding the given values
For Example:
CREATE TABLE #testing(id INT IDENTITY(1,1), Vvalue FLOAT, actual_value VARCHAR(50))
INSERT INTO #testing VALUES
(49640.24738962, '49640.24738962'),
(49640.2473896214, '49640.2473896214'),
(49640.2473896214321, '49640.2473896214321'),
(49640.2473896214521, '49640.2473896214521')
value saved as:
id Vvalue actual_value
1 49640.24738962 49640.24738962 --Saved same as input
2 49640.2473896214 49640.2473896214 --Saved same as input
3 49640.2473896214 49640.2473896214321 --Saved upto 10 precision only by rounding leaving trailing zeros
4 49640.2473896215 49640.2473896214521 --Saved upto 10 precision only by rounding leaving trailing zeros
Now, Apparently following query should return two rows 2,3 but row 3 value is not exactly as out input
SELECT * FROM #testing WHERE Vvalue = 49640.2473896214
id Vvalue actual_value
2 49640.2473896214 49640.2473896214
In my case, it should return both rows 2,3 so, if I round comparison column value by 10 then it will give what I want and it doesn't matter for me now, what unseen value it's holding ? I just simply want to receive what's present there in the table
SELECT * FROM #testing WHERE ROUND(Vvalue, 10) = 49640.2473896214
id Vvalue actual_value
2 49640.2473896214 49640.2473896214
3 49640.2473896214 49640.2473896214321
Thank you everyone for sharing your ideas and boost up my mind :)

Related

Postgres float column sum delta. Update schema or use cast?

It's not a new thing to discuss, but I just want you opinion.
What is a better solution in this case:
there is a column with float values:
Column | Type | Collation | Nullable |
----------------------------------------
work_res | real | | not null |
When I calculate sum over the whole table it gives one value, let's assume 100.2123
select sum(work_res) from work_data
>> 100.2123
But when I work on the same data and group first and after do sum, then sum is 100.9124
select sum(gd.work_res)
from (
select type, sum(work_res) as work_res
from work_data
group by type
) as gd
>> 100.9124
type column is just for example as a column to group by
So the problem is round here. If I will use cast to double precision in the sum function, then numbers are identical.
select sum(cast(work_res as double precision)) from work_data
>> 100.9124
Can you please advise what is better solution for me:
Use casting each time
Update the schema
Values in the column can be in range (0, 1].
For example, it can be: 0.(3), 1, 0.5, 0.(1) and so on
I think in both cases I will still see a small delta. Right?
Thanks a lot

SQL to get all number that have more than 3 digits after the decimal point

I would like to get all number that have more than 3 digits after the decimal point.
For example, I have a table called ARTICLE that have two columns: name (varchar type) and price (number).
I would like to get all the record stored in table ARTICLE where the price column value have more than 3 numbers after the decimal point.
For example, ARTICLE.price value equal to 12.9584 or 45.874521 will be returned since they have more than 3 numbers after the decimal point.
How could I achieve this please?
I tried this request but it is not correct:
select name, price
from ARTICLE
where length(TO_CHAR(price)) > 7;
Thanks
Use ROUND function - you are looking for number that don't equal a rounded value to three digits.
Example - you test data
select round(2/3,rownum) price from dual connect by level <= 5
PRICE
----------
,7
,67
,667
,6667
,66667
Query to get numbers with more that 3 digits after the decimal point
with dt as (
select round(2/3,rownum) price from dual connect by level <= 5
)
select price from dt
where price != round(price,3)
PRICE
----------
,6667
,66667
Coming to a similar solution than #Krzysztof:
CREATE TABLE article (name VARCHAR2(10), price NUMBER);
INSERT INTO article VALUES ('a', 12.9584);
INSERT INTO article VALUES ('b', 45.874521);
INSERT INTO article VALUES ('c', 0.123);
SELECT * FROM article WHERE ABS(price - TRUNC(price,3)) > 0;
NAME PRICE
a 12,9584
b 45,874521
You can operate on numbers only:
where trunc(price * 1000) - price * 1000 <> 0
I don't have a table to test this on, but you might be able to use:
Select Name, Price
From Article
Where Price like '%#.###%'
This would say that there has to be at least 1 number before the decimal, and at least 3 after the decimal. More info on the wildcard characters below:
https://www.w3schools.com/sql/sql_wildcards.asp
Hopefully this helps!

Query returns rows outside of `between` range?

I am querying a SQL Server database to get results from a table between two number values. Here is that statement:
select *
FROM [DATA].[dbo].[TableName] with (nolock)
where number between '1400' and '1500'
order by CAST(number as float);
For the most part, the results are within the range as expected. However, I do see some anomalies where a number that has the first four digits within the range is returned as a result. For example:
14550
In the result above, the first four digits are 1455 which would be within the range of 1400 to 1500. My guess is that this has to do with the CAST(number as float) part of the statement. Any suggestions on how I can update this statement to only return numbers between the stated values?
Here is the number info I get when running sp_help:
| Column_name | Type | Computed | Length | Prec | Scale | Nullable | TrimTrailingBlanks | FixedLenNullInSource | Collation |
=============================================================================================================================================================
| NUMBER | varchar | no | 4000 | | | yes | no | yes | SQL_Latin1_General_CP1_CI_AS |
Your comparison is being done as a string, because a column named number is stored as a string and the comparison values are strings. You could easily fix this just by changing the comparison values to numbers:
select *
FROM [DATA].[dbo].[TableName]
where number between 1400 and 1500
order by CAST(number as float);
But this is a hacky solution -- and it will return an error if any of the number values are not numbers. The real solution is to fix the data model, so it is not storing numbers as strings:
alter table tablename alter number int;
This uses int because all the referenced values in the question are ints.
If you cannot do this because the column is erroneously called number and contains non-numbers, then use a safe conversion function:
select *
FROM [DATA].[dbo].[TableName]
where try_cast(number as float) between 1400 and 1500
order by try_cast(number as float);
Note: I'm also not sure if this is the logic you really want, because it includes 1500. You might really want:
select *
FROM [DATA].[dbo].[TableName]
where try_cast(number as float) >= 1400 and
try_cast(number as float) < 1500
order by try_cast(number as float);
You have to cast the number as an int...
select *
FROM [DATA].[dbo].[TableName]
where CAST(number as int) between 1400 and 1500
order by CAST(number as int);

Giving Range to the SQL Column

I have SQL table in which I have column and Probability . I want to select one row from it with randomly but I want to give more chances to the more waighted probability. I can do this by
Order By abs(checksum(newid()))
But the difference between Probabilities are too much so it gives more chance to highest probability.Like After picking 74 times that value it pick up another value for once than again around 74 times.I want to reduce this .Like I want 3-4 times to it and than others and all. I am thinking to give Range to the Probabilies.Its Like
Row[i] = Row[i-1]+Row[i]
How can I do this .Do I need to create function?Is there any there any other way to achieve this.I am neewby.Any help will be appriciated.Thank You
EDIT:
I have solution of my problem . I have one question .
if I have table as follows.
Column1 Column2
1 50
2 30
3 20
can i get?
Column1 Column2 Column3
1 50 50
2 30 80
3 20 100
Each time I want to add value with existing one.Is there any Way?
UPDATE:
Finally get the solution after 3 hours,I just take square root of my probailities that way I can narrow the difference bw them .It is like I add column with
sqrt(sqrt(sqrt(Probability)))....:-)
I'd handle it by something like
ORDER BY rand()*pow(<probability-field-name>,<n>)
for different values of n you will distort the linear probabilities into a simple polynomial. Small values of n (e.g. 0.5) will compress the probabilities to 1 and thus make less probable choices more probable, big values of n (e.g. 2) will do the opposite and further reduce probability of already inprobable values.
Since the difference in probabilities is too great, you need to add a computed field with a revised weighting that has a more even probability distribution. How you do that depends on your data and preferred distribution. One way to do it is to "normalize" the weighting to an integer between 1 and 10 so that the lowest probability is never more than ten times smaller than the highest.
Answer to your recent question:
SELECT t.Column1,
t.Column2,
(SELECT SUM(Column2)
FROM table t2
WHERE t2.Column1 <= t.Column1) Column3
FROM table t
Here is a basic example how to select one row from the table with taking into account the assigned row weights.
Suppose we have table:
CREATE TABLE TableWithWeights(
Id int NOT NULL PRIMARY KEY,
DataColumn nvarchar(50) NOT NULL,
Weight decimal(18, 6) NOT NULL -- Weight column
)
Let's fill table with sample data.
INSERT INTO TableWithWeights VALUES(1, 'Frequent', 50)
INSERT INTO TableWithWeights VALUES(2, 'Common', 30)
INSERT INTO TableWithWeights VALUES(3, 'Rare', 20)
This is the query that returns one random row with taking into account given row weights.
SELECT * FROM
(SELECT tww1.*, -- Select original table data
-- Add column with the sum of all weights of previous rows
(SELECT SUM(tww2.Weight)- tww1.Weight
FROM TableWithWeights tww2
WHERE tww2.id <= tww1.id) as SumOfWeightsOfPreviousRows
FROM TableWithWeights tww1) as tww,
-- Add column with random number within the range [0, SumOfWeights)
(SELECT RAND()* sum(weight) as rnd
FROM TableWithWeights) r
WHERE
(tww.SumOfWeightsOfPreviousRows <= r.rnd)
and ( r.rnd < tww.SumOfWeightsOfPreviousRows + tww.Weight)
To check query results we can run it for 100 times.
DECLARE #count as int;
SET #count = 0;
WHILE ( #count < 100)
BEGIN
-- This is the query that returns one random row with
-- taking into account given row weights
SELECT * FROM
(SELECT tww1.*, -- Select original table data
-- Add column with the sum of all weights of previous rows
(SELECT SUM(tww2.Weight)- tww1.Weight
FROM TableWithWeights tww2
WHERE tww2.id <= tww1.id) as SumOfWeightsOfPreviousRows
FROM TableWithWeights tww1) as tww,
-- Add column with random number within the range [0, SumOfWeights)
(SELECT RAND()* sum(weight) as rnd
FROM TableWithWeights) r
WHERE
(tww.SumOfWeightsOfPreviousRows <= r.rnd)
and ( r.rnd < tww.SumOfWeightsOfPreviousRows + tww.Weight)
-- Increase counter
SET #count += 1
END
PS The query was tested on SQL Server 2008 R2. And of course the query can be optimized (it's easy to do if you get the idea)

Finding even or odd ID values

I was working on a query today which required me to use the following to find all odd number ID values
(ID % 2) <> 0
Can anyone tell me what this is doing? It worked, which is great, but I'd like to know why.
ID % 2 is checking what the remainder is if you divide ID by 2. If you divide an even number by 2 it will always have a remainder of 0. Any other number (odd) will result in a non-zero value. Which is what is checking for.
For finding the even number we should use
select num from table where ( num % 2 ) = 0
As Below Doc specify
dividend % divisor
Returns the remainder of one number divided by another.
https://learn.microsoft.com/en-us/sql/t-sql/language-elements/modulo-transact-sql#syntax
For Example
13 % 2 return 1
Next part is <> which denotes Not equals.
Therefor what your statement mean is
Remainder of ID when it divided by 2 not equals to 0
Be careful because this is not going to work in Oracle database. Same Expression will be like below.
MOD(ID, 2) <> 0
ID % 2 reduces all integer (monetary and numeric are allowed, too) numbers to 0 and 1 effectively.
Read about the modulo operator in the manual.
In oracle,
select num from table where MOD (num, 2) = 0;
dividend % divisor
Dividend is the numeric expression to divide. Dividend must be any expression of integer data type in sql server.
Divisor is the numeric expression to divide the dividend. Divisor must be expression of integer data type except in sql server.
SELECT 15 % 2
Output
1
Dividend = 15
Divisor = 2
Let's say you wanted to query
Query a list of CITY names from STATION with even ID numbers only.
Schema structure for STATION:
ID Number
CITY varchar
STATE varchar
select CITY from STATION as st where st.id % 2 = 0
Will fetch the even set of records
In order to fetch the odd records with Id as odd number.
select CITY from STATION as st where st.id % 2 <> 0
% function reduces the value to either 0 or 1
It's taking the ID , dividing it by 2 and checking if the remainder is not zero; meaning, it's an odd ID.
<> means not equal. however, in some versions of SQL, you can write !=