Mathematical operator for rounding the numbers up with lots of decimal places - sql

I have a number: 0.01744649 and I need to round it from behind. I would like to get a result: 0.018
P.S.
I've tried all the possibilities of documentation: enter link description here - every time I get a different result but not this what I want.

Use ceil:
SELECT ceil(0.01744649 * 1000) / 1000
If you need to round one digit at a time, like this: 0.01744649 -> 0.0174465 -> 0.017447 -> 0.01745 -> 0.0175 -> 0.018, here's the function:
CREATE OR REPLACE FUNCTION public.rounding(_value numeric, _precision int)
RETURNS numeric AS
$BODY$
DECLARE
tmp_val numeric;
i integer;
BEGIN
tmp_val = _value;
i = 10;
WHILE i >= _precision LOOP
tmp_val = round(tmp_val, i);
i = i - 1;
END LOOP;
RETURN tmp_val;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
Usage:
SELECT public.rounding(0.01744649, 3);
0.018
SELECT public.rounding(0.01744444, 3);
0.017

You just need to add 5/10000 before rounding to 3 decimals.
select round(0.01744649+0.0005,3);
round
-------
0.018
(1 row)

create or replace function dugi_round (
p_fl numeric,
p_pr int,
p_depth int default 0
) returns numeric language plpgsql as $$
declare n_fl numeric;
begin
n_fl := p_fl * 10.0;
-- raise notice 'we have now %, %',n_fl,p_pr;
if floor(n_fl) < n_fl then
-- raise notice 'remaining diff % - % = %',
-- n_fl, floor(n_fl), n_fl - floor(n_fl);
n_fl := dugi_round(n_fl, p_pr, p_depth + 1);
end if;
if (p_depth > p_pr) then
n_fl := round(n_fl / 10);
else
n_fl := round(n_fl / 10, p_pr);
end if;
-- raise notice 'returning %, %', n_fl, p_pr;
return n_fl;
end;
$$
;
ghp=# select dugi_round(0.01744649, 3);
dugi_round
------------
0.018
(1 row)

If you're trying to round it to the 3rd decimal, try to multiply it by 1000, ceil it and divide it by 1000 again. That should produce the result you're expecting.

Related

Converts CRC16 MySQL function to SQL Server

I have this function for calculating crc16, but it is in MySQL, can someone help me convert to SQL Server?
I looked in several places, but I only find the crc32, this does not work for the generation of the PIX QRcode.
Below is an example of the function I have.
CREATE DEFINER=`root`#`%` FUNCTION `CRC16`( _STRING VARCHAR(25)) RETURNS varchar(50) CHARSET utf8mb4
DETERMINISTIC
BEGIN
DECLARE _myCRC integer;
DECLARE _ord INTEGER;
DECLARE _n Integer;
DECLARE _m Integer;
DECLARE _strlend Integer;
SET _myCRC := x'FFFF';
SET _n := 1;
SET _strlend := LENGTH(_STRING) ;
loop_crc: LOOP
IF _n > _strlend THEN
LEAVE loop_crc;
END IF;
SET _ord := ORD(SUBSTRING(_STRING, _n, 1) );
SET _myCRC := _myCRC ^ _ord;
SET _m := 0;
loop_bit: LOOP
IF _m = 8 THEN
LEAVE loop_bit;
END IF;
IF (_myCRC & x'0001') = x'0001' THEN
SET _myCRC := (_myCRC >> 1) ^ x'A001';
ELSE
SET _myCRC := _myCRC >> 1;
END IF;
SET _m := _m + 1;
END LOOP;
SET _n := _n + 1;
END LOOP;
return HEX(_myCRC);
END//
Converting this function to Transact-SQL should be straightforward. In general:
Get rid of the definer, the backticks and DETERMINISTIC in the function header.
For loops, use WHILE condition BEGIN ... END. Notice a 'while' condition is the negation of a 'leave' condition.
Variable and parameter names must be prefixed with #.
Use decimal literals. (Use a calculator to convert the hexadecimals.)
Use = instead of := for variable assignment.
Replace >> 1 with / 2.
Replace LENGTH with LEN.
Replace ORD with ASCII or UNICODE.
Replace HEX(...) with CONVERT(char(4), CONVERT(binary(2), ...), 2), as suggested here.

Find SUM and MAX in custom aggregate function

