I wanted to make an update function for a schema in PG SQL database. A test function is below. It doesn't work because it should never raise a notice, but will do that when running test_schema_update('second').
CREATE OR REPLACE FUNCTION test_schema_update(my_schema_name VARCHAR(200))
RETURNS void AS
$__marker__$
DECLARE
actualValue varchar(1000);
testValue varchar(1000);
BEGIN
EXECUTE 'SET search_path TO ' || quote_ident(my_schema_name);
testValue := (SELECT max(value) FROM setting WHERE settingkey = 'libraryname');
EXECUTE ('SELECT max(value) FROM setting WHERE settingkey = ''libraryname''')
INTO actualValue;
IF (actualValue != testValue)
THEN
RAISE NOTICE '% != %', actualValue, testValue;
RAISE INFO 'Schema was: %', current_schema();
END IF;
RESET search_path;
END;
$__marker__$ LANGUAGE plpgsql;
test_schema_update('first');
test_schema_update('second');
The problem is that PG SQL seem to analyze SELECT statements only once per session and then tables are fixed to specific schema. What is interesting is that you will get Schema was: second.
So is there a way reset SELECT statement analysis or some other way to work around this?
Side note: All schema creation function (ALTER TABLE, CREATE TABLE...) work fine. Only data manipulation functions seem to be affected (SELECT, INSERT, UPDATE).
Before:
IF (
SELECT max(id) FROM dimtime
)
THEN
INSERT INTO dimtime SELECT * FROM public.src_dimtime;
END IF;
After:
EXECUTE ('
SELECT max(id) FROM dimtime
')
INTO testInt;
IF (testInt IS NULL)
THEN
EXECUTE 'INSERT INTO dimtime SELECT * FROM public.src_dimtime';
END IF;
Edit: The problem occurs in PostgreSQL 9.2, but seem not to occur in 9.3. Maybe it was fixed?
That behaviour is to be expected. The reason is that PL/pgSQL uses plan caching for SQL statements, internally using standard prepared statements.
Per documentation:
As each expression and SQL command is first executed in the function, the PL/pgSQL interpreter creates a prepared execution plan (using the SPI manager's
SPI_prepareandSPI_saveplanfunctions). Subsequent visits to that expression or command reuse the prepared plan.
This is also the reason, why plpgsql functions are often faster than plain SQL functions for complex operations:
Prepared statements are saved for the lifetime of the session, not just the transaction (but invalidated when underlying objects change, this is safe with concurrent access). The documentation once again:
Once PL/pgSQL has made an execution plan for a particular command in a function, it will reuse that plan for the life of the database connection. This is usually a win for performance, but it can cause some problems if you dynamically alter your database schema.
Bold emphasis mine.
If you want to "change" the schema of a table name, you are really going to refer to a completely different table and need to use dynamic SQL with EXECUTE, which generates a new plan every time (with all advantages and disadvantages):
Because PL/pgSQL saves execution plans in this way, SQL commands that appear directly in a PL/pgSQL function must refer to the same tables and columns on every execution; that is, you cannot use a parameter as the name of a table or column in an SQL command. To get around this restriction, you can construct dynamic commands using the PL/pgSQL
EXECUTEstatement — at the price of constructing a new execution plan on every execution.
Read the referenced chapter in the manual. It's quite comprehensive.
You don't need dynamic SQL for your added code example, and a single statement will be faster:
INSERT INTO dimtime -- you may want list columns
SELECT * -- here as well
FROM public.src_dimtime
WHERE NOT EXISTS (SELECT 1 FROM dimtime);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With