I have a database with a tree hierarchy. It has some tables, but the main ones are two tables, one of them containing the nodes, with a code field like PK, and another one with the relationships between nodes, with a id field like PK.
Something like that (the relationships):
id(PK) - codefather - codeson - more...
0 - CODE1 - CODE11 -
1 - CODE1 - CODE12 -
2 - CODE11 - CODE112 -
3 - CODE12 - CODE121 -
4 - CODE12 - CODE122 -
---------------------
When I want to see the entire tree I use this function:
CREATE OR REPLACE FUNCTION public.recorrer_principal(
IN nombretabla character varying,
IN codigopadre character varying DEFAULT NULL::character varying,
IN _nivel integer DEFAULT 0,
IN primer_elemento boolean DEFAULT true)
RETURNS TABLE(codigo character varying, naturaleza integer, ud character varying, resumen character varying, preciomed numeric, nivel integer) AS
$BODY$
DECLARE
tablaconceptos character varying := nombretabla || '_Conceptos';
tablarelacion character varying := nombretabla || '_Relacion';
existe boolean;
nombre_codigo character varying;
codpadre character varying := codigopadre;
c tp_concepto%ROWTYPE;
indice integer;
str_null_case character varying;
BEGIN
IF (codigopadre = '') IS NOT FALSE THEN
str_null_case := ' IS NULL';
ELSE
str_null_case := ' = '||quote_literal(codigopadre);
END IF;
--ROOT ELEMENT
IF primer_elemento IS TRUE THEN
EXECUTE FORMAT ('SELECT codhijo FROM %I WHERE codpadre %s',tablarelacion,str_null_case) INTO nombre_codigo;
EXECUTE FORMAT ('SELECT * FROM %I WHERE codigo = %s',tablaconceptos, quote_literal(nombre_codigo)) INTO c;
codigo := c.codigo;
nivel := _nivel;
ud := c.ud;
naturaleza := c.naturaleza;
resumen := c.resumen;
preciomed := c.preciomed;
RETURN NEXT;
codpadre := c.codigo;--if it is the first level I change codpadre to that instead the argument of the function
END IF;
--BEGIN TO ITERATE
FOR nombre_codigo in EXECUTE FORMAT ('SELECT codhijo FROM %I WHERE codpadre = %s', tablarelacion, quote_literal(codpadre))
LOOP
EXECUTE FORMAT ('SELECT * FROM %I WHERE codigo = %s', tablaconceptos, quote_literal(nombre_codigo)) INTO c;
codigo := nombre_codigo;
nivel := _nivel+1;
ud := c.ud;
naturaleza := c.naturaleza;
resumen := c.resumen;
preciomed := c.preciomed;
RETURN NEXT;
EXECUTE FORMAT ('SELECT EXISTS (SELECT * FROM %I WHERE codpadre = %s )', tablarelacion , quote_literal(nombre_codigo)) INTO existe;
--SI QUEDAN MAS HIJOS:
IF existe = TRUE THEN
RETURN QUERY SELECT * FROM recorrer_principal(nombretabla,nombre_codigo,nivel,'false');
END IF;
END LOOP;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
ROWS 1000;
ALTER FUNCTION public.recorrer_principal(character varying, character varying, integer, boolean)
OWNER TO postgres;
The function basically runs over the table of relationships, creating a table with the sons of each node. After that, with the sons of each son, and so on recursively.
Well, surprisingly for me, in a table with 12587 nodes, 39084 relationships, the function returns 52117 rows in 02:25 minutes. I think that it is too much time for this task, but maybe is a normal performance. (In this case I'll have to change the function or even the strategy, because always I open my program this tree is created, and it is too much time for wait)
Thank you in advance
Related
-- create or replace function iq_get_pse_top_active()
-- RETURNS TABLE`enter code here`
-- (
symbol character varying,
orderbook integer,
company_name character varying,
logo_url character varying,
display_logo boolean,
base_url character varying,
stock_value real,
volume integer,
last_trade_price numeric(20,4),
last_trade_date timestamp without time zone,
prev_close_price numeric(20,4),
gain_loss numeric(20,4),
percentage numeric(20,2)
)
VOLATILE
AS $body$
BEGIN
RETURN query
select symbol,
orderbook,
company_name,
logo_url,
display_logo,
base_url,
stock_value,
volume,
last_trade_price::numeric(20,4),
last_trade_date,
prev_close_price::numeric(20,4),
gain_loss::numeric(20,4),
percentage::numeric(20,2)
FROM
(
/* This is where the error. What is a good replacement for this condition? */
IF EXISTS (SELECT 1 FROM vw_pse_top_active)
THEN
(select v.symbol,
v.orderbook,
v.company_name,
v.logo_url,
v.display_logo,
v.base_url,
v.stock_value,
v.volume,
v.last_trade_price::numeric(20,4),
v.last_trade_date,
v.prev_close_price::numeric(20,4),
v.gain_loss::numeric(20,4),
v.percentage::numeric(20,2)
from vw_pse_top_active ) as v
ELSE
/* If the table above is empty then this table will be used */
(select h.symbol,
h.orderbook,
h.company_name,
h.logo_url,
h.display_logo,
h.base_url,
h.stock_value,
h.volume,
h.last_trade_price::numeric(20,4),
h.last_trade_date,
h.prev_close_price::numeric(20,4),
h.gain_loss::numeric(20,4),
h.percentage::numeric(20,2)
prod_stock_daily_top_history ) as h
END IF;
) as t;
END;
$body$
/* end of code */
I have a plpgsql function that returns a varchar[]
CREATE OR REPLACE FUNCTION public.get_distr_parents(distr_id character varying)
RETURNS character varying[]
LANGUAGE plpgsql
AS $function$
DECLARE
all_ids VARCHAR[];
parent_id VARCHAR(6);
cur_id VARCHAR(6) := distr_id;
BEGIN
all_ids := all_ids || distr_id;
parent_id := (SELECT dl.parent_distr_id FROM distribution_list dl WHERE dl.id = distr_id);
WHILE (parent_id IS NOT NULL) LOOP
all_ids := all_ids || parent_id;
cur_id := parent_id;
parent_id := (SELECT dl.parent_distr_id FROM distribution_list dl WHERE dl.id = cur_id);
END LOOP;
RETURN all_ids;
END;
$function$
;
The function seems to work as expected (e.g. it returns an array such as {2,1}).
However, I am trying to perform a query that returns records whose ID is in the returned array:
SELECT * FROM distribution_list dl
WHERE dl.id = ANY((SELECT get_distr_parents('5305')));
This query returns an error:
SQL Error [42883]: ERROR: operator does not exist: character varying = character varying[]
Hint: No operator matches the given name and argument types. You might need to add explicit type casts.
Position: 50
However, the following executes just fine:
SELECT * FROM distribution_list dl
WHERE dl.id = ANY(ARRAY((SELECT get_distr_parents('5305'))));
While the above works, I would rather avoid having to wrap it in an ARRAY every time (especially since I plan using this in a Java program).
What am I missing? If the function already returns an array, why does wrapping it in ARRAY() change the behavior?
Thanks.
I've read some posts about using table column names in a PostgreSQL function but I couldn't make it work for me.
I have this simple function
DROP FUNCTION IF EXISTS public.benchmark(CHARACTER VARYING, CHARACTER VARYING, BIGINT, BIGINT, BIGINT);
CREATE OR REPLACE FUNCTION benchmark(params CHARACTER VARYING, colName CHARACTER VARYING, idFrom BIGINT, idTo BIGINT, testNumber BIGINT) RETURNS SETOF RECORD AS $$
DECLARE
elemArray TEXT[] := ARRAY(SELECT colName FROM public.test WHERE test.id BETWEEN idFrom AND idTo);
selectedElem RECORD;
elem TEXT;
BEGIN
FOREACH elem IN ARRAY elemArray LOOP
raise notice 'elem Value: %', elem;
SELECT elem INTO selectedElem;
RETURN NEXT selectedElem;
END LOOP;
END;
$$ LANGUAGE plpgsql;
When I execute it with
SELECT * FROM public.benchmark('ad','name',1,2,1) AS x(Item TEXT);
I get
and what I should be getting are the name column values between idFrom and idTo. How can I use the colName variable as an actual column name in elemArray TEXT[] := ARRAY(SELECT colName FROM public.test WHERE test.id BETWEEN idFrom AND idTo);
You may use RETURNS TABLE + RETURN QUERY EXECUTE for dynamic columns.
CREATE OR REPLACE FUNCTION benchmark(params CHARACTER VARYING, colName
CHARACTER VARYING, idFrom BIGINT, idTo BIGINT, testNumber BIGINT)
RETURNS TABLE (colvalue TEXT) AS
$$
BEGIN
RETURN QUERY EXECUTE --dynamic query
format('SELECT %I::TEXT FROM test WHERE test.id BETWEEN $1 AND $2',colName)
--dynamic cols --bind parameters
USING idFrom,idTo;
END;
$$ LANGUAGE plpgsql;
Demo
EDIT
I just want to populate it with the elements of colName and use the
array later in the extended code
You could use ARRAY_AGG & load it into an array variable instead.
CREATE OR REPLACE FUNCTION benchmark(params CHARACTER VARYING, colName
CHARACTER VARYING, idFrom BIGINT, idTo BIGINT, testNumber BIGINT)
RETURNS void AS
$$
DECLARE
elemArray TEXT[];
elem TEXT;
BEGIN
EXECUTE format('SELECT array_agg(%I::TEXT) FROM test
WHERE test.id BETWEEN $1 AND $2',colName)
USING idFrom,idTo INTO elemArray ;
FOREACH elem IN ARRAY elemArray LOOP
raise notice 'elem Value: %', elem;
END LOOP;
END;
$$ LANGUAGE plpgsql;
knayak=# DO $$
knayak$# BEGIN
knayak$# PERFORM benchmark('ad','name',1,2,1);
knayak$# END
knayak$# $$;
NOTICE: elem Value: TalG
NOTICE: elem Value: John Doe
DO
I want to create a function that can dynamically insert any data type value into any table and multiple columns. But my function can only insert one value to one column:
CREATE OR REPLACE FUNCTION public.dynamic_insert(
tablename character varying,
columname character varying,
datatype character varying,
valuee text
)
RETURNS void
LANGUAGE 'plpgsql'
COST 100
VOLATILE AS
$BODY$
begin
Execute format('insert into %I ('||columname||')
values (cast($1 as '||datatype||'))',tablename,columname);
end;
$BODY$;
Can any one help?
I don't understand your idea clearly. I guess that you want to write one function to insert into table with list of column and values you specify.
If I guess correctly. I suggest some function like this:
CREATE OR REPLACE FUNCTION public.dynamic_insert(
tablename character varying,
columname character varying[],
datatype character varying[],
valuee character varying[]
)
RETURNS void AS
$BODY$
DECLARE
var_sql varchar;
var_sql_column varchar := '';
var_sql_values varchar := '';
begin
var_sql_column := CONCAT('("',ARRAY_TO_STRING(columname, '","'),'")');
FOR i IN 1..ARRAY_LENGTH(columname,1)
LOOP
var_sql_values := var_sql_values || quote_literal(valuee[i]) || '::' || datatype[i] || ',';
END LOOP;
var_sql_values := CONCAT('(',regexp_replace(var_sql_values, ',$', '', 'g'),')');
var_sql := CONCAT('INSERT INTO ', quote_ident(tablename), var_sql_column, ' VALUES ', var_sql_values);
IF (var_sql IS NOT NULL) THEN
EXECUTE(var_sql);
END IF;
end;
$BODY$;
LANGUAGE 'plpgsql'
COST 100
VOLATILE;
Hopefully it matches your require.
p/s: with my experience, just choose one solution between Format, and String concatenation, don't use both in one query, it makes me hard to view and find bug.
------------- Update for question below -----------------------------------
For the Loop please refer the document here
Basically, it will run from 1 to n (with n is the number elements in array) and build a query.
One simple thing to view how it run is you can put some print out data into you function, such as RAISE. Please refer it document here
Trying to create a function that looks for sequence with particular name if does not exist should create it. Then returns function value of sequence. Part of function that does not seem to be working is where it tests if sequence already exists. Below is code for function
CREATE OR REPLACE FUNCTION public."tenantSequence"(
vtenantid integer,
vtablename character)
RETURNS bigint AS
$BODY$DECLARE
vSeqName character varying;
vSQL character varying;
BEGIN
select ('t' || trim(to_char(vtenantid,'0000')) || vtablename) INTO vSeqName;
if not exists(SELECT 0 FROM pg_class where relkind = 'S' and relname = vSeqName )
then
vSQL := 'create sequence '||vSeqName||';';
execute vSQL;
ELSE
return 0;
end if;
return nextval(vSeqName) * 10000 + vtenantid;
END$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION public."tenantSequence"(integer, character)
OWNER TO postgres;
Problem was case as mentioned by a_horse_with_no_name
changed line assign vSeqName to the following
vSeqName := lower('t' || trim(to_char(vtenantid,'0000')) || vtablename);
Now function works as expected.