I've been working recently with custom aggregate function.
In this custom aggregate, the first function, doesn't compute the sum and the max value correctly.
I'm using a composite type to return the sum and the max value.
I've tried with appending everything with the array, but it's not an efficient way to work
CREATE TYPE sum_max_complex AS (sum real, max_v real);
CREATE OR REPLACE FUNCTION calculateSum(sum_max_complex, real) RETURNS sum_max_complex AS $$
DECLARE
sumValue real := 0;
max_v real := $2;
output sum_max_complex;
BEGIN
RAISE NOTICE '-------------------';
RAISE NOTICE 'IL PRIMO VALORE DI INPUT E: % ... %',$1.sum,$1.max_v;
RAISE NOTICE 'IL SECONDO VALORE DI INPUT E: %',$2;
IF $2 IS NOT NULL THEN
sumValue := calculateSumAggregate(sumValue,$2) + sumValue;
ELSE
sumValue := sumValue;
END IF;
max_v := searchmaximumvalue(max_v,$2);
output.sum := sumValue;
output.max_v := max_v;
RAISE NOTICE '-------------------';
RAISE NOTICE 'IL VALORE DI OUTPUT SONO: % ... %',output.sum,output.max_v;
RETURN output;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION addLaplacianNoiseSum(sum_max_complex) RETURNS real AS $$
DECLARE
epsilon real := 0.005;
sensivity real := $1.max_v;
laplaceDistribution real;
BEGIN
laplaceDistribution := generaterandomvalues(sensivity / (epsilon));
RETURN $1.sum + laplaceDistribution;
END;
$$ LANGUAGE plpgsql;
CREATE AGGREGATE SUM_LAPLACE(real)
(
SFUNC = calculateSum,
STYPE = sum_max_complex,
FINALFUNC = addLaplacianNoiseSum
);
In my table column, I have as values: 19,22,22.5,27.
It takes the correct value in the $2 parameter method, in the 1st function, but doesn't accumulate and sum every value.
It doesn't look like you are ever adding to the values stored in the sum_max_complex type. Here's a simplified example that shows approximately what you should do. I don't know what calculateSumAggregate or generaterandomvalues do, so I wasn't able to reproduce those.
CREATE TYPE sum_max_complex AS (sum real, max_v real);
CREATE OR REPLACE FUNCTION calculateSum(sum_max_complex, real) RETURNS sum_max_complex AS $$
select ROW(
$1.sum + coalesce($2, 0),
greatest($1.max_v, $2)
)::sum_max_complex;
$$ LANGUAGE SQL IMMUTABLE;
CREATE OR REPLACE FUNCTION addLaplacianNoiseSum(sum_max_complex) RETURNS real AS $$
select $1.sum + ($1.max_v/0.005);
$$ LANGUAGE SQL IMMUTABLE;
CREATE AGGREGATE SUM_LAPLACE(real)
(
SFUNC = calculateSum,
STYPE = sum_max_complex,
FINALFUNC = addLaplacianNoiseSum,
INITCOND = '(0, 0)'
);
with a as (select a from (values (19), (22), (22.5), (27)) v(a))
select sum_laplace(a) from a;
sum_laplace
-------------
5490.5

How to generate a random, unique, alphanumeric ID of length N in Postgres 9.6+?

