How to execute this query without imploding my array? - postgresql

Can I execute a query like this? If not, can you give me a better way to do it without walking through my array or imploding it.
....
DECLARE
examples example[];
myinput myinput[];
BEGIN
select array(select e from mytable e where row_id in (myinput)) into examples
...

SELECT e
FROM mytable e
WHERE row_id = ANY(myinput)

Related

Deleting old rows in a schema using stored procedure

Is there a way to select all tables in a schema and delete rows that do not fit the condition (older than some date) in one procedure? I can do the same thing using 2 separate queries,
It would look something like: SELECT table_name FROM information_schema.tables WHERE table_schema = 'schemaName' and then DELETE FROM table_name WHERE time < now()-'12 months'::interval;" but cannot put my head on how to do the same using one stored procedure, I assume I should use for loop on some type of select query, but since I never realy worked with loops in postgres I always get some type of exception trying to do this.
Any help appreciated a lot
You can typically combine both SELECT and DELETE statements using a CTE :
WITH list AS
( SELECT ...
FROM ...
WHERE ...
RETURNING ...
)
DELETE FROM ...
USING list
WHERE ...
Except the fact that in your case, the returned parameter of the SELECT statement corresponds to the name of the table where rows must be deleted. This means that the table name is not known before the run time, and this implies to use a dynamic sql command within a plpgsql FUNCTION :
CREATE OR REPLACE FUNCTION delete_rows_from_table(table_name text)
RETURNS void LANGUAGE plpgsql AS
$$
BEGIN
EXECUTE E'
DELETE FROM ' || quote_ident(table_name) || E'
WHERE time < now()- \'12 months\':: interval' ;
END ;
$$ ;
Then you can use the FUNCTION delete_rows_from_table in your SELECT statement :
SELECT delete_rows_from_table(table_name)
FROM information_schema.tables
WHERE table_schema = 'schemaName'

Postgres subquery in FROM clause

In Postgres, I'm using a subquery in a FROM clause, but don't really know how to get the result I want. Say I have 2 tables, tableNameRegister(idNum integer, tableName text) and mytable(comment text, orderVal integer).
The tableNameRegister would look something like this:
idnum | tablename
-------+------------
1 | mytable
2 | othertable
And mytable would look something like this:
comment | orderval
-----------+-------
comment 1 | 1
comment 2 | 2
What I want to to is take the tableName value from tableNameRegister, and select from that tableName value, but all in one query. Essentially, something like this:
tabName = 'SELECT tableName FROM tableNameRegister WHERE idNum = 1;'
'SELECT * FROM ${tabName} WHERE orderVal = 2'
Ideally this would return the row containing comment 2. So I tried doing it in a subquery:
'SELECT * FROM (SELECT tableName FROM tableNameRegister WHERE idNum = 1) AS tabname WHERE orderVal = 2;'
But found out but this doesn't work the way I thought it would. Essentially, it returns a sort of subtable based off the results of the subquery, and not the actual value of mytable that I want. I was wondering if there is a way to do what I intended, all in one query/using subqueries? Or is this something I would have to do programmatically and seperate into two queries?
This type of thing is best done through some sort of scripting or otherwise programmatically. With something like plpgsql we have more flexibility, but even with this, the tricky thing will be if all the tables do not share the same structure or at least common column names. If they did or could be abstracted in some way something like the following could get you started:
CREATE OR REPLACE FUNCTION tablequery(id_num int)
RETURNS TABLE (comment varchar, orderval int)
AS $$
DECLARE
tableName varchar(50);
BEGIN
SELECT table_name FROM tableNameRegister WHERE idnum = id_num LIMIT 1 into tableName;
RETURN QUERY EXECUTE format('SELECT * FROM %I WHERE orderVal = 2;', tableName);
END;
$$ LANGUAGE plpgsql;
select * FROM tableQuery(1);

How to nest a SELECT into a UPDATE statement in PL/pgSQL

