Preserve PostgreSQL schema identifier in dependent function calls - database

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.

Related

why results of function save late?

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();

How do I unnest an array inside a 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.

SQL Server Stored Procedure - Executing different queries by CASE

I have two parameters for my stored procedure. Based on what the value of Searching_Condition is, the proper column must be searched. In a pseudo code format, it should be something like this
//CASE #Search_Condition
// WHEN 'UserID' THEN SELECT * FROM user_table WHERE UserID LIKE '#Keywords'
// WHEN 'UserName' THEN SELECT * FROM user_table WHERE UserName LIKE '#Keywords'
// WHEN 'UserAddress' THEN SELECT * FROM user_table WHERE UserAddress LIKE '#Keywords'
The following is the code I was working on and where got stuck. It should be simple but man... for being not familiar with SQL Server, I'm so struggling with it and CASE in SQL Server doesn't work the way I thought it would.
Thanks !
CREATE PROCEDURE [dbo].[USP_SP_NAME]
#Searching_Condition NVARCHAR(100),
#Keywords NVARCHAR(100)
AS
SET NOCOUNT ON
SET LOCK_TIMEOUT 3000
SET XACT_ABORT ON
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
BEGIN TRY
SELECT
CASE WHEN
#Searching_Condition = 'user_id' THEN
(select count(*) from user_table)
WHEN
#Searching_Condition = 'user_name' THEN
(select * from user_table)
END
END TRY
The key concept that will help you get this right is the difference between expressions and statements.
A statement is procedural and directs the flow of control. You can think of an instruction pointer proceeding from statement to statement, and every statement is isolated from other statements (although they can select which statements after them are executed or not). They can be thought of as verbs.
An expression is something that reduces to a value--a scalar value, a string, or even a rowset--but the expression doesn't command to DO anything. They can be thought of as nouns. These nouns can't exist by themselves, they must be in the context of a statement.
The CASE statement in SQL Server is an expression. It isn't a procedural statement like Select Case is in, for example, Visual Basic. And the trick is, when the language expects an expression, you cannot substitute a statement--and furthermore, except in some special usages, you can't put procedural statements in the middle of expressions (except rowsets that can be evaluated as an expression, such as a single-column and single-row SELECT, or an EXISTS). An expression can contain expressions which contain expressions. They're like a tree, that is collapsed in order all the way down.
Think of the parts in EXECUTE dbo.MyStoredProcedure (8 + ##SPID) / 2: this is a single statement, with one parameter expression, consisting of three sub-expressions, evaluated in a certain order, that resolve to a single value, which is used as an argument to the stored procedure. You could not execute (8 + ##SPID) / 2 by itself, because it isn't a statement. (Never mind that the expression is silly, it is just for example.)
I did say that in some cases rowsets can be values, but the expected type of almost all expressions is a single value--not a rowset. That's the problem that's happening here--your outer SELECT statement is expecting a single value for the definition of the first column in a single row (since you have no FROM clause), but you're trying to provide a whole rowset when your searching condition is 'user_name'.
You can solve this by abandoning CASE entirely and using IF--because IF is a procedural statement.
CREATE PROCEDURE [dbo].[USP_SP_NAME]
#Searching_Condition NVARCHAR(100),
#Keywords NVARCHAR(100)
AS
SET NOCOUNT ON;
SET LOCK_TIMEOUT 3000;
SET XACT_ABORT ON;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN TRY
IF #Searching_Condition = 'user_id' BEGIN
select count(*) from user_table;
END
ELSE IF #Searching_Condition = 'user_name' BEGIN
select * from user_table;
END;
END TRY;
I advocate avoiding the version of the IF that doesn't use BEGIN and END and accepts a single statement--this form leads to confusion and bugs. I use BEGIN and END every time, which seems like a pain, until you discover how much time and effort doing so saves you down the road...
You can try this, no need to give cases, where condition will change depending on the value of #Searching_Condition:
CREATE PROCEDURE [dbo].[USP_SP_NAME]
#Searching_Condition NVARCHAR(100),
#Keywords NVARCHAR(100)
AS
SET NOCOUNT ON
SET LOCK_TIMEOUT 3000
SET XACT_ABORT ON
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
BEGIN TRY
exec('Select * from user_table WHERE'+ #Searching_Condition+' LIKE '+ #Keywords);
END TRY

sql user defined function

for a table valued function in sql why cant we write sql statements inside begin and end tags like-
create function dbo.emptable()
returns Table
as
BEGIN --it throws an error
return (select id, name, salary from employee)
END
go
while in scalar valued function we can use these tags like
create function dbo.countemp()
returns int
as
begin
return (select count(*) from employee)
end
go
is there any specific rule where we should use BEGIN & END tags
The multi-statement table-valued
function is slightly more complicated
than the other two types of functions
because it uses multiple statements to
build the table that is returned to
the calling statement. Unlike the
inline table-valued function, a table
variable must be explicitly declared
and defined. The following example
shows how to implement a
multi-statement table-valued function
that populates and returns a table
variable.
USE Northwind
go
CREATE FUNCTION fx_OrdersByDateRangeAndCount
( #OrderDateStart smalldatetime,
#OrderDateEnd smalldatetime,
#OrderCount smallint )
RETURNS #OrdersByDateRange TABLE
( CustomerID nchar(5),
CompanyName nvarchar(40),
OrderCount smallint,
Ranking char(1) )
AS
BEGIN
// statements that does some processing ....
END
From the above, I guess BEGIN and END denotes the intent/use of multiple statements & hence it requires the table variable to be defined as shown in the code above.
from http://www.sqlteam.com/article/intro-to-user-defined-functions-updated
In an INLINE TVF (like your first example), the BEGIN and END would try to force it to be procedural code, and therefore it would no longer be an Inline TVF. Scalar functions are only available in a procedural form (which is lousy). Therefore, Scalar functions should generally be avoided in the current versions of SQL Server.

How to return temporary table from stored procedure

CREATE PROCEDURE [test].[proc]
#ConfiguredContentId int,
#NumberOfGames int
AS
BEGIN
SET NOCOUNT ON
RETURN
#WunNumbers TABLE (WinNumb int)
INSERT INTO #WunNumbers (WinNumb)
SELECT TOP (#NumberOfGames) WinningNumber
FROM [Game].[Game] g
JOIN [Game].[RouletteResult] AS rr ON g.[Id] = rr.[gameId]
WHERE g.[ConfiguredContentId] = #ConfiguredContentId
ORDER BY g.[Stoptime] DESC
SELECT WinNumb, COUNT (WinNumb) AS "Count"
FROM #WunNumbers wn
GROUP BY wn.[WinNumb]
END
GO
This stored procedure returns values from first select statement, but I would like to have values from second select statement to be returned. Table #WunNumbers is a temporary table.
Any ideas???
Take a look at this code,
CREATE PROCEDURE Test
AS
DECLARE #tab table (no int, name varchar(30))
insert #tab select eno,ename from emp
select * from #tab
RETURN
What version of SQL Server are you using? In SQL Server 2008 you can use Table Parameters and Table Types.
An alternative approach is to return a table variable from a user defined function but I am not a big fan of this method.
You can find an example here
A temp table can be created in the caller and then populated from the called SP.
create table #GetValuesOutputTable(
...
);
exec GetValues; -- populates #GetValuesOutputTable
select * from #GetValuesOutputTable;
Some advantages of this approach over the "insert exec" is that it can be nested and that it can be used as input or output.
Some disadvantages are that the "argument" is not public, the table creation exists within each caller, and that the name of the table could collide with other temp objects. It helps when the temp table name closely matches the SP name and follows some convention.
Taking it a bit farther, for output only temp tables, the insert-exec approach and the temp table approach can be supported simultaneously by the called SP. This doesn't help too much for chaining SP's because the table still need to be defined in the caller but can help to simplify testing from the cmd line or when calling externally.
-- The "called" SP
declare
#returnAsSelect bit = 0;
if object_id('tempdb..#GetValuesOutputTable') is null
begin
set #returnAsSelect = 1;
create table #GetValuesOutputTable(
...
);
end
-- populate the table
if #returnAsSelect = 1
select * from #GetValuesOutputTable;
YES YOU CAN.
In your stored procedure, you fill the table #tbRetour.
At the very end of your stored procedure, you write:
SELECT * FROM #tbRetour
To execute the stored procedure, you write:
USE [...]
GO
DECLARE #return_value int
EXEC #return_value = [dbo].[getEnregistrementWithDetails]
#id_enregistrement_entete = '(guid)'
GO
The return type of a procedure is int.
You can also return result sets (as your code currently does) (okay, you can also send messages, which are strings)
Those are the only "returns" you can make. Whilst you can add table-valued parameters to a procedure (see BOL), they're input only.
Edit:
(Or as another poster mentioned, you could also use a Table Valued Function, rather than a procedure)
First create a real, permanent table as a template that has the required layout for the returned temporary table, using a naming convention that identifies it as a template and links it symbolically to the SP, eg tmp_SPName_Output. This table will never contain any data.
In the SP, use INSERT to load data into a temp table following the same naming convention, e.g. #SPName_Output which is assumed to exist. You can test for its existence and return an error if it does not.
Before calling the sp use this simple select to create the temp table:
SELECT TOP(0) * INTO #SPName_Output FROM tmp_SPName_Output;
EXEC SPName;
-- Now process records in #SPName_Output;
This has these distinct advantages:
The temp table is local to the current session, unlike ##, so will not clash with concurrent calls to the SP from
different sessions. It is also dropped automatically when out of scope.
The template table is maintained alongside the SP, so if changes are
made to the output (new columns added, for example) then pre-existing
callers of the SP do not break. The caller does not need to be changed.
You can define any number of output tables with different naming for
one SP and fill them all. You can also define alternative outputs
with different naming and have the SP check the existence of the temp
tables to see which need to be filled.
Similarly, if major changes are made but you want to keep backwards
compatibility, you can have a new template table and naming for the later
version but still support the earlier version by checking which temp
table the caller has created.

Resources