I've seen a bunch of different solutions on StackOverflow that span many years and many Postgres versions, but with some of the newer features like gen_random_bytes I want to ask again to see if there is a simpler solution in newer versions.
Given IDs which contain a-zA-Z0-9, and vary in size depending on where they're used, like...
bTFTxFDPPq
tcgHAdW3BD
IIo11r9J0D
FUW5I8iCiS
uXolWvg49Co5EfCo
LOscuAZu37yV84Sa
YyrbwLTRDb01TmyE
HoQk3a6atGWRMCSA
HwHSZgGRStDMwnNXHk3FmLDEbWAHE1Q9
qgpDcrNSMg87ngwcXTaZ9iImoUmXhSAv
RVZjqdKvtoafLi1O5HlvlpJoKzGeKJYS
3Rls4DjWxJaLfIJyXIEpcjWuh51aHHtK
(Like the IDs that Stripe uses.)
How can you generate them randomly and safely (as far as reducing collisions and reducing predictability goes) with an easy way to specify different lengths for different use cases, in Postgres 9.6+?
I'm thinking that ideally the solution has a signature similar to:
generate_uid(size integer) returns text
Where size is customizable depending on your own tradeoffs for lowering the chance of collisions vs. reducing the string size for usability.
From what I can tell, it must use gen_random_bytes() instead of random() for true randomness, to reduce the chance that they can be guessed.
Thanks!
I know there's gen_random_uuid() for UUIDs, but I don't want to use them in this case. I'm looking for something that gives me IDs similar to what Stripe (or others) use, that look like: "id": "ch_19iRv22eZvKYlo2CAxkjuHxZ" that are as short as possible while still containing only alphanumeric characters.
This requirement is also why encode(gen_random_bytes(), 'hex') isn't quite right for this case, since it reduces the character set and thus forces me to increase the length of the strings to avoid collisions.
I'm currently doing this in the application layer, but I'm looking to move it into the database layer to reduce interdependencies. Here's what the Node.js code for doing it in the application layer might look like:
var crypto = require('crypto');
var set = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
function generate(length) {
var bytes = crypto.randomBytes(length);
var chars = [];
for (var i = 0; i < bytes.length; i++) {
chars.push(set[bytes[i] % set.length]);
}
return chars.join('');
}
Figured this out, here's a function that does it:
CREATE OR REPLACE FUNCTION generate_uid(size INT) RETURNS TEXT AS $$
DECLARE
characters TEXT := 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
bytes BYTEA := gen_random_bytes(size);
l INT := length(characters);
i INT := 0;
output TEXT := '';
BEGIN
WHILE i < size LOOP
output := output || substr(characters, get_byte(bytes, i) % l + 1, 1);
i := i + 1;
END LOOP;
RETURN output;
END;
$$ LANGUAGE plpgsql VOLATILE;
And then to run it simply do:
generate_uid(10)
-- '3Rls4DjWxJ'
Warning
When doing this you need to be sure that the length of the IDs you are creating is sufficient to avoid collisions over time as the number of objects you've created grows, which can be counter-intuitive because of the Birthday Paradox. So you will likely want a length greater (or much greater) than 10 for any reasonably commonly created object, I just used 10 as a simple example.
Usage
With the function defined, you can use it in a table definition, like so:
CREATE TABLE users (
id TEXT PRIMARY KEY DEFAULT generate_uid(10),
name TEXT NOT NULL,
...
);
And then when inserting data, like so:
INSERT INTO users (name) VALUES ('ian');
INSERT INTO users (name) VALUES ('victor');
SELECT * FROM users;
It will automatically generate the id values:
id | name | ...
-----------+--------+-----
owmCAx552Q | ian |
ZIofD6l3X9 | victor |
Usage with a Prefix
Or maybe you want to add a prefix for convenience when looking at a single ID in the logs or in your debugger (similar to how Stripe does it), like so:
CREATE TABLE users (
id TEXT PRIMARY KEY DEFAULT ('user_' || generate_uid(10)),
name TEXT NOT NULL,
...
);
INSERT INTO users (name) VALUES ('ian');
INSERT INTO users (name) VALUES ('victor');
SELECT * FROM users;
id | name | ...
---------------+--------+-----
user_wABNZRD5Zk | ian |
user_ISzGcTVj8f | victor |
I'm looking for something that gives me "shortcodes" (similar to what Youtube uses for video IDs) that are as short as possible while still containing only alphanumeric characters.
This is a fundamentally different question from what you first asked. What you want here then is to put a serial type on the table, and to use hashids.org code for PostgreSQL.
This returns 1:1 with the unique number (serial)
Never repeats or has a chance of collision.
Also base62 [a-zA-Z0-9]
Code looks like this,
SELECT id, hash_encode(foo.id)
FROM foo; -- Result: jNl for 1001
SELECT hash_decode('jNl') -- returns 1001
This module also supports salts.
Review,
26 characters in [a-z]
26 characters in [A-Z]
10 characters in [0-9]
62 characters in [a-zA-Z0-9] (base62)
The function substring(string [from int] [for int]) looks useful.
So it looks something like this. First we demonstrate that we can take the random-range and pull from it.
SELECT substring(
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
1, -- 1 is 'a', 62 is '9'
1,
);
Now we need a range between 1 and 63
SELECT trunc(random()*62+1)::int+1
FROM generate_series(1,1e2) AS gs(x)
This gets us there.. Now we just have to join the two..
SELECT substring(
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
trunc(random()*62)::int+1
1
)
FROM generate_series(1,1e2) AS gs(x);
Then we wrap it in an ARRAY constructor (because this is fast)
SELECT ARRAY(
SELECT substring(
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
trunc(random()*62)::int+1,
1
)
FROM generate_series(1,1e2) AS gs(x)
);
And, we call array_to_string() to get a text.
SELECT array_to_string(
ARRAY(
SELECT substring(
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
trunc(random()*62)::int+1,
1
)
FROM generate_series(1,1e2) AS gs(x)
)
, ''
);
From here we can even turn it into a function..
CREATE FUNCTION random_string(randomLength int)
RETURNS text AS $$
SELECT array_to_string(
ARRAY(
SELECT substring(
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
trunc(random()*62)::int+1,
1
)
FROM generate_series(1,randomLength) AS gs(x)
)
, ''
)
$$ LANGUAGE SQL
RETURNS NULL ON NULL INPUT
VOLATILE LEAKPROOF;
and then
SELECT * FROM random_string(10);
Thanks to Evan Carroll answer, I took a look on hashids.org.
For Postgres you have to compile the extension or run some TSQL functions.
But for my needs, I created something simpler based on hashids ideas (short, unguessable, unique, custom alphabet, avoid curse words).
Shuffle alphabet:
CREATE OR REPLACE FUNCTION consistent_shuffle(alphabet TEXT, salt TEXT) RETURNS TEXT AS $$
DECLARE
SALT_LENGTH INT := length(salt);
integer INT = 0;
temp TEXT = '';
j INT = 0;
v INT := 0;
p INT := 0;
i INT := length(alphabet) - 1;
output TEXT := alphabet;
BEGIN
IF salt IS NULL OR length(LTRIM(RTRIM(salt))) = 0 THEN
RETURN alphabet;
END IF;
WHILE i > 0 LOOP
v := v % SALT_LENGTH;
integer := ASCII(substr(salt, v + 1, 1));
p := p + integer;
j := (integer + v + p) % i;
temp := substr(output, j + 1, 1);
output := substr(output, 1, j) || substr(output, i + 1, 1) || substr(output, j + 2);
output := substr(output, 1, i) || temp || substr(output, i + 2);
i := i - 1;
v := v + 1;
END LOOP;
RETURN output;
END;
$$ LANGUAGE plpgsql VOLATILE;
The main function:
CREATE OR REPLACE FUNCTION generate_uid(id INT, min_length INT, salt TEXT) RETURNS TEXT AS $$
DECLARE
clean_alphabet TEXT := 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
curse_chars TEXT := 'csfhuit';
curse TEXT := curse_chars || UPPER(curse_chars);
alphabet TEXT := regexp_replace(clean_alphabet, '[' || curse || ']', '', 'gi');
shuffle_alphabet TEXT := consistent_shuffle(alphabet, salt);
char_length INT := length(alphabet);
output TEXT := '';
BEGIN
WHILE id != 0 LOOP
output := output || substr(shuffle_alphabet, (id % char_length) + 1, 1);
id := trunc(id / char_length);
END LOOP;
curse := consistent_shuffle(curse, output || salt);
output := RPAD(output, min_length, curse);
RETURN output;
END;
$$ LANGUAGE plpgsql VOLATILE;
How-to use examples:
-- 3: min-length
select generate_uid(123, 3, 'salt'); -- output: "0mH"
-- or as default value in a table
CREATE SEQUENCE IF NOT EXISTS my_id_serial START 1;
CREATE TABLE collections (
id TEXT PRIMARY KEY DEFAULT generate_uid(CAST (nextval('my_id_serial') AS INTEGER), 3, 'salt')
);
insert into collections DEFAULT VALUES ;
This query generate required string. Just change second parasmeter of generate_series to choose length of random string.
SELECT
string_agg(c, '')
FROM (
SELECT
chr(r + CASE WHEN r > 25 + 9 THEN 97 - 26 - 9 WHEN r > 9 THEN 64 - 9 ELSE 48 END) AS c
FROM (
SELECT
i,
(random() * 60)::int AS r
FROM
generate_series(0, 62) AS i
) AS a
ORDER BY i
) AS A;
So I had my own use-case for something like this. I am not proposing a solution to the top question, but if you are looking for something similar like I am, then try this out.
My use-case was that I needed to create a random external UUID (as a primary key) with as few characters as possible. Thankfully, the scenario did not have a requirement that a large amount of these would ever be needed (probably in the thousands only). Therefore a simple solution was a combination of using generate_uid() and checking to make sure that the next sequence was not already used.
Here is how I put it together:
CREATE OR REPLACE FUNCTION generate_id (
in length INT
, in for_table text
, in for_column text
, OUT next_id TEXT
) AS
$$
DECLARE
id_is_used BOOLEAN;
loop_count INT := 0;
characters TEXT := 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
loop_length INT;
BEGIN
LOOP
next_id := '';
loop_length := 0;
WHILE loop_length < length LOOP
next_id := next_id || substr(characters, get_byte(gen_random_bytes(length), loop_length) % length(characters) + 1, 1);
loop_length := loop_length + 1;
END LOOP;
EXECUTE format('SELECT TRUE FROM %s WHERE %s = %s LIMIT 1', for_table, for_column, quote_literal(next_id)) into id_is_used;
EXIT WHEN id_is_used IS NULL;
loop_count := loop_count + 1;
IF loop_count > 100 THEN
RAISE EXCEPTION 'Too many loops. Might be reaching the practical limit for the given length.';
END IF;
END LOOP;
END
$$
LANGUAGE plpgsql
STABLE
;
here is an example table usage:
create table some_table (
id
TEXT
DEFAULT generate_id(6, 'some_table', 'id')
PRIMARY KEY
)
;
and a test to see how it breaks:
DO
$$
DECLARE
loop_count INT := 0;
BEGIN
-- WHILE LOOP
WHILE loop_count < 1000000
LOOP
INSERT INTO some_table VALUES (DEFAULT);
loop_count := loop_count + 1;
END LOOP;
END
$$ LANGUAGE plpgsql
;