I have the below code which works well, the problem is I am creating a table each time, which means I need to recreate all indexes and delete the old tables when the new ones have been created.
DO
$do$
DECLARE
m text;
arr text[] := array['e09000001','e09000007','e09000033','e09000019'];
BEGIN
FOREACH m IN ARRAY arr
LOOP
EXECUTE format($fmt$
CREATE TABLE %I AS
SELECT a.ogc_fid,
a.poly_id,
a.title_no,
a.wkb_geometry,
a.distcode,
SUM(COALESCE((ST_Area(ST_Intersection(a.wkb_geometry, b.wkb_geometry))/ST_Area(a.wkb_geometry))*100, 0)) AS aw
FROM %I a
LEFT OUTER JOIN filter_ancientwoodlands b ON
ST_Overlaps(a.wkb_geometry, b.wkb_geometry) OR ST_Within(b.wkb_geometry, a.wkb_geometry)
GROUP BY a.ogc_fid,
a.poly_id,
a.title_no,
a.wkb_geometry,
a.distcode;
$fmt$, m || '_splitv2_aw', m || '_splitv2_distcode');
END LOOP;
END
$do$
Instead I would like to just create a new column in the existing table and update it. I have done this with simple queries like:
ALTER TABLE e09000001 ADD COLUMN area double precision;
UPDATE e09000001 SET area=ST_AREA(wkb_geometry);
I am having a lot of trouble figuring out to use UPDATE and SET with my more complicated SELECT statement above. Does anyone know how I can achieve this?
UPDATE: So I tried doing what #abelisto suggested:
UPDATE test_table
SET aw = subquery.aw_temp
FROM (SELECT SUM(COALESCE((ST_Area(ST_Intersection(a.wkb_geometry, b.wkb_geometry))/ST_Area(a.wkb_geometry))*100, 0)) AS aw_temp
FROM test_table a
LEFT OUTER JOIN filter_ancientwoodlands b ON
ST_Overlaps(a.wkb_geometry, b.wkb_geometry) OR ST_Within(b.wkb_geometry, a.wkb_geometry)
GROUP BY a.ogc_fid,
a.poly_id,
a.title_no,
a.wkb_geometry,
a.distcode) AS subquery;
But the query just runs for a long time (going one an hour) when it should only take a few seconds. Can anyone see an error in my code?
You need a WHERE clause to join the from expression to the update table.
perhaps like this.
UPDATE test_table
SET aw = subquery.aw_temp
FROM (SELECT SUM(COALESCE((ST_Area(ST_Intersection(a.wkb_geometry, b.wkb_geometry))/ST_Area(a.wkb_geometry))*100, 0)) AS aw_temp,a.wkb_geometry
FROM test_table a
LEFT OUTER JOIN filter_ancientwoodlands b ON
ST_Overlaps(a.wkb_geometry, b.wkb_geometry) OR ST_Within(b.wkb_geometry, a.wkb_geometry)
GROUP BY a.ogc_fid,
a.poly_id,
a.title_no,
a.wkb_geometry,
a.distcode) AS subquery
WHERE
subquery.wkb_geometry = test_table.wkb_geometry;

Postgresql 8.4 a variable hold set of records returned from another function

I am using postgresql 8.4 in backend
I have a postgres function say A() it can return a set of records (3 columns) like:
<A_id>::int,<A_ts_1>::timestamp,<A_ts_2>::timestamp
function A define like this(for example):
CREATE OR REPLACE FUNCTION A()
RETURNS SETOF record AS
$$
DECLARE
BEGIN
RETURN QUERY SELECT DISTINCT ON (A.id) A.id, A.ts_1, A.ts_2 FROM tablea;
END;
$$ LANGUAGE plpgsql;
SQL
function A has been called in another function B. In function B I need a variable to hold what returned from A() then do some query for example:
<variable> = select * from A();
a_id_array = ARRAY(select A_id from <variable>);
a_filtered_array = ARRAY(select A_id from <variable> where A_ts_1 ><a_timestamp> and A_ts_2 < <a_timestamp>);
So My question is what variable I should define to hold the set of records returned from A().
I tried temp table which really not good for multi-session env, it blocks data insertion. postgresql create temp table could block data insertion?
I checked doc for views seems not meet my requirements, however I may wrong so if any of you could give me an idea on how to use view in this case and use view will block data insertion as well?
Thank all!
P.S.
I think the worse case is in function B() I call function A() twice for example:
a_id_array = ARRAY(select A_id from A());
a_filtered_array = ARRAY(select A_id from A() where A_ts_1 ><a_timestamp> and A_ts_2 < <a_timestamp>);
Then my question would slightly change, can I achive this case just using one function call to A()?
PostgreSQL doesn't (yet, as of postgres 10) have table-valued variables backed by a tuplestore. So your best options are:
Return a REFCURSOR and use it from the other function. Can be clumsy to work with as you cannot reuse the resultset easily or FETCH in a subquery. It's not always easy to generate a cursor resultset either, depending on how you're creating the results.
Use temp tables with generated names so they don't collide. Lots of dynamic SQL involved here (EXECUTE format(...)) but it works.
Avoid trying to pass result sets between functions
After researching, found a way to replace using temp table and query returned set of record which is using WITH query.
SELECT c.r_ids, c.a_r_ids into a_id_array, a_filtered_array FROM(
WITH returned_r AS (SELECT * FROM a())
SELECT * from (
SELECT ARRAY( SELECT A_id from returned_r ) as r_ids ) as a
CROSS JOIN (
SELECT ARRAY(SELECT A_id FROM returned_r WHERE A_ts_1 is NOT NULL AND A_ts_2 IS NULL) as a_r_ids
) as b
) as c;

