I have a table I want to partition based on HASH. This table has a column with varchar, which is the key I want to use to partition.
Ofc. I can't partition based on HASH with varchar, therefore I will SUM all the ASCII values of each character in the varchar.
I hope to get some help to stitch together a function, which takes a varchar parameter and returns the SUM as an INTEGER.
I have tried several variations - some of them commented out -, this is how it looks so far:
CREATE OR REPLACE FUNCTION sum_string_ascii_values(theString varchar)
RETURNS INTEGER
LANGUAGE plpgsql
AS
$$
DECLARE
theSum INTEGER;
BEGIN
-- Sum on all ascii values coming from the every single char from the input varchar.
SELECT SUM( val )
FROM LATERAL ( SELECT ASCII( UNNEST( STRING_TO_ARRAY( LOWER(theString), null) ) ) ) AS val
INTO theSum;
--SELECT SUM(val) FROM ASCII( UNNEST( STRING_TO_ARRAY( LOWER(theString), null) ) ) AS val INTO theSUM;
--RETURN SUM( ASCII( UNNEST( STRING_TO_ARRAY( LOWER(theString), null) ) ) );
RETURN theSUM;
END;
$$;
I hope someone will be able to write and explain a solution to this problem.
Instead of using SELECT to sum the characters, you can loop through the string instead
CREATE OR REPLACE FUNCTION sum_string_ascii_values(input text) RETURNS int LANGUAGE plpgsql AS $$
DECLARE
hash int = 0;
pos int = 0;
BEGIN
WHILE pos <= length(input) LOOP
hash = hash + ascii(upper(substr(input, pos, 1)));
pos = pos + 1;
END LOOP;
RETURN hash;
END;
$$;
Here is a link to a dbfiddle to demonstrate https://dbfiddle.uk/yfhpHyT1
How can I pass my table name as an argument of a function?
CREATE OR REPLACE
FUNCTION public.countries_name1(
z integer, x integer , y integer,
name_prefix text default 'B' )
RETURNS bytea
AS $$
-- Convert tile coordinates to a bounding box
WITH
bounds AS (
SELECT ST_TileEnvelope(z, x, y) AS geom,
(CASE
when z >= 16 then 5
when z = 15 then 50
when z = 14 then 100
when z = 13 then 150
when z = 12 then 200
when z <= 11 then 300
--when z <= 10 then 500
--when z = 9 then 700
--when z = 8 then 800
--when z = 7 then 900
--when z <= 6 then 1000
ELSE 1 END
) as simplify_tolerance
),
-- Convert raw geometry into MVT geometry
-- Pull just the name in addition to the geometry
-- Apply the name_prefix parameter to the WHERE clause
mvtgeom AS (
SELECT ST_AsMVTGeom(ST_Transform(ST_simplify(t.geom,simplify_tolerance), 3857), bounds.geom)
AS geom,
t.fclass,z,x,y
FROM table_name t, bounds
WHERE ST_Intersects(t.geom, ST_Transform(bounds.geom, 25833))
AND upper(t.fclass) LIKE (upper(name_prefix) || '%') AND z>=10
)
-- Serialize the result set into an MVT object
SELECT ST_AsMVT(mvtgeom, 'public.countries_name1') FROM mvtgeom;
$$
LANGUAGE 'sql'
STABLE
PARALLEL SAFE;
In this code I want to pass my table_name from function parameter.I want to use this function in pg_tileserv .I tried to pass table name from argument but it did not work.
OK, how do you rewrite your statement to dynamic SQL.
Well the first thing you do is read the Dynamic SQL documentation. Also helpful would be Lexical Structure section 4.1.2.4, and the Format function. I advise you first write a few simple test cases to become familiar with it. Dynamic SQL is not simple.
Below I do a direct translation of your function into dynamic SQL. I make **no guarantees on the correctness of the query; it just shows how to convert it and it is not tested. It will fail. You said you passed the table name as a parameter, but your function does not have a parameter table_name, the rewrite just shows how you would use it. Note the lines marked --<<<.
create or replace
function public.countries_name(z integer
, x integer
, y integer
, name_prefix text default 'B' )
returns bytea
language plpgsql --<<<
as $$
declare
k_sql_base constant text = --<<< Convert Query to a STRING
-- Convert tile coordinates to a bounding box
$STMT$ --<<<
with bounds as
(
select
ST_TileEnvelope(z
, x
, y) as geom
,
(case
when z >= 16 then 5
when z = 15 then 50
when z = 14 then 100
when z = 13 then 150
when z = 12 then 200
when z <= 11 then 300
else 1
end
) as simplify_tolerance
)
-- Convert raw geometry into MVT geometry
-- Pull just the name in addition to the geometry
-- Apply the name_prefix parameter to the WHERE clause
, mvtgeom as
(
select
ST_AsMVTGeom(ST_Transform(ST_simplify(t.geom
, simplify_tolerance)
, 3857)
, bounds.geom) as geom
, t.fclass
, z
, x
, y
from
%I t --<<< place holder for TABLE_NAME
, bounds
where
ST_Intersects(t.geom
, ST_Transform(bounds.geom
, 25833))
and upper(t.fclass) like (upper(name_prefix) || '%')
and z >= 10
)
-- Serialize the result set into an MVT object
select
ST_AsMVT(mvtgeom, 'public.countries_name1')
from
mvtgeom;
$STMT$;
--<<<
l_sql_statement text;
l_result bytea;
begin
l_sql_statement = format(k_sql_base, table_name); -- <<< fails as table_name NOT defined as parameter
raise notice E'Executing SQL Statement:\n' || l_sql_statement; -- show the statement to be executed
execute l_sql_statement into l_result bytea; -- execute statement
return l_result bytea; -- return result
end ;
$$
I'm using ROW_NUMBER() to generate incremetal IDs numbers like this
ROW_NUMBER () OVER (ORDER BY ProductDate) as ID
i.e.
1
2
3
4
How can I do the same but create Alphabetic ordered letters like this
A
B
C
D
E
F.
..... AA, BB, CC
any ideas? Thx in advance
demo:db<>fiddle
You need to write an own function like this (original source):
CREATE OR REPLACE FUNCTION number_to_base(num BIGINT, base INTEGER)
RETURNS TEXT
LANGUAGE sql
IMMUTABLE
STRICT
AS $function$
WITH RECURSIVE n(i, n, r) AS (
SELECT -1, num - 1, 0
UNION ALL
SELECT i + 1, n / base, (n % base)::INT
FROM n
WHERE n > 0
)
SELECT string_agg(ch, '')
FROM (
SELECT chr(ascii('A') + r) ch
FROM n
WHERE i >= 0
ORDER BY i DESC
) ch
$function$;
It converts the numeral radix from base 10 to base 26 and replace the numbers 0-25 with A to Z. It's not quite perfect but a quick sketch (e.g. the A is 0 at the moment, you need to adjust it a bit).
You can use the the functions Chr() and Ascii() together to increment a character. The only other complication is determining when the next value is '...AA' instead a incrementing the last letter (i.e 'AY' => 'AZ' while 'AZ' => 'AAA). So try:
create or replace function alpha_sequence_next_val( alpha_seq_in text)
returns text
language sql
immutable
as $$
select case when alpha_seq_in is null
or alpha_seq_in = ''
then 'A'
when substr(alpha_seq_in, length(alpha_seq_in), 1) = 'Z'
then concat(substr(alpha_seq_in,1,length(alpha_seq_in)-1 ), 'AA')
else (substr(alpha_seq_in, 1,length(alpha_seq_in)-1)) ||
chr(ascii(substr(alpha_seq_in, length(alpha_seq_in)))+1)
end;
$$;
Test:
do $$
declare
seq_value text;
begin
for test_seq in 0 .. 26*3
loop
seq_value = alpha_sequence_next_val(seq_value);
raise notice 'Next Sequence==> %', seq_value;
end loop;
end;
$$;
I have two tables:
CREATE TABLE arapply
(
arapply_id serial NOT NULL,
arapply_postdate date,
arapply_source_docnumber text,
arapply_target_docnumber text,
arapply_target_paid numeric
);
CREATE TABLE aropenbal
(
ar_id integer,
doc_number text,
doc_type text,
doc_date date,
base_amount numeric,
paid_amount numeric,
open_balance numeric
);
For each entry in aropenbal, I want to SUM arapply.arapply_target_paid values where arapply.arapply_source_docnumber = aropenbal.doc_number (if aropenbal.doctype is C or R) or arapply.arapply_target_docnumber = aropenbal.doc_number (if aropenbal.doctype is not C or R) AND also arapply_postdate <= aropenbal.doc_date. The result should be stored to aropenbal.paid_amount.
I then wish to update aropenbal.open_balance with aropenbal.base_amount + aropenbal.paid_amount.
The function should return the total (SUM) of aropenbal.open_balance.
I'm having problems with the code below. The SELECT statements inside the FOR loop don't work, unless I manually assign a value say '362' in place of r.docnumber. Otherwise, the result is zero.
Seems to be a formatting problem.
Any insights?
CREATE OR REPLACE FUNCTION testit() RETURNS NUMERIC AS
$BODY$
DECLARE
r RECORD;
BEGIN
FOR r IN
SELECT * FROM aropenbal ORDER BY doc_date
LOOP
UPDATE aropenbal SET paid_amount = (
CASE WHEN (doc_type IN ('C', 'R')) THEN
(SELECT COALESCE (SUM (arapply_target_paid)* -1, 0)
FROM arapply
WHERE arapply_source_docnumber = r.doc_number
AND arapply_postdate <= r.doc_date)
ELSE
(SELECT COALESCE(SUM (arapply_target_paid),0)
FROM arapply
WHERE arapply_target_docnumber = r.doc_number
AND arapply_postdate <= r.doc_date)
END) WHERE ar_id = r.ar_id;
UPDATE aropenbal SET open_balance = (base_amount - paid_amount)
WHERE ar_id = r.ar_id;
END LOOP;
RETURN (SELECT SUM(open_balance) FROM aropenbal);
END;
$BODY$
LANGUAGE plpgsql;
I have a table with two columns.
Example:
create table t1
(
cola varchar,
colb varchar
);
Now I want to insert the rows from function.
In the function: I want to use two parameters which is of type varchar to insert the values into the above table. I am passing the string to insert into the table.
I am passing two string of characters as a parameters to the function:
Parameters:
cola varchar = 'a,b,c,d';
colb varchar = 'e,f,g,h';
The above parameters have to insert into the table like this:
cola colb
----------------
a e
b f
c g
d h
My try:
create or replace function fun_t1(cola varchar,colb varchar)
returns void as
$body$
Declare
v_Count integer;
v_i integer = 0;
v_f1 text;
v_cola varchar;
v_colb varchar;
v_query varchar;
Begin
drop table if exists temp_table;
create temp table temp_table
(
cola varchar,
colb varchar
);
v_Count := length(cola) - length(replace(cola, ',', ''));
raise info '%',v_Count;
WHILE(v_i<=v_Count) LOOP
INSERT INTO temp_table
SELECT LEFT(cola,CHARINDEX(',',cola||',',0)-1)
,LEFT(colb,CHARINDEX(',',colb||',',0)-1);
cola := overlay(cola placing '' from 1 for CHARINDEX(',',cola,0));
colb := overlay(colb placing '' from 1 for CHARINDEX(',',colb,0));
v_i := v_i + 1;
END LOOP;
for v_f1 IN select * from temp_table loop
v_cola := v_f1.cola; /* Error occurred here */
v_colb := v_f1.colb;
v_query := 'INSERT INTO t1 values('''||v_cola||''','''||v_colb||''')';
execute v_query;
end loop;
end;
$body$
language plpgsql;
Note: In the function I have used temp_table that is according to the requirement which
I am using for the other use also in the function which I have not display here.
Calling function:
SELECT fun_t1('a,b,c','d,e,f');
Getting an error:
missing FROM-clause entry for table "v_f1"
Try this way using split_part() : -
create or replace function ins_t1(vala varchar,valb varchar,row_cnt int) returns void as
$$
BEGIN
FOR i IN 1..row_cnt LOOP -- row_cnt is the number rows you need to insert (ex. 4 or 5 or whatever it is)
insert into t1 (cola,colb)
values (
(select split_part(vala,',',i))
,(select split_part(valb,',',i))
);
END LOOP;
END;
$$
language plpgsql
function call :select ins_t1('a,b,c,d','e,f,g,h',4)
As mike-sherrill-cat-recall said in his answer by using regexp_split_to_table
create or replace function fn_t1(vala varchar,valb varchar) returns void
as
$$
insert into t1 (cola, colb)
select col1, col2 from (select
trim(regexp_split_to_table(vala, ',')) col1,
trim(regexp_split_to_table(valb, ',')) col2)t;
$$
language sql
function call :select fn_t1('a,b,c,d','e,f,g,h')
If there's no compelling reason to use a function for this, you can just split the text using a regular expression. Here I've expressed your arguments as a common table expression, but that's just for convenience.
with data (col1, col2) as (
select 'a, b, c, d'::text, 'e, f, g, h'::text
)
select
trim(regexp_split_to_table(col1, ',')) as col_a,
trim(regexp_split_to_table(col2, ',')) as col_b
from data;
col_a col_b
--
a e
b f
c g
d h
If there is a compelling reason to use a function, just wrap a function definition around that SELECT statement.
create function strings_to_table(varchar, varchar)
returns table (col_a varchar, col_b varchar)
as
'select trim(regexp_split_to_table($1, '','')),
trim(regexp_split_to_table($2, '',''));'
language sql
stable
returns null on null input;
select * from strings_to_table('a,b,c,d', 'e,f, g, h');
col_a col_b
--
a e
b f
c g
d h
My personal preference is usually to build functions like this to return tables rather than inserting into tables. To insert, I'd usually write a SQL statement like this.
insert into foo (col_a, col_b)
select col_a, col_b from strings_to_table('a,b,c,d', 'e,f,g,h');
The simpest way is using plpython for this.
create or replace function fill_t1(cola varchar, colb varchar) returns void as $$
for r in zip(cola.split(','), colb.split(',')):
plpy.execute(plpy.prepare('insert into t1(cola, colb) values ($1, $2)', ['varchar', 'varchar']), [r[0], r[1]])
$$ language plpythonu;
The result:
# create table t1 (cola varchar, colb varchar);
CREATE TABLE
# select fill_t1('1,2,3', '4,5,6');
fill_t1
---------
(1 row)
# select * from t1;
cola | colb
------+------
1 | 4
2 | 5
3 | 6
(3 rows)
You can read about Python zip function here: https://docs.python.org/2/library/functions.html#zip