Returning multiple values from an Oracle 12c function

I am writing a function in Oracle 12c (and it has to be a function) that should return 3 values, order_line.o_id, a variable total that I create in the function, and a discounted_amount variable that I create in the function.
I have gotten my function to create the discounted_amount variable and return it but I am not sure how to get it to return these other two values.
CREATE OR replace FUNCTION DiscountsReport (input_o_id IN REAL)
RETURN REAL
IS
output_o_id NUMBER;
total REAL;
discounted_total REAL;
percent REAL;
BEGIN
output_o_id := input_o_id;
SELECT SUM(o.ol_quantity * o.ol_price)
INTO total
FROM order_line o
WHERE o.o_id = input_o_id;
IF total > 100 THEN
percent := 0.1;
ELSIF total > 200 THEN
percent := 0.2;
ELSE
percent := 0.0;
END IF;
discounted_total := ( total * ( 1 - percent ) );
RETURN discounted_total;
END;
Create a new type:
CREATE OR REPLACE TYPE new_type AS OBJECT(v1 type1, v2 type2, v3 type3);
and use it after RETURN (call the result output - its type is new_type).
You can access those values using:
output.v1
output.v2
output.v3

Sum of Digits in Pl/SQL (Error In extracting Digits)

I have Written Following Code :
DECLARE
n integer;
s integer;
d integer;
BEGIN
n:=&n;
while n!=0 loop
d:=mod(n,10);
s:=s+d;
n:=n/10;
end loop;
dbms_output.put_line('output :'||s);
end;
/
Input Value : 1234
Output Value : 4321 (Perfect What I want)
But When I Tried with following (I found incorrect output)
Input value : 5678
Output : 88761
Expected Output : 8765
The code you provided didn't work for me but the error is because it is rounding this operation:
n:=n/10;
If you change it to this it should work:
n:=floor(n/10); OR n:=trunc(n/10);
However it's not working for me, I needed to add something to s:=s+d. Here's my code:
DECLARE
n INTEGER;
s INTEGER:= 0;
d INTEGER;
i INTEGER:= 0;
BEGIN
n:= 5678;
i:= length(to_char(n))-1;
WHILE n!=0 LOOP
d:=mod(n,10);
s:=s+(d*power(10,i));
i := i - 1;
n:= trunc(n/10);
END LOOP;
dbms_output.put_line('output :'||s);
END;
/
You should initialize s and a few more changes.
DECLARE
n INTEGER;
s integer := 0;
d integer;
BEGIN
n:=&n;
while n!=0 loop
d:=mod(n,10);
s:=(s*10)+d;
n:=floor(n/10);
end loop;
dbms_output.put_line('output :'||s);
END;
/