I have my_table with two text columns: my_query, which contains various DML queries in text form; and and my_explanation, which is entirely populated with nulls.
I'd like to do something like:
UPDATE my_table SET my_explanation = execute explain analyze my_table.my_query
So that the my_explanation column on each row is updated to contain the textual output of the explain analyze of the query contained in its my_query column.
I tried making a function with cursors but it didn't work. I'm still learning pgplsql. Any ideas?
EXECUTE is a PL/pgSQL statement; it can't be used in an SQL query, only in a LANGUAGE plpgsql function or a DO block.
You probably want a function like this:
CREATE FUNCTION explain_query(sql TEXT) RETURNS SETOF TEXT AS $$
BEGIN
RETURN QUERY EXECUTE 'EXPLAIN ANALYZE ' || sql;
END
$$
LANGUAGE plpgsql;
Because EXPLAIN returns multiple rows, you'll need to aggregate the output to fit it into a single record in my_table:
UPDATE my_table
SET my_explanation = (
SELECT string_agg(plan, E'\n')
FROM explain_query(my_query) plan
)
Related
ERROR: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM instead.
CONTEXT: PL/pgSQL function show_emp_details(character varying) line 5 at SQL statement
SQL state: 42601
mycode:
CREATE OR REPLACE function show_emp_details(project_id varchar(10))
RETURNS VARCHAR
LANGUAGE 'plpgsql'
AS $BODY$
DECLARE
show_emp_details VARCHAR;
BEGIN
SELECT emp_id ,role_id,from_date,to_date
FROM allocation al
JOIN t_project pj
ON(pj.project_id=al.project_id);
RETURN
emp_id ,role_id,from_date,to_date ;
END
$BODY$;
drop function show_emp_details();
SELECT show_emp_details('P01');
I need to create a functions
There are multiple problems with your function:
You declare it to return single varchar field but based on your function body you are trying returning multiple fields. Take a look at Return multiple fields as a record in PostgreSQL with PL/pgSQL
You have SELECT in your function, but it does not have INTO, you do not define where the result should be put. If you wish to return the result of the SQL take a look at https://www.postgresql.org/docs/current/plpgsql-control-structures.html#PLPGSQL-STATEMENTS-RETURNING - especially at RETURN QUERY.
You don't even need plpgsql for a simple function returning query result. Use sql language for it.
I'm sure you aren't selecting the function after you drop it, just edit the code in your question.
I'm trying to EXECUTE some SELECTs to use inside a function, my code is something like this:
DECLARE
result_one record;
BEGIN
EXECUTE 'WITH Q1 AS
(
SELECT id
FROM table_two
INNER JOINs, WHERE, etc, ORDER BY... DESC
)
SELECT Q1.id
FROM Q1
WHERE, ORDER BY...DESC';
RETURN final_result;
END;
I know how to do it in MySQL, but in PostgreSQL I'm failing. What should I change or how should I do it?
For a function to be able to return multiple rows it has to be declared as returns table() (or returns setof)
And to actually return a result from within a PL/pgSQL function you need to use return query (as documented in the manual)
To build dynamic SQL in Postgres it is highly recommended to use the format() function to properly deal with identifiers (and to make the source easier to read).
So you need something like:
create or replace function get_data(p_sort_column text)
returns table (id integer)
as
$$
begin
return query execute
format(
'with q1 as (
select id
from table_two
join table_three on ...
)
select q1.id
from q1
order by %I desc', p_sort_column);
end;
$$
language plpgsql;
Note that the order by inside the CTE is pretty much useless if you are sorting the final query unless you use a LIMIT or distinct on () inside the query.
You can make your life even easier if you use another level of dollar quoting for the dynamic SQL:
create or replace function get_data(p_sort_column text)
returns table (id integer)
as
$$
begin
return query execute
format(
$query$
with q1 as (
select id
from table_two
join table_three on ...
)
select q1.id
from q1
order by %I desc
$query$, p_sort_column);
end;
$$
language plpgsql;
What a_horse said. And:
How to return result of a SELECT inside a function in PostgreSQL?
Plus, to pick a column for ORDER BY dynamically, you have to add that column to the SELECT list of your CTE, which leads to complications if the column can be duplicated (like with passing 'id') ...
Better yet, remove the CTE entirely. There is nothing in your question to warrant its use anyway. (Only use CTEs when needed in Postgres, they are typically slower than equivalent subqueries or simple queries.)
CREATE OR REPLACE FUNCTION get_data(p_sort_column text)
RETURNS TABLE (id integer) AS
$func$
BEGIN
RETURN QUERY EXECUTE format(
$q$
SELECT t2.id -- assuming you meant t2?
FROM table_two t2
JOIN table_three t3 on ...
ORDER BY t2.%I DESC NULL LAST -- see below!
$q$, $1);
END
$func$ LANGUAGE plpgsql;
I appended NULLS LAST - you'll probably want that, too:
PostgreSQL sort by datetime asc, null first?
If p_sort_column is from the same table all the time, hard-code that table name / alias in the ORDER BY clause. Else, pass the table name / alias separately and auto-quote them separately to be safe:
Define table and column names as arguments in a plpgsql function?
I suggest to table-qualify all column names in a bigger query with multiple joins (t2.id not just id). Avoids various kinds of surprising results / confusion / abuse.
And you may want to schema-qualify your table names (myschema.table_two) to avoid similar troubles when calling the function with a different search_path:
How does the search_path influence identifier resolution and the "current schema"
I want to measure the performance of postgresql code I wrote. In the code tables get created, selfwritten functions get called etc.
Looking around, I found EXPLAIN ANALYSE is the way to go.
However, as far as I understand it, the code only gets executed once. For a more realistic analysis I want to execute the code many many times and have the results of each iteration written somewhere, ideally in a table (for statistics later).
Is there a way to do this with a native postgresql function? If there is no native postgresql function, would I accomplish this with a simple loop? Further, how would I write out the information of every EXPLAIN ANALYZE iteration?
One way to do this is to write a function that runs an explain and then spool the output of that into a file (or insert that into a table).
E.g.:
create or replace function show_plan(to_explain text)
returns table (line_nr integer, line text)
as
$$
declare
l_plan_line record;
l_line integer;
begin
l_line := 1;
for l_plan_line in execute 'explain (analyze, verbose)'||to_explain loop
return query select l_line, l_plan_line."QUERY PLAN";
l_line := l_line + 1;
end loop;
end;
$$
language plpgsql;
Then you can use generate_series() to run a statement multiple times:
select g.i as run_nr, e.*
from show_plan('select * from foo') e
cross join generate_series(1,10) as g(i)
order by g.i, e.line_nr;
This will run the function 10 times with the passed SQL statement. The result can either be spooled to a file (how you do that depends on the SQL client you are using) or inserted into a table.
For an automatic analysis it's probably easer to use a more "parseable" explain format, e.g. XML or JSON. This is also easier to handle in the output as the plan is a single XML (or JSON) value instead of multiple text lines:
create or replace function show_plan_xml(to_explain text)
returns xml
as
$$
begin
return execut 'explain (analyze, verbose, format xml)'||to_explain;
end;
$$
language plpgsql;
Then use:
select g.i as run_nr, show_plan_xml('select * from foo')
from join generate_series(1,10) as g(i)
order by g.i;
I need to show the Tree_Nodes table data
CREATE OR REPLACE FUNCTION sample()
RETURNS TABLE() AS
$BODY$
BEGIN
select * from "Tree_Nodes";
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
ROWS 1000;
ALTER FUNCTION sample()
OWNER TO postgres;
It fails with:
ERROR: query has no destination for result data
Avoid the error and i will get the table column format in all data
To return the result of a SELECT, a pure SQL function is much more suitable:
CREATE OR REPLACE FUNCTION sample()
RETURNS TABLE ( .... ) AS
$BODY$
select * from "Tree_Nodes";
$BODY$
LANGUAGE sql;
Or if you really need PL/pgSQL, you need to use return query (which is clearly documented in the manual)
CREATE OR REPLACE FUNCTION sample()
RETURNS TABLE (....)
AS
$BODY$
BEGIN
return query select * from "Tree_Nodes";
END;
$BODY$
LANGUAGE plpgsql;
But you cannot just specify returns table() you have to also define the structure of the result:
CREATE OR REPLACE FUNCTION sample()
RETURNS TABLE(id integer, some_column text, other_column decimal(10,2), ...)
AS
The exact error you quote is caused by using SELECT without an INTO clause in PL/PgSQL. You must either use SELECT INTO somevariable, use RETURN QUERY, if you want to discard the data, use the PERFORM statement instead of SELECT, as covered by the PL/PgSQL manual.
Once you fix that by using RETURN QUERY SELECT .... you'll find that the function still doesn't work, because RETURNS TABLE() doesn't make sense. You're returning an empty result set. It'll fail, complaining that the statement is returning a result set that doesn't match the function.
It makes no sense to do this anyway, since you can just write it as a trivial SQL function like:
CREATE OR REPLACE FUNCTION sample()
RETURNS SETOF "Tree_Nodes"
AS $$
SELECT * FROM "Tree_Nodes";
$$ LANGUAGE sql;
This function appears to serve no purpose. What are you trying to achieve with it?
(By the way, you should generally avoid SELECT * in production code. List the columns. That way, if you add a column later, things that use the table won't suddenly stop working.)
I see from the Postgres 8.1 docs that EXPLAIN generated something like tabular data:
Prior to PostgreSQL 7.3, the plan was emitted in the form of a NOTICE
message. Now it appears as a query result (formatted like a table with
a single text column).
I'm working with 9.0, for which, the docs say, the output can be a variety of types, including TEXT and XML. What I'd really like to do is treat the output as a standard query result so that I could generate a simple report for a query or a set of queries, e.g.,
SELECT maxcost FROM (
EXPLAIN VERBOSE
SELECT COUNT(*)
FROM Mytable
WHERE value>17);
The above doesn't work in any form that I've tried, and I made up the attribute maxcost to demonstrate how neat it would be to pull out specific bits of data (in this case, the maximum estimated cost of the query). Is there anything I can do that would get me part of the way there? I'd prefer to be able to work within a simple SQL console.
No other answers so far, so here's my own stab at it.
It's possible to read the results of explain into a variable within plpgsql, and since the output can be in XML one can wrap EXPLAIN in a stored function to yield the top-level costs using xpath:
CREATE OR REPLACE FUNCTION estimate_cost(IN query text,
OUT startup numeric,
OUT totalcost numeric,
OUT planrows numeric,
OUT planwidth numeric)
AS
$BODY$
DECLARE
query_explain text;
explanation xml;
nsarray text[][];
BEGIN
nsarray := ARRAY[ARRAY['x', 'http://www.postgresql.org/2009/explain']];
query_explain :=e'EXPLAIN(FORMAT XML) ' || query;
EXECUTE query_explain INTO explanation;
startup := (xpath('/x:explain/x:Query/x:Plan/x:Startup-Cost/text()', explanation, nsarray))[1];
totalcost := (xpath('/x:explain/x:Query/x:Plan/x:Total-Cost/text()', explanation, nsarray))[1];
planrows := (xpath('/x:explain/x:Query/x:Plan/x:Plan-Rows/text()', explanation, nsarray))[1];
planwidth := (xpath('/x:explain/x:Query/x:Plan/x:Plan-Width/text()', explanation, nsarray))[1];
RETURN;
END;
$BODY$
LANGUAGE plpgsql;
Hence the example from the question becomes:
SELECT totalcost
FROM estimate_cost('SELECT COUNT(*)
FROM Mytable
WHERE value>17');
This message suggests using "FOR rec IN EXECUTE EXPLAIN..." which seems to do the trick e.g.:
drop table if exists temp_a;
create temp table temp_a
(
"QUERY PLAN" text
);
DO
$$
DECLARE
rec record;
BEGIN
FOR rec IN EXECUTE 'EXPLAIN VERBOSE select version()'
LOOP
-- RAISE NOTICE 'rec=%', row_to_json(rec);
insert into temp_a
select rec."QUERY PLAN";
END LOOP;
END
$$ language plpgsql;
select *
from temp_a;