How can I omit records when returning a table in postgresql? - postgresql

I have a problem when I try to execute a function where, depending on the result of another function, it returns a record or not. What I want to do is that when the function ("function_typecar" in the example) returns 1 as a result, it returns the record, otherwise no. The problem is when the condition is not met. It repeats the same record until there is one that meets the condition again. Is there a way to skip returning the records when the condition is not met? I leave an example of this situation
CREATE OR REPLACE FUNCTION schema.function_test
RETURNS TABLE(id_r INTEGER, name_r CHARACTER VARYING, year_r INTEGER) AS
$BODY$
DECLARE
sql_record RECORD;
type_car INTEGER;
BEGIN
SELECT id, name, year
FROM car
FOR sql_record IN
SELECT id, name, year
FROM car
LOOP
SELECT type INTO type_car FROM function_typecar(sql_record.id);
IF type_car = 1 THEN
id_r := sql_record.id;
name_r := sql_record.name;
year_r := sql_record.year;
END IF;
RETURN NEXT;
END LOOP;
END; $BODY$
This is the result that I want to avoid and that only brings me those that meet the IF condition:

Something like this should work, without a LOOP and in plain SQL:
CREATE OR REPLACE FUNCTION function_test()
RETURNS TABLE(id_r INTEGER, name_r CHARACTER VARYING, year_r INTEGER)
LANGUAGE SQL
AS
$BODY$
SELECT id, name, year
FROM car
JOIN function_typecar(id) ON type = 1;
$BODY$;

Related

Query on Return Statement - PostgreSQL

I have this question, I was doing some migration from SQL Server to PostgreSQL 12.
The scenario, I am trying to accomplish:
The function should have a RETURN Statement, be it with SETOF 'tableType' or RETURN TABLE ( some number of columns )
The body starts with a count of records, if there is no record found based on input parameters, then simply Return Zero (0), else, return the entire set of record defined in the RETURN Statement.
The Equivalent part in SQL Server or Oracle is: They can just put a SELECT Statement inside a Procedure to accomplish this. But, its a kind of difficult in case of PostgreSQL.
Any suggestion, please.
What I could accomplish still now - If no record found, it will simply return NULL, may be using PERFORM, or may be selecting NULL as column name for the returning tableType columns.
I hope I am clear !
What I want is something like -
============================================================
CREATE OR REPLACE FUNCTION public.get_some_data(
id integer)
RETURNS TABLE ( id_1 integer, name character varying )
LANGUAGE 'plpgsql'
AS $BODY$
DECLARE
p_id alias for $1;
v_cnt integer:=0;
BEGIN
SELECT COUNT(1) FROM public.exampleTable e
WHERE id::integer = e.id::integer;
IF v_cnt= 0 THEN
SELECT 0;
ELSE
SELECT
a.id, a.name
public.exampleTable a
where a.id = p_id;
END;
$BODY$;
If you just want to return a set of a single table, using returns setof some_table is indeed the easiest way. The most basic SQL function to do that would be:
create function get_data()
returns setof some_table
as
$$
select *
from some_table;
$$
language sql;
PL/pgSQL isn't really necessary to put a SELECT statement into a function, but if you need to do other things, you need to use RETURN QUERY in a PL/pgSQL function:
create function get_data()
returns setof some_table
as
$$
begin
return query
select *
from some_table;
end;
$$
language plpgsql;
A function as exactly one return type. You can't have a function that sometimes returns an integer and sometimes returns thousands of rows with a dozen columns.
The only thing you could do, if you insist on returning something is something like this:
create function get_data()
returns setof some_table
as
$$
begin
return query
select *
from some_table;
if not found then
return query
select (null::some_table).*;
end if;
end;
$$
language plpgsql;
But I would consider the above an extremely ugly and confusing (not to say stupid) solution. I certainly wouldn't let that pass through a code review.
The caller of the function can test if something was returned in the same way I implemented that ugly hack: check the found variable after using the function.
One more hack to get as close as possible to what you want. But I will repeat what others have told you: You cannot do what you want directly. Just because MS SQL Server lets you get away poor coding does not mean Postgres is obligated to do so. As the link by #a_horse_with_no_name implies converting code is easy, once you migrate how you think about the problem in the first place. The closest you can get is return a tuple with a 0 id. The following is one way.
create or replace function public.get_some_data(
p_id integer)
returns table ( id integer, name character varying )
language plpgsql
as $$
declare
v_at_least_one boolean = false;
v_exp_rec record;
begin
for v_exp_rec in
select a.id, a.name
from public.exampletable a
where a.id = p_id
union all
select 0,null
loop
if v_exp_rec.id::integer > 0
or (v_exp_rec.id::integer = 0 and not v_at_least_one)
then
id = v_exp_rec.id;
name = v_exp_rec.name;
return next;
v_at_least_one = true;
end if;
end loop ;
return;
end
$$;
But that is still just a hack and assumes there in not valid row with id=0. A much better approach would by for the calling routing to check what the function returns (it has to do that in one way or another anyway) and let the function just return the data found instead of making up data. That is that mindset shift. Doing that you can reduce this function to a simple select statement:
create or replace function public.get_some_data2(
p_id integer)
returns table ( id integer, name character varying )
language sql strict
as $$
select a.id, a.name
from public.exampletable a
where a.id = p_id;
$$;
Or one of the other solutions offered.

