Passing record into Postgres function - sql

I'm trying to create an aggregate function that takes a set of records, does some calculations, and returns a number.
I have gotten the following dummy example to work:
CREATE OR REPLACE FUNCTION dummy_func(text) RETURNS int
AS
$$
DECLARE
rec record;
num int := 0;
BEGIN
FOR rec IN EXECUTE $1
LOOP
IF POSITION('some text' IN rec.known_column_name) = 1
THEN
num := num + 1;
END IF;
END LOOP;
RETURN num;
END;
$$ LANGUAGE PLPGSQL;
If I enter
SELECT dummy_func('select * from some_table');
, it works as expected.
However, I would like to make it so that I can use multiple functions and conditions, such as:
SELECT conditional_col_1, dummy_func_1(*), dummy_func_2(*)
FROM some_table
WHERE conditional_col_2 = 'some val'
GROUP BY 1
How can I rewrite my dummy example into being able to function as so (or use a bunch of UNIONS ALL, etc)?
In response to Hambone
If I have the following logic (counting 'a' or counting 'b' * column something3)
CREATE OR REPLACE FUNCTION count_a(text) RETURNS int
AS
$$
DECLARE
rec record;
num int := 0;
BEGIN
FOR rec IN EXECUTE $1
LOOP
num := num + length(regexp_replace(rec.something1, '[^a]+', '', 'g'));
END LOOP;
RETURN num;
END;
$$ LANGUAGE PLPGSQL;
CREATE OR REPLACE FUNCTION count_e_times_something3(text) RETURNS int
AS
$$
DECLARE
rec record;
num int := 0;
BEGIN
FOR rec IN EXECUTE $1
LOOP
num := num + length(regexp_replace(rec.something1, '[^e]+', '', 'g')) * rec.something3::int;
END LOOP;
RETURN num;
END;
$$ LANGUAGE PLPGSQL;
I currently have to execute them individually,
$=# SELECT count_a('SELECT * FROM MY_TEST WHERE something2 = ''A'' and something1 = ''0''');
-[ RECORD 1 ]
count_a | 0
$=# SELECT count_a('SELECT * FROM MY_TEST WHERE something2 = ''A'' and something3 = ''0''');
-[ RECORD 1 ]
count_a | 2
$=# SELECT count_e_times_something3('SELECT * FROM MY_TEST WHERE something2 = ''A'' and something3 = ''0''');
-[ RECORD 1 ]------------+--
count_e_times_something3 | 0
$=# SELECT count_e_times_something3('SELECT * FROM MY_TEST WHERE something2 = ''A'' and something3 = ''1''');
-[ RECORD 1 ]------------+--
count_e_times_something3 | 7
It would be a lot nicer if I could use the following query (I know I need to define an Aggregate function but I can't find any examples where records are taken in an scalars are returned):
SELECT something2, count_a(*), count_e_times_something3(*) FROM MY_TEST WHERE something3 = '1';
something: 'A'
count_a: 3
count_e_times_something3: 7
something: 'B'
count_a: 3
count_e_times_something3: 2
Both count_a resulting in 3 is a coincidence

Related

Syntax error while using EXECUTE format in Postgresql Function to assign variable

I have been trying to create a function that intends to assign a value to a declared variable, and act accordingly based on that value.
I use EXECUTE format(<SQL statement>) for assigning the value to cnt.
CREATE OR REPLACE FUNCTION my_function() RETURNS TRIGGER AS $$
DECLARE
cnt bigint;
BEGIN
IF NEW.field1 = 'DECLINED' THEN
cnt := EXECUTE format('SELECT count(*) FROM table1 WHERE field2 = $1 AND field1 != $2 AND id != $3;') USING NEW.field2, NEW.field1, NEW.id INTO cnt;
IF cnt = 0 THEN
EXECUTE format('UPDATE table1 SET field1 = %1$s WHERE id = $2') USING 'DECLINED', NEW.field2;
END IF;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER my_trigger BEFORE
UPDATE OF field1 ON table1 FOR EACH ROW WHEN (NEW.field1 = 'DECLINED') EXECUTE FUNCTION my_function();
However, I am getting the following error:
ERROR: syntax error at or near "("
LINE 7: cnt := EXECUTE format('SELECT count(*) FROM...
Not sure if it is relevant, but id is a text column, field1 is an ENUM, and field2 is also a text column. Could that be a problem in the SELECT statement?
Any ideas what I could be missing?
I only want to fire the second statement if cnt equals to 0
It could be rewritten as single statement:
UPDATE table1
SET field1 = ...
WHERE id = ...
AND NOT EXISTS (SELECT *
FROM table1
WHERE field2 = ...
AND field1 != ...
AND id != ...);
Using it in trigger indicates it is a try to implement partial uniqueness. If so then partial/filtered index is also an option:
CREATE UNIQUE INDEX uq ON table1(id, field1) WHERE field2 = ....;
Although #Lukasz solution may also work. I ended up using the implementation suggested by #stickybit in the comments of the question
Answer:
CREATE OR REPLACE FUNCTION my_function() RETURNS TRIGGER AS $$
DECLARE
cnt bigint;
BEGIN
IF NEW.field1 = 'DECLINED' THEN
cnt := ('SELECT count(*) FROM table1 WHERE field2 = NEW.field2 AND field1 != NEW.field1 AND id != NEW.id;')
IF cnt = 0 THEN
'UPDATE table1 SET field1 = 'DECLINED' WHERE id = NEW.field2';
END IF;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

Drop table if it's empty / pass the result of count(*) to variable

I would like to check if a table is empty, and if it is, I would like drop it. I know this little function doesn't seem as a useful thing by itself, but I have a much longer function, so this is just the main part.
CREATE OR REPLACE FUNCTION public.cl_tbl(t_name character varying)
RETURNS void AS
$BODY$
DECLARE
rownum int;
BEGIN
SELECT COUNT(*) INTO rownum FROM format('myschema.%I',t_name);
IF rownum = 0 then
EXECUTE format('DROP TABLE myschema.%I',t_name);
END IF;
RETURN;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
My problem is, that the line
SELECT COUNT(*) INTO rownum FROM format('myschema.%I',t_name);
doesn't returns 0 if the table is empty, instead it returns 1 as the number of rows of the selection.
| count(bigint)
--------------------
1 | 0
I've tried this as well:
rownum := SELECT COUNT(*) FROM format('myschema.%I',t_name);
but the result is the same. How could I pass the real number of the rows of a given table?
You can use EXISTS() - SELECT EXISTS(SELECT * FROM table_name).
CREATE OR REPLACE FUNCTION public.cl_tbl(t_name character varying)
RETURNS void AS
$BODY$
DECLARE
x BOOLEAN;
BEGIN
EXECUTE format('select exists (select * from myschema.%I) t', t_name) INTO x;
IF x = False then
EXECUTE format('DROP TABLE myschema.%I',t_name);
END IF;
RETURN;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
Try using EXECUTE:
CREATE OR REPLACE FUNCTION public.cl_tbl(t_name character varying)
RETURNS void AS
$BODY$
DECLARE
rownum int;
BEGIN
EXECUTE format('select count(*) from %I', t_name) into rownum;
IF rownum = 0 then
EXECUTE format('DROP TABLE %I',t_name);
END IF;
RETURN;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
;

Is there an oracle spatial function for finding self-intersecting linestrings?

I need to find all self-intersecting linestrings in table. SDO_GEOM.VALIDATE_GEOMETRY_WITH_CONTEXT finds only self-intersecting polygons, because self-intersecting linestrings are allowed. Any ideas?
It takes a bit of work, but it's doable.
Since Oracle(11.2) failed to provide, the only option we have is to brake the line into segments and use RELATE on the segments' pairs.
Following is my own implementation (used in production code for over 3 years, over millions of geometries). I chose the pipelined approach to cover for overly big or complex geometries.
Prerequisit 1, database type:
CREATE OR REPLACE TYPE ElemGeom as object
(eid integer, egeom mdsys.sdo_geometry, egtype integer, eelemnum integer, evertnum integer, earea number, elength number);
CREATE OR REPLACE TYPE ElemGeomTbl as table of ElemGeom;
Prerequisit 2, splitting function:
create or replace FUNCTION LineSegments (igeom in mdsys.sdo_geometry)
RETURN ElemGeomTbl pipelined
is
seg ElemGeom := ElemGeom(null,null,null,null,null,null,null);
cursor c is select T.id, T.X ,T.Y from table(SDO_UTIL.GETVERTICES(iGEOM)) T order by 1;
type ctbl is table of c%rowtype;
carr ctbl;
seg_geom mdsys.sdo_geometry;
cnt integer:=0;
segid integer; x1 number; y1 number; x2 number; y2 number;
begin
--if igeom.sdo_gtype not in (2002,2006)
--then... if you need to catch non-linears here...
--end if;
open c;
loop
fetch c
bulk collect into carr ;
for i in carr.first .. carr.last -1
loop cnt:=cnt+1;
segid := cnt;
x1 := carr(i).X; y1 := carr(i).Y;
x2 := carr(i+1).X; y2 := carr(i+1).Y;
seg_geom:= (mdsys.sdo_geometry(2002,2100,null
,mdsys.sdo_elem_info_array(1,2,1)
,mdsys.sdo_ordinate_array(x1,y1, x2,y2)));
seg.eid:=segid;
seg.egeom:=seg_geom;
seg.egtype:=seg_geom.sdo_gtype;
pipe row(seg);
end loop;
exit when c%notfound;
end loop;
close c;
end LineSegments;
You can test its output with something like (my GEOMs are SRID 2100):
with t1 as (
select
SDO_GEOMETRY(2002,2100,NULL,
SDO_ELEM_INFO_ARRAY(1,2,1),
SDO_ORDINATE_ARRAY(290161.697,4206385.413, 290161.901,4206388.095, 290162.684,4206385.188, 290163.188,4206388.041,
290163.51,4206385.22, 290164.357,4206388.159, 290166.879,4206387.108, 290161.397,4206387.366,
290166.331,4206386.067, 290165.763,4206388.052))
as G from DUAL
)
select * from t1,table(LineSegments(g));
And the main function:
create or replace FUNCTION validate_Line
(igeom in mdsys.sdo_geometry, itol in number default null)
RETURN varchar2
is
vtol number:= nvl(itol, 1/power(10,6));
verd1 varchar2(256); verd2 varchar2(256); v varchar2(256);
begin
verd1:= sdo_geom.validate_geometry_with_context(igeom,vtol);
for r1 in ( select a.eid seg1, a.egeom geom1, b.eid seg2, b.egeom geom2
from table(LineSegments(igeom)) a, table(LineSegments(igeom)) b
where a.eid < b.eid
order by a.eid, b.eid )
loop
--I hate outputting long words, so:
v:= replace(replace(sdo_geom.relate(r1.geom1,'determine',r1.geom2, vtol)
,'OVERLAPBDYDISJOINT','OVR-BDIS'),'OVERLAPBDYINTERSECT','OVR-BINT');
if instr('EQUAL,TOUCH,DISJOINT',v) = 0 then
verd2:= verd2|| case when verd2 is not null
then ', '||r1.seg1||'-'||r1.seg2||'='||v
else r1.seg1||'-'||r1.seg2||'='||v end;
end if;
end loop;
verd1:= nvl(verd1,'NULL')
|| case when verd1 ='TRUE' and verd2 is null then null
when verd1 ='TRUE' and verd2 is not null then ' *+: '||verd2
end;
return verd1;
end validate_Line;
And its test:
with t1 as (
select
SDO_GEOMETRY(2002,2100,NULL,
SDO_ELEM_INFO_ARRAY(1,2,1),
SDO_ORDINATE_ARRAY(290161.697,4206385.413, 290161.901,4206388.095, 290162.684,4206385.188, 290163.188,4206388.041,
290163.51,4206385.22, 290164.357,4206388.159, 290166.879,4206387.108, 290161.397,4206387.366,
290166.331,4206386.067, 290165.763,4206388.052))
as G from DUAL
)
select t1.*,validate_Line(g) from t1;
This returns:
*TRUE *+: 1-7=OVR-BDIS, 1-8=OVR-BDIS, 2-7=OVR-BDIS, 2-8=OVR-BDIS, 3-7=OVR-BDIS, 3-8=OVR-BDIS, 4-7=OVR-BDIS, 4-8=OVR-BDIS, 5-7=OVR-BDIS, 5-8=OVR-BDIS, 6-9=OVR-BDIS, 7-9=OVR-BDIS*
Of course, you can modify the output to be just a flag or anything else - this is just what suited my own needs.
HTH

Create a NZPLSQL to find min(date) and a max(date) for a group of fields that serve as a key

I would like to know how I can execute a select from a procedure stored in Netezza.
I currently have the procedure under construction and I read that the raise notice serves to print on screen, however I do not know how (or if you can print a table in sql) just as if you were doing a query in traditional sql.
What the query is trying to do is that for a record,
COUNT (*) AS N_REPETITIONS,
CDS.CASO_PACIENTE_FK,
CDS.RESIVALITY_SPECIALITY_COD,
CDS.CASO_NIVEL_ATENCION_TIPO,
CDS.CASO_TIPO_PRESTACION_FK,
CDS.DIVING_DATE_DAY,
CDS.SALID_DATE_DAY
and that it gives me MIN(CDS.DERIVACION_FECHA_HORA) and MAX(CDS.SALIDA_FECHA_HORA) based on a key that are the fields.
If I have dates between MIN(CDS.DERIVACION_FECHA_HORA) and MAX(CDS.SALIDA_FECHA_HORA) for a CDS.CASO_PACIENTE_FK, i want that register added to number of register for each CDS.CASO_PACIENTE_FK
Here the code:
CREATE OR REPLACE PROCEDURE SP_ANALISIS_DUPLICADOS()
RETURNS REFTABLE(NETEZZA_PROD_LISTA_ESPERA_NOGES.LLAVE_CASOS_DUPLICADOS)
EXECUTE AS OWNER
LANGUAGE NZPLSQL AS
BEGIN_PROC
DECLARE
num_args INTEGER;
I am just starting to work with Netezza.
Thank you in advance to whom you answer.
This is the code:
CREATE OR REPLACE PROCEDURE SP_ANALISIS_DUPLICADOS()
RETURNS REFTABLE(NETEZZA_PROD_LISTA_ESPERA_NOGES.LLAVE_CASOS_DUPLICADOS)
EXECUTE AS OWNER
LANGUAGE NZPLSQL AS
BEGIN_PROC
DECLARE
num_args INTEGER;
typ oid;
idx INTEGER;
N_REPETICIONES NUMERIC(19);
CASO_PACIENTE_FK NUMERIC(19);
DERIVACION_ESPECIALIDAD_COD VARCHAR(255);
CASO_NIVEL_ATENCION_TIPO NUMERIC(19);
CASO_TIPO_PRESTACION_FK NUMERIC(19);
DERIVACION_FECHA_HORA TIMESTAMP;
SALIDA_FECHA_HORA TIMESTAMP;
--CURSOR FECHA_HORA_DERIVACION_MIN;
--CURSOR FECHA_HORA_SALIDA_MAX;
CURSOR CASO_PACIENTE_FK;
CURSOR DERIVACION_ESPECIALIDAD_COD;
CURSOR CASO_NIVEL_ATENCION_TIPO;
CURSOR CASO_TIPO_PRESTACION_FK;
--L_CONDITIONS VARCHAR(1000);
--Duplicados CNE MIN
BEGIN
num_args := PROC_ARGUMENT_TYPES.count;
FOR CASO_PACIENTE_FK IN
num_args := CASO_PACIENTE_FK.count;
--RAISE NOTICE ’Number of arguments: %’, num_args;
for i IN 0
CASO_PACIENTE_FK.count - 1 LOOP
typ := CASO_PACIENTE_FK(i);
idx := i+1;
FOR DERIVACION_ESPECIALIDAD_COD IN
num_args := DERIVACION_ESPECIALIDAD_COD.count;
for j IN 0
DERIVACION_ESPECIALIDAD_COD.count - 1 LOOP
typ := DERIVACION_ESPECIALIDAD_COD(j);
idx := j+1;
FOR CASO_NIVEL_ATENCION_TIPO IN
num_args := CASO_PACIENTE_FK.count;
--RAISE NOTICE ’Number of arguments: %’, num_args;
for k IN 0
CASO_NIVEL_ATENCION_TIPO.count - 1 LOOP
typ := CASO_NIVEL_ATENCION_TIPO(k);
idx := k+1;
FOR CASO_TIPO_PRESTACION_FK IN
num_args := CASO_TIPO_PRESTACION_FK.count;
--RAISE NOTICE ’Number of arguments: %’, num_args;
for a IN 0
CASO_TIPO_PRESTACION_FK.count - 1 LOOP
typ := CASO_TIPO_PRESTACION_FK(a);
idx := a+1;
--RAISE NOTICE ’argument $% is type % and has the value ’’%’’’,
--idx, typ, $idx;
F FLAG = 1 THEN
IF Fecha_Entrada = SELECT COUNT(*) AS N_REPETICIONES, MIN(CDS.DERIVACION_FECHA_HORA) AS FECHA_HORA_DERIVACION_MIN
FROM NETEZZA_PROD_SIGTE_DIARIO.SIG_CASO_DERIVACION_SALIDA CDS
WHERE (CDS.CASO_TIPO_PRESTACION_FK=1
OR CDS.CASO_TIPO_PRESTACION_FK=2)
--AND CDS.DERIVACION_FECHA_HORA >= CDS.DERIVACION_FECHA_HORA
GROUP BY CDS.CASO_PACIENTE_FK, CDS.DERIVACION_ESPECIALIDAD_COD, /*CDS.DERIVACION_ESTABLECIMIENTO_COD*/
CDS.CASO_NIVEL_ATENCION_TIPO, /*CDS.DERIVACION_FECHA_HORA,*/ CDS.CASO_TIPO_PRESTACION_FK, CDS.DERIVACION_FECHA_HORA,
CDS.SALIDA_FECHA_HORA
HAVING COUNT(*)>1
ORDER BY N_REPETICIONES, CDS.CASO_PACIENTE_FK, CDS.DERIVACION_FECHA_HORA, CDS.SALIDA_FECHA_HORA DESC
THEN Fecha_Entrada = 'TRUE';
ELSE FLAG = 0 THEN
Fecha_Entrada = 'FALSE'
F FLAG = 1 THEN
IF Fecha_Salida = SELECT COUNT(*) AS N_REPETICIONES, MAX(CDS.SALIDA_FECHA_HORA) AS FECHA_HORA_DERIVACION_MIN
FROM NETEZZA_PROD_SIGTE_DIARIO.SIG_CASO_DERIVACION_SALIDA CDS
WHERE (CDS.CASO_TIPO_PRESTACION_FK=1
OR CDS.CASO_TIPO_PRESTACION_FK=2)
--AND CDS.DERIVACION_FECHA_HORA >= CDS.DERIVACION_FECHA_HORA
GROUP BY CDS.CASO_PACIENTE_FK, CDS.DERIVACION_ESPECIALIDAD_COD, /*CDS.DERIVACION_ESTABLECIMIENTO_COD*/
CDS.CASO_NIVEL_ATENCION_TIPO, /*CDS.DERIVACION_FECHA_HORA,*/ CDS.CASO_TIPO_PRESTACION_FK, CDS.DERIVACION_FECHA_HORA,
CDS.SALIDA_FECHA_HORA
HAVING COUNT(*)>1
ORDER BY N_REPETICIONES, CDS.CASO_PACIENTE_FK, CDS.DERIVACION_FECHA_HORA, CDS.SALIDA_FECHA_HORA DESC
THEN Fecha_Salida = 'TRUE';
ELSE FLAG = 0 THEN
Fecha_Salida = 'FALSE'
END LOOP;
END LOOP;
END LOOP;
END LOOP;
RETURN REFTABLE;
RAISE NOTICE 'Hello, %', MYNAME; --Right Here must go the Println (Select field1, field2, etc.....)
--END;
--COMMIT;
END;
END_PROC;

Plpgsql : non recursive category tree function doesn't return any rows :(

So, here's the function that would return the products of a given category and its child categories. Its a 3 level tree. The function is fine, but when i run it, it says 0 rows returned. Any ideas?
EDIT: parents1 and parents2 are supposed to be arrays of the children of the parent category. Parents1 children of $1 , and parents2 children of all parents1 nodes.
CREATE OR REPLACE FUNCTION ecommerce.select_products_by_category(par bigint)
RETURNS SETOF ecommerce.product AS
$BODY$
declare
result ecommerce.product;
parents bigint[];
parents2 bigint[];
i int;
begin
parents := array(
select category_id from ecommerce.category where parent_id = par
);
return query select * from ecommerce.product where category_id = par;
for i in 1..array_upper(parents,1)
loop
return query select * from ecommerce.product where category_id = parents[i];
raise notice 'p %',parents[i];
end loop;
for i in 1..array_upper(parents,1)
loop
parents2 := array(
select category_id from ecommerce.category where parent_id = parents[i]
);
end loop;
for i in 1..array_upper(parents2,1)
loop
return query select * from ecommerce.product where category_id = parents2[i];
raise notice 'p2 %',parents2[i];
end loop;
--return query theset;
end ; $BODY$
and this is how i run it
SELECT * From ecommerce.select_products_by_category(
1
);
Your code cannot to work. The result of table function is related with any individual CALL of table function, not with global CALL of table function. Any table returning recursive function have to have use a pattern:
CREATE OR REPLACE FUNCTION foo(_parent_id integer)
RETURNS TABLE (node_id integer, node_val, parent_id integer) AS $$
BEGIN
FOR node_id, node_val, parent_id IN
SELECT f.node_id, f.node_val, f.parent_id
FROM footab f
WHERE f.parent_id = _parent_id
LOOP
RETURN NEXT;
/*
* Copy result of recursive call to function result
*/
RETURN QUERY SELECT * FROM foo(node_id);
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;
When you use CTE, then then you can get result little bit faster and the code will be more readable:
WITH RECURSIVE t AS (
SELECT node_id, node_val, parent_id FROM footab
WHERE parent_id = <<root_id>>
UNION ALL
SELECT node_id, node_val, parent_id FROM footab, t
WHERE footab.parent_id = t.id
) SELECT * FROM t;