Is it possible to create a function like this:
CREATE FUNCTION Testing(p_1 INTEGER DEFAULT NULL, p_2 INTEGER DEFAULT NULL, p_3 DATE DEFAULT current_date)
RETURNS TABLE
(
column_a INTEGER,
column_b INTEGER,
column_c INTEGER,
column_d DATE
)
LANGUAGE plpgsql
AS
$f$
BEGIN
RETURN QUERY
SELECT column_a, column_b, column_c, column_d
FROM table_test
WHERE column_d <= p_3
AND CASE WHEN NOT p_1 IS NULL THEN column_a = p_1 ELSE TRUE END
AND CASE WHEN NOT p_2 IS NULL THEN column_b = p_2 ELSE TRUE END;
END;
$f$;
And then call it specifying the parameter name, like:
SELECT * FROM Testing(p_2 = 23)
(If I try that, for instance, PostgreSQL returns "column "p_2" does not exist")
Accordnig to the documentation:
In named notation, each argument's name is specified using => to separate it from the argument expression.
So the correct way is:
SELECT * FROM Testing(p_2 => 23)
The function however is not perfect. Column names in the returning table are the same as those used in the query, so they are ambiguous.
Use an alias in the query:
CREATE FUNCTION Testing(p_1 INTEGER DEFAULT NULL, p_2 INTEGER DEFAULT NULL, p_3 DATE DEFAULT current_date)
RETURNS TABLE
(
column_a INTEGER,
column_b INTEGER,
column_c INTEGER,
column_d DATE
)
LANGUAGE plpgsql
AS
$f$
BEGIN
RETURN QUERY
SELECT t.column_a, t.column_b, t.column_c, t.column_d
FROM table_test t
WHERE t.column_d <= p_3
AND CASE WHEN NOT p_1 IS NULL THEN t.column_a = p_1 ELSE TRUE END
AND CASE WHEN NOT p_2 IS NULL THEN t.column_b = p_2 ELSE TRUE END;
END;
$f$;
Alternatively, use SETOF if the table has only four columns:
CREATE FUNCTION Testing(p_1 INTEGER DEFAULT NULL, p_2 INTEGER DEFAULT NULL, p_3 DATE DEFAULT current_date)
RETURNS SETOF table_test
LANGUAGE plpgsql
AS
$f$
BEGIN
RETURN QUERY
SELECT column_a, column_b, column_c, column_d
FROM table_test
WHERE column_d <= p_3
AND CASE WHEN NOT p_1 IS NULL THEN column_a = p_1 ELSE TRUE END
AND CASE WHEN NOT p_2 IS NULL THEN column_b = p_2 ELSE TRUE END;
END;
$f$;
Related
CREATE OR REPLACE FUNCTION public.getappointmentwithfilter(
userid integer,
periodid integer)
RETURNS TABLE(user_id integer,
scheduleid integer,
member_id varchar,
appoinmentdate timestamp,
membername character varying
)
LANGUAGE 'plpgsql'
AS $BODY$
BEGIN
RETURN QUERY
SELECT
a.userid ,
a.schedule_id,
a.memberid,
a.appoinment_date,
p.name
FROM
member_appointment_details a
LEFT OUTER JOIN
patients p
ON
p.patientid=a.memberid
WHERE
a.userid=userid
AND
CASE
WHEN periodid = 1 THEN
date(a.appoinment_date) = date(now())
WHEN periodid = 2 THEN
date('week',a.appoinment_date) = date('week',CURRENT_TIMESTAMP)
WHEN periodid = 3 THEN
date_part('month',a.appoinment_date) = date_part('month',CURRENT_TIMESTAMP)
ELSE
userid=usrid::integer
END
END;
$BODY$;
I want to have case for date week month in this function. I tried like this but got error!
I am trying to build a slow changing dimensional table, it track all the history of records. The schema of the table is like this:
CREATE TABLE test.dim
(id text,
column1 text,
column2 text,
begin_date timestamp without time zone,
is_current boolean,
end_date timestamp without time zone)
I defined a trigger function, and fire it before each insert action:
CREATE OR REPLACE FUNCTION test.slow_change_func()
RETURNS trigger AS
$BODY$
DECLARE
BEGIN
IF ( NOT EXISTS ( SELECT 1 FROM yang_test.dim
WHERE id= NEW.id
AND(column1 = NEW.column1 OR (column1 is null AND NEW.column1 is null))
AND (column2 = NEW.column2 OR (column2 is null AND NEW.column2 is null))
AND is_current
)
)
THEN UPDATE yang_test.dim
SET (end_date, is_current) = (now(), FALSE)
WHERE id = NEW.id
AND is_current;
INSERT INTO test.dim (id, column1, column2, begin_date, is_current, end_date)
VALUES ( NEW.id, NEW.column1, NEW.column2, now(), TRUE, 'infinity'::timestamp );
END IF;
RETURN NULL;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
CREATE TRIGGER slow_change_trigger
BEFORE INSERT
ON test.dim
FOR EACH ROW
EXECUTE PROCEDURE test.slow_change_func();
When I try to test it,
INSERT INTO test.dim (id, column1, column2, begin_date, is_current, end_date)
VALUES ( 1, 'hello', 'world', now(), TRUE, 'infinity'::timestamp )
it will throw an error: stack depth limit exceeded. it looks like the function is running a loop. any suggestion s?
I think I have figure this out, this will match my requirement:
CREATE OR REPLACE FUNCTION yang_test.slow_change_func()
RETURNS trigger AS
$BODY$
DECLARE
BEGIN
IF ( NOT EXISTS ( SELECT 1 FROM yang_test.dim
WHERE id= NEW.id
AND(column1 = NEW.column1 OR (column1 is null AND NEW.column1 is null))
AND (column2 = NEW.column2 OR (column2 is null AND NEW.column2 is null))
AND is_current
)
)
THEN UPDATE yang_test.dim
SET (end_date, is_current) = (now(), FALSE)
WHERE id = NEW.id
AND is_current;
ELSE RETURN null;
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
Now I have the following procedure:
CREATE OR REPLACE FUNCTION find_city_by_name(match varchar) RETURNS TABLE(city_name varchar) LANGUAGE plpgsql as $$
BEGIN
RETURN QUERY WITH r AS (
SELECT short_name FROM geo_cities WHERE short_name ILIKE CONCAT(match, '%')
)
SELECT r.short_name FROM r;
END;
$$
I want return all fields (*) (not only short_name). What I need to change in my procedure?
Here is a simplified (w/o WITH and with language sql) version, that I've mentioned in my comment to the adjacent answer:
create or replace function find_city_by_name(text)
returns table(city_name varchar, long_name varchar)
as $$
select * from geo_cities where short_name ilike $1 || '%';
$$ language sql;
Also, you might find it more convenient to refer to the geo_cities table itself defining the function's signature, using SETOF geo_cities:
create or replace function find_city_by_name(text)
returns setof geo_cities
as $$
select * from geo_cities where short_name ilike $1 || '%';
$$ language sql;
-- this will allow you to change the structure of geo_cities table w/o necessity to change the function's definition.
If you want a real row you must to explicit declare all fields in the return clausule:
create table geo_cities (
short_name varchar,
long_name varchar
);
insert into geo_cities values ('BERLIN', 'BERLIN'), ('BERLIN 2','BERLIN TWO');
CREATE OR REPLACE FUNCTION find_city_by_name(match varchar)
RETURNS TABLE(city_name varchar, long_name varchar)
LANGUAGE plpgsql
AS
$$
BEGIN
RETURN QUERY WITH r AS (
SELECT * FROM geo_cities WHERE short_name ILIKE CONCAT(match, '%')
)
SELECT * FROM r;
END;
$$;
select * from find_city_by_name('BERLIN');
See the example running at: http://rextester.com/IKTT52978
I'm trying to create a function which will return setof record. I want to use the function as follows:
SELECT city_name FROM set_city(1, 1, 'ExampleName');
My function:
CREATE OR REPLACE FUNCTION set_city(_city_id integer, _country_id integer, _city_name varchar)
RETURNS SETOF RECORD
LANGUAGE plpgsql
as $$
DECLARE
result record;
BEGIN
IF EXISTS (SELECT 1 FROM geo_cities gc WHERE gc.id = _city_id)
THEN
UPDATE geo_cities
SET country_id = _country_id, city_name = _city_name
WHERE id = _city_id
RETURNING * INTO result;
ELSE
INSERT INTO geo_cities(id, country_id, city_name)
VALUES (_city_id, _country_id, _city_name)
RETURNING * INTO result;
END IF;
-- It's wrong
RETURN QUERY SELECT result;
END;
$$
What should I change?
You could change the return statement:
...
-- It's wrong
-- RETURN QUERY SELECT result;
RETURN NEXT result; -- that's good
...
However, a column definition list is required for functions returning "record", so you would have to add it in every query:
SELECT city_name FROM set_city(1, 1, 'ExampleName')
AS (id int, country_id int, city_name text);
In fact the function returns a single row of the type geo_cities and you do not need setof:
DROP FUNCTION set_city(_city_id integer, _country_id integer, _city_name varchar);
CREATE OR REPLACE FUNCTION set_city(_city_id integer, _country_id integer, _city_name varchar)
RETURNS geo_cities
LANGUAGE plpgsql
as $$
DECLARE
result geo_cities;
BEGIN
IF EXISTS (SELECT 1 FROM geo_cities gc WHERE gc.id = _city_id)
THEN
UPDATE geo_cities
SET country_id = _country_id, city_name = _city_name
WHERE id = _city_id
RETURNING * INTO result;
ELSE
INSERT INTO geo_cities(id, country_id, city_name)
VALUES (_city_id, _country_id, _city_name)
RETURNING * INTO result;
END IF;
RETURN result;
END;
$$;
SELECT city_name FROM set_city(1, 1, 'ExampleName');
Note that you can get the same functionality in a single SQL statement:
INSERT INTO geo_cities(id, country_id, city_name)
VALUES (1, 1, 'ExampleName')
ON CONFLICT (id) DO UPDATE SET
country_id = excluded.country_id,
city_name = excluded.city_name
RETURNING *;
CREATE OR REPLACE FUNCTION employee(IN emp_id integer)
RETURNS TABLE(id integer, name text, designation text, salary integer, man_id integer) AS
$BODY$
BEGIN
RETURN QUERY
WITH recursive manager_hierarchy(e_id,e_name,e_desig,e_sal,m_id) AS
(
select e.id,e.name,e.designation,e.salary,e.man_id FROM emp_table e
WHERE e.id=emp_id
union
SELECT
rp.id,rp.name,rp.designation,rp.salary,rp.man_id
FROM
manager_hierarchy mh
INNER JOIN emp_table rp ON mh.e_id=rp.man_id
)
SELECT h.e_id,h.e_name,h.e_desig,h.e_sal,h.m_id
FROM manager_hierarchy h;
END;
$BODY$
LANGUAGE plpgsql
can anyone help me to change the same program with using loop