How to select float values by mantissa length? - postgresql

I have a column with double precision values. I am trying to select only those with precision greater than a tenth (e.g., 24.13, 1.347, etc.).
I know I can convert the float to a string and query based on string length using the following:
select * from schema.table where char_length(to_char(float_column, 'FM999999999D999999999')) > 3;
This will return all the rows with more than 1 decimal place if the integer portion of the number is single digit (eg., it will return 24.1).
How can I select any float value that has a mitissa length greater than one?

Use split_part():
with my_table(float_column) as (
values
(24.13::float), (1.347), (12345), (.1)
)
select float_column, split_part(float_column::text, '.', 2)
from my_table;
float_column | split_part
--------------+------------
24.13 | 13
1.347 | 347
12345 |
0.1 | 1
(4 rows)
So your query may look like this:
select *
from my_table
where length(split_part(float_column::text, '.', 2)) > 1;
float_column
--------------
24.13
1.347
(2 rows)

If you stored the value as numeric (fixed point versus floating point), then you could simply do:
where floor(col * 100) <> floor(col * 10) * 10
Unfortunately, there are edge cases where this logic doesn't work for floating point numbers, because you can get something like 21.99999999997.

look for rounding errors:
where round(float_column,1) <> round(float_column,5)

Related

Removing all the trailing zeroes of a numeric column in PostgreSQL

I have this table properties, which has a column atomic_mass of type NUMERIC(9,6):
atomic_mass
-------------
1.008000
4.002600
6.940000
9.012200
10.810000
12.011000
14.007000
15.999000
1.000000
(9 rows)
So I want to remove all the trailing zeros of the column such as 1.008, 4.0026, etc.
So I tried to do the following
UPDATE properties SET atomic_mass=trim(trailing '0' from atomic_mass::text)::numeric;
But it's not working. I tested the trim function which works fine. If I type
SELECT trim(trailing '0' from atomic_mass::text)::numeric from properties
it returns
rtrim
--------
1.008
4.0026
6.94
9.0122
10.81
12.011
14.007
15.999
1
The column that I wanted. So what is it that I am doing wrong here?
I am using PostgreSQL 12.9.
You have defined the column as NUMERIC(9,6). From here Numeric types that is NUMERIC(precision, scale), where scale is :
The scale of a numeric is the count of decimal digits in the fractional part, to the right of the decimal point.
So running trim to update the values is not going to help as the column definition scale will override that. The trailing zeros is a formatting issue that will have to be dealt with on output.
UPDATE
Given the information in your comments to this answer about being able to change the column type:
create table numeric_test (num_fld numeric, num6_fld numeric(9,6));
insert into numeric_test values (12.011000, 12.011000), (4.002600, 4.002600), (1.000000, 1.000000);
select * from numeric_test ;
num_fld | num6_fld
-----------+-----------
12.011000 | 12.011000
4.002600 | 4.002600
1.000000 | 1.000000
update numeric_test set num_fld = trim(trailing '0' from num_fld::text)::numeric, num6_fld = trim(trailing '0' from num6_fld::text)::numeric ;
select * from numeric_test ;
num_fld | num6_fld
---------+-----------
12.011 | 12.011000
4.0026 | 4.002600
1 | 1.000000
--
insert into numeric_test values (9.012200, 9.012200);
select * from numeric_test ;
num_fld | num6_fld
----------+-----------
12.011 | 12.011000
4.0026 | 4.002600
1 | 1.000000
9.012200 | 9.012200
With the unconstrained numeric you can remove trailing zeros from existing values on an update that trims them. However you will still get them if they are included in a insert or update that does not trim them.
You should not change type to REAL they asked to change it to DECIMAL. So if you change it to DECIMAL only not DECIMAL(9,6) it will work.
Here is what worked for me:

PostGIS returns record as datatype. This is unexpected

