JSON building functions produce numeric values with trailing zeros after division - postgresql

Next json functions produce values with trailing zeros and the question is how to avoid it?
SELECT JSON_BUILD_OBJECT('a', (1::NUMERIC / 10));
SELECT JSONB_SET('{}'::JSONB, '{a}', (1::NUMERIC / 10)::TEXT::JSONB);
The oputput is
{"a": 0.10000000000000000000}
And it is observed only after division, for example, the next function produces the result without zeros
SELECT JSON_BUILD_OBJECT('a', 0.1::NUMERIC); -- {"a" : 0.1}
Division without json function works the same
SELECT 1::NUMERIC / 10;-- 0.1
If it matters, Postgres version is 10.5

It may be weird if you display a json value to the screen. Try convert number to double precision and then back to numeric, like:
SELECT JSON_BUILD_OBJECT('a', (1::NUMERIC / 10)::double precision::numeric);
SELECT JSONB_SET('{}'::JSONB, '{a}', (1::NUMERIC / 10)::double precision::numeric::TEXT::JSONB);

Related

case when value contains text or symbols (>, <)

I'm trying to write case when statement for a particular field that has three patterns of values, which are normal numeric values (i.e. 0.6, 12.1), numeric values with symbols (i.e. > 14.0, < 1.2) and texts (i.e. canceled).
I'm trying to keep the numeric values (i.e. if 0.6 then leave it as 0.6), convert numeric values with symbols to numeric (i.e. if > 14.0 then convert it to 14.0), and exclude text values. Here is some example of the data, 'original' is the value I have currently and 'I want' is the desired output of case when.
row_id val (original) val(I want)
---------------------------------------
1 1.2 1.2
2 > 0.4 0.4
3 Canceled null
4 < 1.6 1.6
5 22.0 22.0
6 not done null
For the symbols (> or <) I could hardcode each distinct val (i.e. case when val = '> 0.2' then '0.2') but I'm hoping to learn if there is a more general way to approach it without writing when statement for each val.
For the text values, I tried isnumeric() but it doesn't work, it isn't even recognized as a function. I haven' found successful solution in PostgreSQL from other posts, would there be a way to classify text values and exclude it?
You can use the trim and replace functions to eliminate the characters < and > and leading and trailing spaces. Then a regular expression to validate the remaining characters form a legitimate numeric value. So something like:
with test (rnum, val) as
( values (1,' 1.2 ')
, (2,' > .4 ')
, (3,' Canceled ')
, (4,' < 1.6 ')
, (5,' 22.0 ')
, (6,' not done ')
)
select rnum, val
, case when res ~ '^([0-9]+\.?|^\.?)[0-9]*$' then res else null end res
from (select rnum, val, trim(replace(replace(val,'<',''),'>','')) res
from test
) r;
See additional example here
Admittedly this can be done with a single regular expression, but in order to deal with the additional 'valid' non numeric characters and spaces the expression gets complicated fast. And I am not sure the performance would be any better. Regular expressions tend to be slow, trunc and replace are pretty fast.

Convert a bytea into a binary string

I need to decode a base64 string and take a chunk of binary.
Is there a SQL function in Postgres to simply convert a bytea into a binary string representation?
(Like "00010001010101010".)
If your Postgres installation runs with the default setting bytea_output = 'hex', there is a very simple hack:
SELECT right(bytea_col::text, -1)::varbit;
Example:
SELECT right((bytea '\xDEADBEEF')::text, -1)::varbit;
Result:
'11011110101011011011111011101111'
right(text, -1) is just the cheapest way to remove the leading backslash from the text representation.
varbit (standard SQL name bit varying) is for bit strings of arbitrary length. Cast the result to text or varchar if you like.
Related, with explanation:
Convert hex in text representation to decimal number
demo:db<>fiddle
You could put the following code into a function:
WITH byte AS ( -- 1
SELECT E'\\xDEADBEEF'::bytea as value
)
SELECT
string_agg( -- 5
get_byte(value, gs)::bit(8)::text -- 4
, ''
)
FROM
byte,
generate_series( -- 3
0,
length(value) - 1 -- 2
) gs
I demonstrated the development of the query within the fiddle.
The WITH clause encapsulates the bytea value for double usage in further code
length() calculates the binary length of the bytea value
generate_series() creates a list from 0 to length - 1 (0 - 3 in my example)
get_byte() takes the bytea value a second time and gives out the byte at position gs (the previous calculated values 0-3). This gives an integer representation of the the byte. After that the cast to bit(8) type converts the result of this function to its binary representation (1 byte = 8 bit)
string_agg() finally aggregates all for binary strings into one. (taking its text representations instead of bit type, with no delimiters)
A function could look like this:
CREATE OR REPLACE FUNCTION to_bit(value bytea) RETURNS SETOF text AS
$$
BEGIN
RETURN QUERY
SELECT
string_agg(get_byte(value, gs)::bit(8)::text, '')
FROM
generate_series(0, length(value) - 1) gs;
END;
$$ LANGUAGE plpgsql;
After that you could call it:
SELECT to_bit(E'\\xDEADBEEF'::bytea)
You could try it using get_bit() instead of get_byte(). This safes you the ::bit(8) cast but of course you need to multiply the length with factor 8 indeed.
The resulting bit string has another bit order but maybe it fits your use case better:
WITH byte AS (
SELECT E'\\xDEADBEEF'::bytea as value
)
SELECT
string_agg(get_bit(value, gs)::text, '')
FROM
byte,
generate_series(0, length(value) * 8 - 1) gs
demo:db<>fiddle