In clause in postgres

Need Output from table with in clause in PostgreSQL
I tried to make loop or ids passed from my code. I did same to update the rows dynamically, but for select I m not getting values from DB
CREATE OR REPLACE FUNCTION dashboard.rspgetpendingdispatchbyaccountgroupidandbranchid(
IN accountgroupIdCol numeric(8,0),
IN branchidcol character varying
)
RETURNS void
AS
$$
DECLARE
ArrayText text[];
i int;
BEGIN
select string_to_array(branchidcol, ',') into ArrayText;
i := 1;
loop
if i > array_upper(ArrayText, 1) then
exit;
else
SELECT
pd.branchid,pd.totallr,pd.totalarticle,pd.totalweight,
pd.totalamount
FROM dashboard.pendingdispatch AS pd
WHERE
pd.accountgroupid = accountgroupIdCol AND pd.branchid IN(ArrayText[i]::numeric);
i := i + 1;
end if;
END LOOP;
END;
$$ LANGUAGE 'plpgsql' VOLATILE;
There is no need for a loop (or PL/pgSQL actually)
You can use the array directly in the query, e.g.:
where pd.branchid = any (string_to_array(branchidcol, ','));
But your function does not return anything, so obviously you won't get a result.
If you want to return the result of that SELECT query, you need to define the function as returns table (...) and then use return query - or even better make it a SQL function:
CREATE OR REPLACE FUNCTION dashboard.rspgetpendingdispatchbyaccountgroupidandbranchid(
IN accountgroupIdCol numeric(8,0),
IN branchidcol character varying )
RETURNS table(branchid integer, totallr integer, totalarticle integer, totalweight numeric, totalamount integer)
AS
$$
SELECT pd.branchid,pd.totallr,pd.totalarticle,pd.totalweight, pd.totalamount
FROM dashboard.pendingdispatch AS pd
WHERE pd.accountgroupid = accountgroupIdCol
AND pd.branchid = any (string_to_array(branchidcol, ',')::numeric[]);
$$
LANGUAGE sql
VOLATILE;
Note that I guessed the data types for the columns of the query based on their names. You have to adjust the line with returns table (...) to match the data types of the select columns.

postgresql create function dynamically insert value into different table

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

How to clone a RECORD in PostgreSQL