I have this query
WITH buffered AS (
SELECT
ST_Buffer(geom , 10, 'endcap=round join=round') AS geom,
id
FROM line),
hexagons AS (
SELECT
ST_HexagonGrid(10, buffered.geom) AS hex,
buffered.id
FROM buffered
) SELECT * FROM hexagons;
This gives the datatype record in the column hex. This is unexpected. I expect geometry as a datatype. Why is that?
According to the documentation, the function ST_HexagonGrid returns a setof record. These records contain however a geometry attribute called geom, so in order to access the geometry of this record you have to wrap the variable with parenthesis () and call the attribute with a dot ., e.g.
SELECT (hex).geom FROM hexagons;
or just access fetch all attributes using * (in this case, i,j and geom):
SELECT (hex).* FROM hexagons;
Demo (PostGIS 3.1):
WITH j (hex) AS (
SELECT
ST_HexagonGrid(
10,ST_Buffer('LINESTRING(-105.55 41.11,-115.48 37.16,-109.29 29.38,-98.34 27.13)',1))
)
SELECT ST_AsText((hex).geom,2) FROM j;
st_astext
----------------------------------------------------------------------------------------
POLYGON((-130 34.64,-125 25.98,-115 25.98,-110 34.64,-115 43.3,-125 43.3,-130 34.64))
POLYGON((-115 25.98,-110 17.32,-100 17.32,-95 25.98,-100 34.64,-110 34.64,-115 25.98))
POLYGON((-115 43.3,-110 34.64,-100 34.64,-95 43.3,-100 51.96,-110 51.96,-115 43.3))
POLYGON((-100 34.64,-95 25.98,-85 25.98,-80 34.64,-85 43.3,-95 43.3,-100 34.64))
As ST_HexagonGrid returns a setof record, you can access the record atributes using a LATERAL as described here, or just call the function in the FROM clause:
SELECT i,j,ST_AsText(geom,2) FROM
ST_HexagonGrid(
10,ST_Buffer('LINESTRING(-105.55 41.11,-115.48 37.16,-109.29 29.38,-98.34 27.13)',1));
i | j | st_astext
----+---+----------------------------------------------------------------------------------------
-8 | 2 | POLYGON((-130 34.64,-125 25.98,-115 25.98,-110 34.64,-115 43.3,-125 43.3,-130 34.64))
-7 | 1 | POLYGON((-115 25.98,-110 17.32,-100 17.32,-95 25.98,-100 34.64,-110 34.64,-115 25.98))
-7 | 2 | POLYGON((-115 43.3,-110 34.64,-100 34.64,-95 43.3,-100 51.96,-110 51.96,-115 43.3))
-6 | 2 | POLYGON((-100 34.64,-95 25.98,-85 25.98,-80 34.64,-85 43.3,-95 43.3,-100 34.64))
Further reading: How to divide world into cells (grid)

DB2: Converting varchar to money

I have 2 varchar(64) values that are decimals in this case (say COLUMN1 and COLUMN2, both varchars, both decimal numbers(money)). I need to create a where clause where I say this:
COLUMN1 < COLUMN2
I believe I have to convert these 2 varchar columns to a different data types to compare them like that, but I'm not sure how to go about that. I tried a straight forward CAST:
CAST(COLUMN1 AS DECIMAL(9,2)) < CAST(COLUMN2 AS DECIMAL(9,2))
But I had to know that would be too easy. Any help is appreciated. Thanks!
You can create a UDF like this to check which values can't be cast to DECIMAL
CREATE OR REPLACE FUNCTION IS_DECIMAL(i VARCHAR(64)) RETURNS INTEGER
CONTAINS SQL
--ALLOW PARALLEL -- can use this on Db2 11.5 or above
NO EXTERNAL ACTION
DETERMINISTIC
BEGIN
DECLARE NOT_VALID CONDITION FOR SQLSTATE '22018';
DECLARE EXIT HANDLER FOR NOT_VALID RETURN 0;
RETURN CASE WHEN CAST(i AS DECIMAL(31,8)) IS NOT NULL THEN 1 END;
END
For example
CREATE TABLE S ( C VARCHAR(32) );
INSERT INTO S VALUES ( ' 123.45 '),('-00.12'),('£546'),('12,456.88');
SELECT C FROM S WHERE IS_DECIMAL(c) = 0;
would return
C
---------
£546
12,456.88
It really is that easy...this works fine...
select cast('10.15' as decimal(9,2)) - 1
from sysibm.sysdummy1;
You've got something besides a valid numerical character in your data..
And it's something besides leading or trailing whitespace...
Try the following...
select *
from table
where translate(column1, ' ','0123456789.')
<> ' '
or translate(column2, ' ','0123456789.')
<> ' '
That will show you the rows with alpha characters...
If the above does't return anything, then you've probably got a string with double decimal points or something...
You could use a regex to find those.
There is a built-in ability to do this without UDFs.
The xmlcast function below does "safe" casting between (var)char and decfloat (you may use as double or as decimal(X, Y) instead, if you want). It returns NULL if it's impossible to cast.
You may use such an expression twice in the WHERE clause.
SELECT
S
, xmlcast(xmlquery('if ($v castable as xs:decimal) then xs:decimal($v) else ()' passing S as "v") as decfloat) D
FROM (VALUES ( ' 123.45 '),('-00.12'),('£546'),('12,456.88')) T (S);
|S |D |
|---------|------------------------------------------|
| 123.45 |123.45 |
|-00.12 |-0.12 |
|£546 | |
|12,456.88| |

Show Only 2 Decimal Places in SQL Server

