I am building function and I want it to use my another function, that returns table. I want to fill temporary table with result of this function and apparently this causes error. Code Below (function is under construction, so it is only part of code):
CREATE OR REPLACE FUNCTION energy.map_wit_messages_hourly()
RETURNS TABLE (_t_id text)
AS $$
BEGIN
CREATE TEMP TABLE tmp_final ON COMMIT DROP AS
SELECT * FROM map_wit_messages_hourly_for_specific_day('2017-09-02',
'2017-12-01 09:00:00', 'PL');
RETURN QUERY
SELECT a._time_id FROM tmp_final a;
END
$$ LANGUAGE plpgsql;
Does anyone knows solution of this problem?
Related
today i faced with a strange problem. i want to invoke a plpgsql function inside a view(i know it is a bad and strange way to call a function so please don't mention it in your answers). i do that but the results of function apply after showing the results of view!!!
i want to know how it is possible? and how i can force pgpgsql function to store its result before finishing the query?
some information about my code:
plan: this is a table about travel plans including date_of_travel, price etc.
travel_check(): this is a function that deletes those records from plan table that their date_of_travel is less than current_date and store them to another table and return an integer.(this is not important so much just remember this function delete some records from plan table) but i write exact definition below:
create or replace function finish_travel(todelete integer) returns void as
$body$
begin
create table if not exists history_of_travel(
id integer,
traveldate date,
source_id char(3),
destination_id char(3),
timeofday time
);
insert into history_of_travel
select id,traveldate,source_id,destination_id,timeofday
from plan
where id=todelete;
delete from plan where id=todelete;
end;
$body$
language plpgsql volatile;
create or replace function travel_check() returns int as
$body$
declare
trip record;
begin
for trip in select *
from plan
loop
if(trip.traveldate<current_date) then
perform finish_travel(trip.id);
end if;
if(trip.traveldate=current_date and trip.timeofday=now()::timestamp(0)::time) then
perform finish_travel(trip.id);
end if;
end loop;
return 1;
end;
$body$
language plpgsql volatile;
i want to create a view containing two step:
call function and update plan.
show the plan's records.
i tried below code
create view clients_select_query as
select plan.*
from plan,(select travel_check()) as d
where exists(select * from plan)
when i run:
select * from clients_select_query
unfortunately it shows the contents of plan table without any change
but if i run again:
select * from clients_select_query
or
select * from plan
i see that the changes have been applied.
how i can see changes after first running query without changing method?
if it's not clear, tell me to put exact definition of function,table and view
The result isn't "saved late", but Postgres hides the changes from you, so that queries behave predictably.
Postgres' concurrency control means that a query won't see any changes after it started running. This is true even if your own query is making the changes, and there is no way to avoid it.
Instead, you could create a set-returning function which does the deletion first, and then returns the contents of the table using a second query. Then, create a view which selects from that function.
create function clients_select_function() returns setof plan as $$
select travel_check();
select * from plan;
$$
language sql;
create view clients_select_query as
select * from clients_select_function();
create or replace function pd.check(
interval_ text[])
returns void as
$BODY$
BEGIN
EXECUTE '
drop table if exists check_;
create temp table check_
as
(
select unnest(' || interval_ || ')
) ;
';
END;
$BODY$
LANGUAGE PLPGSQL volatile;
I am running it as
select pd.check(ARRAY['2','3','4']);
It gives me an error :
operator is not unique: unknown || text[]
HINT: Could not choose a best candidate operator. You may need to add explicit type casts.
Assuming current Postgres 9.6, your function would work like this:
CREATE OR REPLACE FUNCTION pd.check(interval_ text[])
RETURNS void AS
$func$
BEGIN
EXECUTE '
DROP TABLE IF EXISTS pg_temp.check_;
CREATE TEMP TABLE check_ AS
SELECT unnest($1)'
USING $1; -- pass as value
END
$func$ LANGUAGE plpgsql;
Notes
You could concatenate the parameter value as string, but then you need an explicit type cast (since concatenating an untyped string literal and text[] is ambiguous to Postgres, it might produce text or text[], hence the error!) and escape special characters to make it work. quote_literal() does both: quote_literal(interval_). Still, don't.
Instead, pass the parameter as value, not as string. That's faster and safer and avoids any such error as you show.
Note that $1 in the command string refers to the first expression provided by the USING clause, not to the function parameter. The 2nd instance of $1 actually refers to the function parameter (different scope!).
Note, that this superior way of passing values to DML statements works here because it's part of the included SELECT statement, but not for other utility commands. Details:
“ERROR: there is no parameter $1” in “EXECUTE .. USING ..;” statement in plpgsql
drop table check_; in a function is dangerous. Obviously you want to target the temporary table. But if that should not exist, the next available table of the same name in the search_path would be dropped. Potentially catastrophic damage. To target the temp table and no other, schema-qualify with the pseudo-name pg_temp.
CREATE TABLE AS does not require parentheses around a following SELECT command.
VOLATILE is the default (and correct for this function), I omitted the noise.
In this particular case you would not need dynamic SQL. See #klin's answer. But EXECUTE is still a good choice for queries that have nothing to gain from plan caching.
You do not need a dynamic SQL (execute):
create or replace function pd.check(interval_ text[])
returns void as
$body$
begin
drop table if exists check_;
create temp table check_
as select unnest(interval_);
end;
$body$
language plpgsql volatile;
Test:
select pd.check(array['2','3','4']);
select * from check_;
unnest
--------
2
3
4
(3 rows)
This is similar to the other answer, but you really shouldn't be using RETURNS VOID; you should be returning a set from the function itself.
CREATE OR REPLACE FUNCTION pd.check(
intervals_ TEXT[] )
RETURNS TABLE ( pd_interval TEXT )
$f$
BEGIN
RETURN QUERY SELECT UNNEST(intervals_);
END;
$f$ LANGUAGE plpgsql IMMUTABLE;
postgres=# select * from pd_check(ARRAY['1','2','3']);
pd_interval
-------------
1
2
3
(3 rows)
Other changes I made:
You shouldn't name a column after a different data type than the column itself, hence changing interval_ to intervals_.
The function is actually IMMUTABLE, not VOLATILE, as-is.
Is it possible, and if so how, to pass data to a table-valued parameter of a stored function using SQL EXEC?
I know how to pass in data from C#. One of my four stored procs using table-valued parameters is not producing the expected results. I'd like to execute my proc from SQL server management studio for debugging purposes, but I am unable to find the correct syntax for doing so, if such a syntax even exists. I haven't found anything relevant in the docs.
My type table:
CREATE TYPE [MyNameSpace].[MyTypeTable] AS TABLE(
//... all my fields
)
My stored proc:
//... bunch of stuff
ALTER PROCEDURE [MyNameSpace].[MyStoredProc]
#MyTypeTableVar MyTypeTable READONLY
AS
BEGIN
//Do a bunch of stuff
//Want to test the stuff in here
END
I have tried:
IF OBJECT_ID('tempdb.dbo.#MyTempTable') IS NOT NULL DROP TABLE tempdb.dbo.#MyTempTable;
select top 0 *
into #MyTempTable
//existing table with structure that matches the table-valued param
from MyNameSpace.MyTable;
//...Long insert statement assigning test data to #MyTempTable
EXECUTE MyNameSpace.MyStoredProc #MyTypeTableVar = #MyTempTable;
which throws:
Operand type clash: nvarchar is incompatible with MyTypeTable
You can't use a temp table - you have to use a table variable:
declare #t [MyNameSpace].[MyTypeTable]
insert into #t (/*columns*/) values
(/* first row */),
(/* second row */)
EXECUTE MyNameSpace.MyStoredProc #MyTypeTableVar = #t;
(You can populate it with either INSERT ... VALUES as shown above or INSERT ... SELECT if you have an existing table containing the data you care about)
Here's a working example:
-- Declare a table parameter
DECLARE #registryUpdates AS typ_KeyValuePairStringTable;
-- Insert one row
INSERT INTO #registryUpdates
VALUES ('Hello', 'World');
-- Call Stored Procedure
EXEC prc_UpdateRegistry #registryUpdates
I have a stored procedure that RETURNS record:
CREATE OR REPLACE FUNCTION r_rpt_prov_summary()
RETURNS record
AS $$
DECLARE
_fields record;
BEGIN
SELECT INTO _fields
0::integer AS customers,
0::integer AS customers_active;
SELECT INTO _fields.customers count(*) FROM customer;
SELECT INTO _fields.customers_active count(*) FROM customer WHERE active = 't';
RETURN _fields;
END
$$ LANGUAGE 'plpgsql';
However, in order to query it, I would have to explicitly enumerate the returned columns and types:
SELECT * FROM r_rpt_prov_summary() AS (a integer, b integer);
In order to make this palatable to an MVC framework, which by its nature wants to query tables, I wrap it in an SQL function that RETURNS TABLE:
CREATE OR REPLACE FUNCTION rpt_prov_summary()
RETURNS TABLE (
customers integer,
customers_active integer
) AS $$
SELECT * FROM r_rpt_prov_summary() AS (customers integer, customers_active integer);
$$ LANGUAGE 'sql';
This works very well as long as both functions reside in the same schema or search_path space. However, in this case, they live in a nonstandard schema of their own, so I have to query the outer function as myschema.rpt_prov_summary(), i.e.
SELECT * FROM myschema.rpt_prov_summary();
This doesn't work if my schema and search path are set to public:
test=> SELECT * FROM myschema.rpt_prov_summary();
ERROR: function r_rpt_prov_summary() does not exist
LINE 2: SELECT * FROM r_rpt_prov_summary() AS (customers integer, ...
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
Naturally, your thoughts would turn to calling SET SCHEMA 'myschema' or SET search_path TO myschema prior to query execution. So did mine. The problem is it's just not going to work for the environment from which I'm calling. The MVC framework is using prepared statements to construct the query, and PostgreSQL frowns upon something like:
SET search_path TO myschema;SELECT * FROM rpt_prov_summary();
Which is to say:
< 2016-03-14 20:50:46.410 EDT >ERROR: cannot insert multiple commands into a prepared statement
So, that's not going to work. Providing the schema as an argument to the outer function isn't going to work, either; I just don't have that flexibility within the constraints of the MVC framework, which wants to be querying plain old tables or things that act like tables.
current_schema identifies the current schema of the client session, so that's not going to help. Already tried this:
CREATE OR REPLACE FUNCTION rpt_prov_summary()
RETURNS TABLE (
customers integer,
customers_active integer
) AS $$
BEGIN
RAISE NOTICE 'Current schema: %', current_schema;
RETURN QUERY EXECUTE 'SELECT * FROM ' || current_schema || '.r_rpt_prov_summary() AS (customers integer, customers_active integer)';
END
$$ LANGUAGE 'plpgsql';
No dice - Current schema: public, as expected.
Is there any way to somehow capture the schema space of the outer call and propagate that into the encapsulated query?
1.
I have a stored procedure ...
No, you don't. You have a function, which is almost but not quite the same. Postgres doesn't currently support stored procedures.
2.
Radically simplify. Instead of your two nested functions, use one simple SQL function with OUT parameters:
CREATE OR REPLACE FUNCTION myschema.r_rpt_prov_summary( -- schema-qualify!
OUT _customers integer
, OUT _customers_active integer) AS
$func$
SELECT count(*)::int
, count(*) FILTER (WHERE active)::int -- requires Postgres 9.4+
FROM myschema.customer; -- schema-qualify!
$func$ LANGUAGE sql; -- don't quote the language name
Call:
SELECT * FROM myschema.rpt_prov_summary();
Works independently of your current search_path setting.
Note a subtle difference between RETURNS TABLE() and RETURNS record (my version is also RETURNS record, but I didn't specify explicitly after declaring OUT parameters!): The later always returns exactly 1 row (and Postgres knows that and can rely on it), while the former can return 0 - n rows.
3.
You could set the search_path in many different ways, even as local setting to the function itself - if necessary:
How does the search_path influence identifier resolution and the "current schema"
To answer your actual question:
Is there any way to somehow capture the schema space of the outer call
and propagate that into the encapsulated query?
CREATE OR REPLACE FUNCTION myschema.r_rpt_prov_summary(OUT _customers integer
, OUT _customers_active integer) AS
$func$
BEGIN
SELECT INTO _customers, _customers_active
count(*)::int, count(*) FILTER (WHERE active)::int
FROM customer; -- no schema-qualification
END
$func$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION myschema.rpt_prov_summary()
RETURNS TABLE (customers int, customers_active int) AS
$func$
SELECT * FROM myschema.r_rpt_prov_summary();
$func$ LANGUAGE sql SET search_path = myschema; -- set the search_path here
The search_path is propagated to the nested function - just what you were looking for.
Or just schema-qualify identifiers everywhere (including function names) to be unambiguous.
I have a stored procedure I don't want to modify. It's rather large and complex, and I don't want to add any more confusion to it.
So what I would like to do is have another store procedure that calls on the big one, and uses the result set to perform further selects / joins etc.
You can insert procedure's result set into table. Like this:
create procedure test
as
begin
select 1
end
go
declare #t table
(
id int
)
insert into #t
exec test
select * from #t -- returns one row
You can use a user-defined function instead:
create function table_func
()
returns table
as
return
(
select top 10 *
from master..msreplication_options
)
Then, to get your result set
select * from table_func()
If you still need to call this as a stored proc in other places, create a stored proc that wraps the user-defined function:
create procedure test_proc
as
select * from test_func();
You can create a user defined function which call the stored procedure you have and use it in other queries.