Removing all the trailing zeroes of a numeric column in PostgreSQL - 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:

Related

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)

How to select float values by mantissa length?

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)

How to remove the dot in to_char if the number is an integer

I need to perform to_char on a numeric field in postgresql. I am not able to come up with the right format string yet. I would like to get some help.
test=# select to_char(1.2, 'FM9999.9999'), to_char(1, 'FM9999.9999'), to_char(1.2212, 'FM9999.9999');
to_char | to_char | to_char
---------+---------+---------
1.2 | 1. | 1.2212
(1 row)
Basically, I should not have the dot if there is are no places after the decimal places. to_char(1, fstring) should result in 1, but to_char(1.23, fstring) should result in '1.23'. I need the to_char to behave exactly like str() function in python. For instance in python console:
>>> str(1.2)
'1.2'
>>> str(1)
'1'
>>> str(1.23)
'1.23'
closest solution I can find is to use the cast function.
=# select cast(1 as text), cast(1.2 as text), cast(12.3 as text), cast(1.0 as text);
text | text | text | text
------+------+------+------
1 | 1.2 | 12.3 | 1.0
Is there a way to use format string achieve the same. If so, can someone help me with the right format string?
You can create a function:
create function to_ch (value numeric, format text)
returns text language sql as $$
select rtrim(to_char(value, format), '.')
$$;
select to_ch(1.2, 'FM9999.9999'), to_ch(1, 'FM9999.9999'), to_ch(1.2212, 'FM9999.9999');
to_ch | to_ch | to_ch
-------+-------+--------
1.2 | 1 | 1.2212
(1 row)
Variant with predefined format (maybe more handy):
create function to_ch4 (value numeric)
returns text language sql as $$
select rtrim(to_char(value, 'FM9999.9999'), '.')
$$;
select to_ch4(1.2), to_ch4(1), to_ch4(1.2212);
to_ch4 | to_ch4 | to_ch4
--------+--------+--------
1.2 | 1 | 1.2212
(1 row)

Split a string and populate a table for all records in table in SQL Server 2008 R2