I want to loop through a query, but also retain the actual record for the next loop, so I can compare two adjacent rows.
CREATE OR REPLACE FUNCTION public.test ()
RETURNS void AS
$body$
DECLARE
previous RECORD;
actual RECORD;
query TEXT;
isdistinct BOOLEAN;
tablename VARCHAR;
columnname VARCHAR;
firstrow BOOLEAN DEFAULT TRUE;
BEGIN
tablename = 'naplo.esemeny';
columnname = 'esemeny_id';
query = 'SELECT * FROM ' || tablename || ' LIMIT 2';
FOR actual IN EXECUTE query LOOP
--do stuff
--save previous record
IF NOT firstrow THEN
EXECUTE 'SELECT ($1).' || columnname || ' IS DISTINCT FROM ($2).' || columnname
INTO isdistinct USING previous, actual;
RAISE NOTICE 'previous: %', previous.esemeny_id;
RAISE NOTICE 'actual: %', actual.esemeny_id;
RAISE NOTICE 'isdistinct: %', isdistinct;
ELSE
firstrow = false;
END IF;
previous = actual;
END LOOP;
RETURN;
END;
$body$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER
COST 100;
The table:
CREATE TABLE naplo.esemeny (
esemeny_id SERIAL,
felhasznalo_id VARCHAR DEFAULT "current_user"() NOT NULL,
kotesszam VARCHAR(10),
idegen_azonosito INTEGER,
esemenytipus_id VARCHAR(10),
letrehozva TIMESTAMP WITHOUT TIME ZONE DEFAULT now() NOT NULL,
szoveg VARCHAR,
munkalap_id VARCHAR(13),
ajanlat_id INTEGER,
CONSTRAINT esemeny_pkey PRIMARY KEY(esemeny_id),
CONSTRAINT esemeny_fk_esemenytipus FOREIGN KEY (esemenytipus_id)
REFERENCES naplo.esemenytipus(esemenytipus_id)
ON DELETE RESTRICT
ON UPDATE RESTRICT
NOT DEFERRABLE
)
WITH (oids = true);
The code above doesn't work, the following error message is thrown:
ERROR: could not identify column "esemeny_id" in record data type
LINE 1: SELECT ($1).esemeny_id IS DISTINCT FROM ($2).esemeny_id
^
QUERY: SELECT ($1).esemeny_id IS DISTINCT FROM ($2).esemeny_id
CONTEXT: PL/pgSQL function "test" line 18 at EXECUTE statement
LOG: duration: 0.000 ms statement: SET DateStyle TO 'ISO'
What am I missing?
Disclaimer: I know the code doesn't make too much sense, I only created so I can demonstrate the problem.
This does not directly answer your question, and may be of no use at all, since you did not really describe your end goal.
If the end goal is to be able to compare the value of a column in the current row with the value of the same column in the previous row, then you might be much better off using a windowing query:
SELECT actual, previous
FROM (
SELECT mycolumn AS actual,
lag(mycolumn) OVER () AS previous
FROM mytable
ORDER BY somecriteria
) as q
WHERE previous IS NOT NULL
AND actual IS DISTINCT FROM previous
This example prints the rows where the current row is different from the previous row.
Note that I added an ORDER BY clause - it does not make sense to talk about "the previous row" without specifying ordering, otherwise you would get random results.
This is plain SQL, not PlPgSQL, but if you can wrap it in a function if you want to dynamically generate the query.
I am pretty sure, there is a better solution for your actual problem. But to answer the question asked, here is a solution with polymorphic types:
The main problem is that you need well known composite types to work with. the structure of anonymous records is undefined until assigned.
CREATE OR REPLACE FUNCTION public.test (actual anyelement, _col text
, OUT previous anyelement) AS
$func$
DECLARE
isdistinct bool;
BEGIN
FOR actual IN
EXECUTE format('SELECT * FROM %s LIMIT 3', pg_typeof(actual))
LOOP
EXECUTE format('SELECT ($1).%1$I IS DISTINCT FROM ($2).%1$I', _col)
INTO isdistinct
USING previous, actual;
RAISE NOTICE 'previous: %; actual: %; isdistinct: %'
, previous, actual, isdistinct;
previous := actual;
END LOOP;
previous := NULL; -- reset dummy output (optional)
END
$func$ LANGUAGE plpgsql;
Call:
SELECT public.test(NULL::naplo.esemeny, 'esemeny_id')
I am abusing an OUT parameter, since it's not possible to declare additional variables with a polymorphic composite type (at least I have failed repeatedly).
If your column name is stable you can replace the second EXECUTE with a simple expression.
I am running out of time, explanation in these related answers:
Declare variable of composite type in PostgreSQL using %TYPE
Refactor a PL/pgSQL function to return the output of various SELECT queries
Asides:
Don't quote the language name, it's an identifier, not a string.
Do you really need WITH (oids = true) in your table? This is still allowed, but largely deprecated in modern Postgres.

Returning result set from Postgres functions