I've got some columns that are stored as an INT value that I am doing some addition and division on and I would like to display the results by limiting the digits after the decimal to 2. I've tried different combinations of DECIMAL / NUMERIC / ROUND but I can't get the solution.
Could anyone offer any advice on how to get the desired solution?
Query:
SELECT cc.code AS [CountyID], RTRIM(LTRIM(cc.[description])) AS [CountyName],
SUM(ISNULL(ps.push_count,0)) AS [CountyPushCounts],
SUM(ISNULL(ps.push_unique_count,0)) AS [UniquePushCount],
SUM(ISNULL(ps.error_count,0)) AS [PushErrorCount],
SUM(ISNULL(ps.warning_count,0)) AS [PushWarningCount],
(CAST(SUM(ISNULL(ps.push_unique_count,0)) AS DECIMAL(15,2)) / CAST(SUM(ISNULL(ps.push_count,0)) AS DECIMAL(15,2)) * 100.0) AS [Unique Push Per Day] ,
((CAST(SUM(ISNULL(ps.error_count,0)) AS DECIMAL(15,2)) + CAST(SUM(ISNULL(ps.warning_count,0)) AS DECIMAL(15,2))) / CAST(SUM(ISNULL(ps.push_count,0)) AS DECIMAL(15,2)) * 100.0) AS [Data Error Rate]
FROM dbo.push_stats AS [ps]
INNER JOIN CCIS.dbo.county_codes AS [cc] ON ps.county_code = cc.code
WHERE DATEPART(YEAR,ps.ldstat_date) = 2017
AND DATEPART(MONTH,ps.ldstat_date) = 3
GROUP BY cc.code, cc.[description]
ORDER BY cc.[description];
And my data set looks as follows:
CountyID CountyName PushCounts UniquePushCount PushErrorCount PushWarningCount [Unique Push Per Day] [Data Error Rate]
1 ALACHUA 210422 77046 0 39 36.61499273 0.018534184
2 BAKER 8099 5306 0 71 65.51426102 0.876651438
3 BAY 3178214 434893 117 2793 13.68356568 0.091560857
4 BRADFORD 17654 12119 0 131 68.64733205 0.742041464
I think this will do it:
SELECT cc.code AS [CountyID], RTRIM(LTRIM(cc.[description])) AS [CountyName],
SUM(ISNULL(ps.push_count,0)) AS [CountyPushCounts],
SUM(ISNULL(ps.push_unique_count,0)) AS [UniquePushCount],
SUM(ISNULL(ps.error_count,0)) AS [PushErrorCount],
SUM(ISNULL(ps.warning_count,0)) AS [PushWarningCount],
CAST((SUM(ISNULL(ps.push_unique_count,0)) * 100.00) / SUM(ISNULL(ps.push_count,0)) AS DECIMAL(15,2)) AS [Unique Push Per Day] ,
CAST((SUM(ISNULL(ps.error_count,0)) + SUM(ISNULL(ps.warning_count,0)) * 100.00) / SUM(ISNULL(ps.push_count,0)) AS DECIMAL(15,2)) AS [Data Error Rate]
FROM dbo.push_stats AS [ps]
INNER JOIN CCIS.dbo.county_codes AS [cc] ON ps.county_code = cc.code
WHERE DATEPART(YEAR,ps.ldstat_date) = 2017
AND DATEPART(MONTH,ps.ldstat_date) = 3
GROUP BY cc.code, cc.[description]
ORDER BY cc.[description];
There are two main points here:
With the division operation, get at least one term to a floating point type of some kind before the division happens, so that it doesn't do integer division and truncate the decimal portion of the result. You're okay as long as either term is a floating point type of some kind, and you can accomplish this simply by moving the * 100.00 earlier.
You want to allow much greater than 2 decimal places for the internal calculations, and only set your output format at the very end. Rounding or casting to limited types too soon in an expression can change intermediate values in small ways that are exaggerated in the final results. This means you only want one big CAST() operation around the whole set.
You have to cast the final result, not just the individual numerator and denominator
DECLARE #Numerator INTEGER
DECLARE #Denominator INTEGER
SET #Numerator = 10
SET #Denominator = 3;
-- This will produce 3.33333333
SELECT CAST(#Numerator AS DECIMAL(5,2))/CAST(#Denominator AS DECIMAL(5,2))
-- This will give you 3.33
SELECT CAST(
CAST(#Numerator AS DECIMAL(5,2))
/CAST(#Denominator AS DECIMAL(5,2))
AS DECIMAL(5,2))

invalid input syntax for integer: "1" postgresql

PostgreSql gives me this error when i try to cast a TEXT colum to a integer.
select pro_id::integer from mmp_promocjas_tmp limit 1;
This colum contains only digits, valid integer. How can "1" be invalid integer?
select pro_id, length(pro_id) ,length(trim(pro_id)) from mmp_promocjas_tmp limit 1;
outputs:
1 | 2 | 2
Query select pro_id from mmp_promocjas_tmp where trim(pro_id) = '1' shows nothing.
I tried to remove whitespaces, without no result:
select pro_id from mmp_promocjas_tmp where regexp_replace(trim(pro_id), '\s*', '', 'g')
There are probably spurious invisible contents in the column.
To make them visible, try a query like this:
select pro_id, c,lpad(to_hex(ascii(c)),4,'0') from (
select pro_id,regexp_split_to_table(pro_id,'') as c
from (select pro_id from mmp_promocjas_tmp limit 10) as s
) as g;
This will show the ID and each character its contains, both as a character and as its hexadecimal code in the repertoire.