RIGHT Function in UPDATE Statement w/ Integer Field

I am attempting to run a simple UPDATE script on an integer field, whereby the trailing 2 numbers are "kept", and the leading numbers are removed. For example, "0440" would be updated as "40." I can get the desired data in a SELECT statement, such as
SELECT RIGHT(field_name::varchar, 2)
FROM table_name;
However, I run into an error when I try to use this same functionality in an UPDATE script, such as:
UPDATE schema_name.table_name
SET field_name = RIGHT(field_name::varchar, 2);
The error I receive reads:
column . . . is of type integer but expression is of type text . . .
HINT: You will need to rewrite or cast the expression
You're casting the integer to varchar but you're not casting the result back to integer.
UPDATE schema_name.table_name
SET field_name = RIGHT(field_name::TEXT, 2)::INTEGER;
The error is quite straight forward - right returns textual data, which you cannot assign to an integer column. You could, however, explicitly cast it back:
UPDATE schema_name.table_name
SET field_name = RIGHT(field_name::varchar, 2)::int;
1 is a digit (or a number - or a string), '123' is a number (or a string).
Your example 0440 does not make sense for an integer value, since leading (insignificant) 0 are not stored.
Strictly speaking data type integer is no good to store the "trailing 2 numbers" - meaning digits - since 00 and 0 both result in the same integer value 0. But I don't think that's what you meant.
For operating on the numeric value, don't use string functions (which requires casting back and forth. The modulo operator % does what you need, exactly: field_name%100. So:
UPDATE schema_name.table_name
SET field_name = field_name%100
WHERE field_name > 99; -- to avoid empty updates

Postgres division incorrect and rows disappear in WITH query

I have a WITH AS query, I hope the brevity can be appreciated, i've distilled this down to what the problem is:
WITH XX AS (
SELECT ....,
floor(GREATEST(value*-1, value2) * (value4*value5/value) * -1 * 100)/100 as x,
....
)
Then I use this later in the query
SELECT 1/x as "ratio" from XX
This is where a whole bunch of rows disappear,
now if i do:
SELECT 2/1*x "ratio" from XX
also weird is this returns the same result:
SELECT 2*1*x "ratio" from XX
The rows come back but the value of ratio is incorrect. I've tried to also use CAST but it will still return the incorrect result. Curiously the result is actually the result of 2*x instead of 2/x
Why is the result incorrect, and why do the rows disappear?
SELECT 2/1*x "ratio" from XX
is equivalent to
SELECT 2*1*x "ratio" from XX
because of multiplications and divisions are evaluated from left to right. Thus, the first expression is evaluated as (2 / 1) * x, and not as 2 / (1 * x).
BTW: Your arithmetics smell like integer arithmethics which should be done as float arithmetics, e.g. you better wrote the expression as 2.0 / x.

How should I query an integer where there are decimals in the data?

SELECT * FROM table1 WHERE spent>= '1000'
This query still bring out numbers such as 598.99 and 230.909. My question is why is it doing this when I asked to search over or equal to 1000. Is there anyway to query so it only shows equal and more than 1000?
This happens because your '1000' is a text value. The other value is (or is converted to) text, too, so you end up with byte-per-byte comparison.
598.99 is greater then 1000 because 5... is greater then 1....
Cast to numeric types to do a proper comparison:
SELECT * FROM table1 WHERE spent::numeric >= '1000'::numeric
Or simply:
SELECT * FROM table1 WHERE spent::numeric >= 1000
You must compare numbers to get numeric comparison.
Use
WHERE CAST(spent AS numeric) >= 1000