In my Postgres 9.2 database, I need to build a function that takes several parameters, performs several queries, and then returns a data set that is composed of several rows and several columns. I've built several test functions to get a better grasp of Postgres' functionality, here is one:
CREATE OR REPLACE FUNCTION sql_with_rows11(id integer) RETURNS character varying AS
$BODY$
declare vid integer;
declare vendor character varying;
BEGIN
vid := (select v_id from public.gc_alerts where a_id = id);
vendor := (select v_name from public.gc_vendors where v_id = vid);
RETURN vendor;
END;
$BODY$
LANGUAGE plpgsql;
I know that I can combine this into one query, but this is more of a practice exercise. This works fine and I get the vendor name. However, I need to return more than one column from the gc_vendors table.
Ultimately, I need to return columns from several tables based on subqueries. I've looked into creating a result set function, but I believe it only returns one row at a time. I also looked into returning setof type, but that seems to be limited to existing tables.
After initial feedback, I changed the function to the following:
CREATE OR REPLACE FUNCTION sql_with_rows14(IN v_uid character varying, IN lid integer)
RETURNS table (aid int, aname character varying) AS
$BODY$
declare aid integer;
declare aname character varying;
BEGIN
sql_with_rows14.aid := (select a_id from public.gc_alerts where v_id = sql_with_rows14.v_uid);
sql_with_rows14.aname := (select a_name from public.gc_alerts where a_id = sql_with_rows14.aid);
RETURN;
END;
$BODY$
LANGUAGE plpgsql;
I also tried RETURN NEXT, but same results.
When I query it, if the query returns only one row, it works fine. However it doesn't work for multiple rows. I also tried something like this, with the same result:
...
BEGIN
sql_with_rows14.aid := (select a_id from public.gc_alerts);
sql_with_rows14.aname := (select a_name from public.gc_alerts);
RETURN NEXT;
END;
I need to return more than one column from the gc_vendors table
To return a single row with multiple fields (as opposed to a set of rows), you can either use:
RETURNS row_type
.. where row_type is a pre-defined composite type (like a table name, that serves as such automatically). Or:
RETURNS record
combined with OUT parameters. Be aware that OUT parameters are visible in the body almost everywhere and avoid naming conflicts.
Using the second option, your function could look like this:
CREATE OR REPLACE FUNCTION sql_with_columns(IN _id integer -- IN is optional default
, OUT vid integer
, OUT vendor text)
RETURNS record
LANGUAGE plpgsql AS
$func$
BEGIN
SELECT INTO vid v_id
FROM public.gc_alerts
WHERE a_id = id;
SELECT INTO vendor v_name
FROM public.gc_vendors
WHERE v_id = vid;
RETURN; -- just noise, since OUT parameters are returned automatically
END
$func$;
As you mentioned, you should combine both queries into one, or even use a plain SQL statement instead. This is just a show case. The excellent manual has all the details.
You can also use:
RETURNS TABLE (...)
Or:
RETURNS SETOF row_type
This allows to return a set of rows (0, 1 or many). But that's not in your question.
To get individual columns instead of a record representation, call the function with:
SELECT * FROM sql_with_columns(...);
There are lots of examples here on SO, try a search - maybe with additional key words.
Also read the chapter "Returning from a Function" in the manual.
First of all, consider using views or simple queries. I'd say that if you can process something with a simple query, you shouldn't create function for that. in your case, you can use this query
select
v.v_name, v.* -- or any other columns from gc_alerts or gc_vendors
from public.gc_alerts as a
inner join public.gc_vendors as v on v.v_id = a.vid
where a.a_id = <your id here>
if you want your function to return rows, you can declare it like
CREATE OR REPLACE FUNCTION sql_with_rows11(id integer)
RETURNS table(vendor text, v_id int)
as
$$
select
v.v_name, v.v_id
from public.gc_alerts as a
inner join public.gc_vendors as v on v.v_id = a.vid
where a.a_id = id
$$ language SQL;
or plpgsql function:
CREATE OR REPLACE FUNCTION sql_with_rows11(id integer)
RETURNS table(vendor text, vid int)
AS
$$
declare vid integer;
declare vendor character varying;
BEGIN
sql_with_rows11.vid := 1; -- prefix with function name because otherwise it would be declared variables
sql_with_rows11.vendor := 4;
return next;
sql_with_rows11.vid := 5;
sql_with_rows11.vendor := 8;
return next;
END;
$$ LANGUAGE plpgsql;
sql fiddle demo to fiddle with :)