PL/SQL SELECT INTO alternative

I have few SELECT statements that are made by joining multiple tables.
Each select is returning single row but from 10 to 20 fields in that row. What is the easiest way to store that data for later use?
I would like to avoid creating 50 variables and writing select into statements, using cursors and loop for single row is not the smartest idea from what i read around.
Is there a good way to do this?
Stupid example so you can get general idea
SELECT t1.field1
, t1.field2
, t1.field3
, t1.field4
, t2.field5
, t2.field6
, t2.field7
, t3.field8
FROM table1 t1
JOIN table2 t2 ON something
JOIN table3 t3 ON something
Sorry for errors in my english and thanks in advance
Create a view from your select statement. Thereafter can reference a record by using a single variable of type <viewname>%ROWTYPE.
Another option would be to wrap the select in an implicit cursor loop:
DECLARE
strvar VARCHAR2(400); -- demo purpose only
BEGIN
-- ...
FOR i IN (
-- ... here goesyour select statement ...
) LOOP
strvar := i.field1 || i.field2; -- ... whatever
END LOOP;
-- ...
END;
-- ...
Still another option is the declaration of a record type and a record variable:
DECLARE
TYPE tRec IS RECORD (
field1 table1.field1%TYPE
, field2 table1.field2%TYPE
, field3 table1.field3%TYPE
, field4 table1.field4%TYPE
, field5 table2.field5%TYPE
, field6 table2.field6%TYPE
, field7 table2.field7%TYPE
, field8 table3.field8%TYPE
)
r tRec;
BEGIN
-- ...
SELECT --...
INTO r
FROM --...
;
-- ...
END;
-- ...
Every time you make a select you will have a cursor - there's no escape from that.
Views and explicit cursors are a way to reuse select statements. Which one is better option depends on the case.
rowtype-attribute is handy way to create records automatically based on tables/views/cursors. I think this is the closest to your requirement what PL/SQL can offer.
create or replace package so51 is
cursor cursor1 is -- an example single row query
select
dual.* -- all table columns included
,'Y' as str
,rownum as num
,sysdate as date_
from dual
;
function get_data return cursor1%rowtype;
end;
/
show errors
create or replace package body so51 is
-- function never changes when cursor1 changes
function get_data return cursor1%rowtype is
v_data cursor1%rowtype;
begin
open cursor1;
fetch cursor1 into v_data;
close cursor1;
return v_data;
end;
end;
/
show errors
declare
v_data constant so51.cursor1%rowtype := so51.get_data;
begin
-- use only the data you need
dbms_output.put_line('v_data.dummy = ' || v_data.dummy);
dbms_output.put_line('v_data.str = ' || v_data.str);
dbms_output.put_line('v_data.num = ' || v_data.num);
dbms_output.put_line('v_data.date_ = ' || v_data.date_);
end;
/
Example run
SQL> #so51.sql
Package created.
No errors.
Package body created.
No errors.
v_data.dummy = X
v_data.str = Y
v_data.num = 1
v_data.date_ = 2015-11-23 09:42:02
PL/SQL procedure successfully completed.
SQL>