I have a table EmployeeMoves:
| EmployeeID | CityIDs
+------------------------------
| 24 | 23,21,22
| 25 | 25,12,14
| 29 | 1,2,5
| 31 | 7
| 55 | 11,34
| 60 | 7,9,21,23,30
I'm trying to figure out how to expand the comma-delimited values from the EmployeeMoves.CityIDs column to populate an EmployeeCities table, which should look like this:
| EmployeeID | CityID
+------------------------------
| 24 | 23
| 24 | 21
| 24 | 22
| 25 | 25
| 25 | 12
| 25 | 14
| ... and so on
I already have a function called SplitADelimitedList that splits a comma-delimited list of integers into a rowset. It takes the delimited list as a parameter. The SQL below will give me a table with split values under the column Value:
select value from dbo.SplitADelimitedList ('23,21,1,4');
| Value
+-----------
| 23
| 21
| 1
| 4
The question is: How do I populate EmployeeCities from EmployeeMoves with a single (even if complex) SQL statement using the comma-delimited list of CityIDs from each row in the EmployeeMoves table, but without any cursors or looping in T-SQL? I could have 100 records in the EmployeeMoves table for 100 different employees.
This is how I tried to solve this problem. It seems to work and is very quick in performance.
INSERT INTO EmployeeCities
SELECT
em.EmployeeID,
c.Value
FROM EmployeeMoves em
CROSS APPLY dbo.SplitADelimitedList(em.CityIDs) c;
UPDATE 1:
This update provides the definition of the user-defined function dbo.SplitADelimitedList. This function is used in above query to split a comma-delimited list to table of integer values.
CREATE FUNCTION dbo.fn_SplitADelimitedList1
(
#String NVARCHAR(MAX)
)
RETURNS #SplittedValues TABLE(
Value INT
)
AS
BEGIN
DECLARE #SplitLength INT
DECLARE #Delimiter VARCHAR(10)
SET #Delimiter = ',' --set this to the delimiter you are using
WHILE len(#String) > 0
BEGIN
SELECT #SplitLength = (CASE charindex(#Delimiter, #String)
WHEN 0 THEN
datalength(#String) / 2
ELSE
charindex(#Delimiter, #String) - 1
END)
INSERT INTO #SplittedValues
SELECT cast(substring(#String, 1, #SplitLength) AS INTEGER)
WHERE
ltrim(rtrim(isnull(substring(#String, 1, #SplitLength), ''))) <> '';
SELECT #String = (CASE ((datalength(#String) / 2) - #SplitLength)
WHEN 0 THEN
''
ELSE
right(#String, (datalength(#String) / 2) - #SplitLength - 1)
END)
END
RETURN
END
Preface
This is not the right way to do it. You shouldn't create comma-delimited lists in SQL Server. This violates first normal form, which should sound like an unbelievably vile expletive to you.
It is trivial for a client-side application to select rows of employees and related cities and display this as a comma-separated list. It shouldn't be done in the database. Please do everything you can to avoid this kind of construction in the future. If at all possible, you should refactor your database.
The Right Answer
To get the list of cities, properly expanded, from a table containing lists of cities, you can do this:
INSERT dbo.EmployeeCities
SELECT
M.EmployeeID,
C.CityID
FROM
EmployeeMoves M
CROSS APPLY dbo.SplitADelimitedList(M.CityIDs) C
;
The Wrong Answer
I wrote this answer due to a misunderstanding of what you wanted: I thought you were trying to query against properly-stored data to produce a list of comma-separated CityIDs. But I realize now you wanted the reverse: to query the list of cities using existing comma-separated values already stored in a column.
WITH EmployeeData AS (
SELECT
M.EmployeeID,
M.CityID
FROM
dbo.SplitADelimitedList ('23,21,1,4') C
INNER JOIN dbo.EmployeeMoves M
ON Convert(int, C.Value) = M.CityID
)
SELECT
E.EmployeeID,
CityIDs = Substring((
SELECT ',' + Convert(varchar(max), CityID)
FROM EmployeeData C
WHERE E.EmployeeID = C.EmployeeID
FOR XML PATH (''), TYPE
).value('.[1]', 'varchar(max)'), 2, 2147483647)
FROM
(SELECT DISTINCT EmployeeID FROM EmployeeData) E
;
Part of my difficulty in understanding is that your question is a bit disorganized. Next time, please clearly label your example data and show what you have, and what you're trying to work toward. Since you put the data for EmployeeCities last, it looked like it was what you were trying to achieve. It's not a good use of people's time when questions are not laid out well.

Filter an ID Column against a range of values

I have the following SQL:
SELECT ',' + LTRIM(RTRIM(CAST(vessel_is_id as CHAR(2)))) + ',' AS 'Id'
FROM Vessels
WHERE ',' + LTRIM(RTRIM(CAST(vessel_is_id as varCHAR(2)))) + ',' IN (',1,2,3,4,5,6,')
Basically, I want to filter the vessel_is_id against a variable list of integer values (which is passed in as a varchar into the stored proc). Now, the above SQL does not work. I do have rows in the table with a `vessel__is_id' of 1, but they are not returned.
Can someone suggest a better approach to this for me? Or, if the above is OK
EDIT:
Sample data
| vessel_is_id |
| ------------ |
| 1 |
| 2 |
| 5 |
| 3 |
| 1 |
| 1 |
So I want to returned all of the above where vessel_is_id is in a variable filter i.e. '1,3' - which should return 4 records.
Cheers.
Jas.
IF OBJECT_ID(N'dbo.fn_ArrayToTable',N'FN') IS NOT NULL
DROP FUNCTION [dbo].[fn_ArrayToTable]
GO
CREATE FUNCTION [dbo].fn_ArrayToTable (#array VARCHAR(MAX))
-- =============================================
-- Author: Dan Andrews
-- Create date: 04/11/11
-- Description: String to Tabled-Valued Function
--
-- =============================================
RETURNS #output TABLE (data VARCHAR(256))
AS
BEGIN
DECLARE #pointer INT
SET #pointer = CHARINDEX(',', #array)
WHILE #pointer != 0
BEGIN
INSERT INTO #output
SELECT RTRIM(LTRIM(LEFT(#array,#pointer-1)))
SELECT #array = RIGHT(#array, LEN(#array)-#pointer),
#pointer = CHARINDEX(',', #array)
END
RETURN
END
Which you may apply like:
SELECT * FROM dbo.fn_ArrayToTable('2,3,4,5,2,2')
and in your case:
SELECT LTRIM(RTRIM(CAST(vessel_is_id AS CHAR(2)))) AS 'Id'
FROM Vessels
WHERE LTRIM(RTRIM(CAST(vessel_is_id AS VARCHAR(2)))) IN (SELECT data FROM dbo.fn_ArrayToTable('1,2,3,4,5,6')
Since Sql server doesn't have an Array you may want to consider passing in a set of values as an XML type. You can then turn the XML type into a relation and join on it. Drawing on the time-tested pubs database for example. Of course you're client may or may not have an easy time generating the XML for the parameter value, but this approach is safe from sql-injection which most "comma seperated" value approaches are not.
declare #stateSelector xml
set #stateSelector = '<values>
<value>or</value>
<value>ut</value>
<value>tn</value>
</values>'
select * from authors
where state in ( select c.value('.', 'varchar(2)') from #stateSelector.nodes('//value') as t(c))