Postgresql - how to run a query on multiple tables with same schema - sql

I have a postgres database that has several tables (a few hundreds). A subset Foo of the tables in the database have the same schema.
Ideally, I would like to create a stored procedure which can run a query against a single table, or against all tables in subset Foo.
Pseudocode:
CREATE TABLE tbl_a (id INTEGER, name VARCHAR(32), weight double, age INTEGER);
CREATE TABLE tbl_b (id INTEGER, name VARCHAR(32), weight double, age INTEGER);
CREATE TABLE tbl_c (id INTEGER, name VARCHAR(32), weight double, age INTEGER);
CREATE TABLE tbl_d (id INTEGER, name VARCHAR(32), weight double, age INTEGER);
CREATE TYPE person_info AS (id INTEGER, name VARCHAR(32), weight double, age INTEGER);
CREATE FUNCTION generic_func(ARRAY one_or_more_table_names)
RETURNS person_info
-- Run query on table or all specified tables
AS $$ $$
LANGUAGE SQL;
How could I implement this requirement in Postgresql 9.x ?

You should have a look at table inheritance in PostgreSQL, they allow exactly what you speak about.
For example, you could create a table parent_tbl:
CREATE TABLE parent_tbl (id INTEGER, name VARCHAR(32), weight numeric, age INTEGER);
Then link your tables to this parent table:
ALTER TABLE tbl_a INHERIT parent_tbl;
ALTER TABLE tbl_b INHERIT parent_tbl;
ALTER TABLE tbl_c INHERIT parent_tbl;
ALTER TABLE tbl_d INHERIT parent_tbl;
Then a SELECT query over parent_tbl will query all of tbl_x tables, while a query on tbl_x will query only this particular table.
INSERT INTO tbl_a VALUES (1, 'coucou', 42, 42);
SELECT * FROM tbl_a;
id | name | weight | age
----+--------+--------+-----
1 | coucou | 42 | 42
(1 row)
SELECT * FROM parent_tbl;
id | name | weight | age
----+--------+--------+-----
1 | coucou | 42 | 42
(1 row)
SELECT * FROM tbl_b;
id | name | weight | age
----+--------+--------+-----
(0 rows)
It is also possible to filter data from given children tables. For example, if you are interested in data coming from tables tbl_a and tbl_b, you can do
select id, name, weight, age
from parent_tbl
left join pg_class on oid = parent_tbl.tableoid
where relname in ('tbl_a', 'tbl_b');
EDIT : I put numeric for weight instead of double as this type is not supported on my server.

To create select query dynamically using items(table name) in an array you can use following select statement
SELECT string_agg(q, ' union all ')
FROM (
SELECT 'select * from ' || unnest(array ['tble_a','tble_b']) AS q
) t
Result:
string_agg
---------------------------------------------------
select * from tble_a union all select * from tble_b
You can create the function that returns table with columns
id INTEGER
,name VARCHAR(32)
,weight numeric
,age INTEGER
P.S: I am avoiding TYPE person_info
function:
CREATE
OR REPLACE FUNCTION generic_func (tbl varchar [])
RETURNS TABLE ( -- To store the output
id INTEGER
,name VARCHAR(32)
,weight numeric
,age INTEGER
) AS $BODY$
DECLARE qry text;
BEGIN
SELECT string_agg(q, ' union all ') --To create select query dynamically
INTO qry
FROM (
SELECT 'select * from ' || unnest(tbl) AS q
) t;
RAISE NOTICE 'qry %',qry; --optional
RETURN query --Executes the query to the defined table
EXECUTE qry;
END;$BODY$
LANGUAGE plpgsql VOLATILE
Usage:
select * from generic_func(array['tbl_a','tbl_b','tbl_c','tbl_d'])
Result:
id name weight age
-- ---- ------ ---
2 ABC 11 112
2 CBC 11 112
2 BBC 11 112
2 DBC 11 112
and
select * from generic_func(array['tbl_a'])
Result:
id name weight age
-- ---- ------ ---
2 ABC 11 112

Related

Table Variable and Table-Valued Function equivalent in PostgreSQL

I need to create a function in PostgreSQL for the following :
Query multiple tables based on a business logic (all result sets return the same type of data)
Compile all result sets into one table and return that table
Is it possible to accomplish this without using the temp tables in PostgreSQL?
I currently do this in Microsoft SQL server using Table Variables, below is a sample function:
create FUNCTION test(#search_in nvarchar(500))
RETURNS #data_table TABLE
(
item_id int,
item_type nvarchar(1),
first_name nvarchar(100),
last_name nvarchar(100))
) AS
BEGIN
-- from first table
if charindex('search_in_authors', #search_in) > 0
insert into #data_table
select item_id, 'a', first_name, last_name
from authors
where first_name = 'james'
-- from second table
if charindex('search_in_editors', #search_in) > 0
insert into #data_table
select item_id, 'e', first_name, last_name
from editors
where first_name = 'james'
-- from third table
if charindex('search_in_publishers', #search_in) > 0
insert into #data_table
select item_id, 'p', first_name, last_name
from publishes
where first_name = 'james'
-- there could be more like these based on the business logic...
(...)
-- finally return the records compiled in #data_table
RETURN
END
Sample calls to the function:
select * from dbo.test('search_in_authors')
select * from dbo.test('search_in_authors, search_in_editors')
select * from dbo.test('search_in_authors, search_in_editors,search_in_publishers ')
Are there any options in PostgreSQL to achieve this other than using a temp table ?
Thanks,
San
You can use RETURN QUERY to add the result of various queries to the output.
CREATE OR REPLACE FUNCTION public.testf()
RETURNS TABLE(id INTEGER, name text)
STABLE
AS $$
DECLARE
BEGIN
RETURN QUERY select 1 as id, 'abc' as name;
RETURN QUERY select 2 as id, 'def' as name;
RETURN QUERY select 3 as id, 'xyz' as name;
-- Final return as set is now complete.
return;
END;
$$ LANGUAGE plpgsql;
select * from public.testf();
id | name
----+------
1 | abc
2 | def
3 | xyz
(3 rows)

How correctly create multiple entries by arrays in PostgreSQL?

In PostgreSQL database I have table which looks like this:
| question_id | question_text | widget | required | position |
|-------------|---------------|--------|----------|----------|
| int | text | int | boolean | int |
Second table which called factors_questions_relationship looks like this:
| factor_id | question_id |
|-------------|---------------|
| int | text |
I am trying to create function which would create multiple rows and return array of ids of new created entries. How correctly to make such function?
CREATE OR REPLACE FUNCTION factorio(
FACTOR_IDENTIFIER INT,
TEXT_ARR VARCHAR[],
WIDGET_ARR INT[],
REQUIRED_ARR BOOLEAN[],
POSITION_ARR INT[]
) RETURNS SETOF INT AS $$
BEGIN
RETURN QUERY
WITH RESULT_SET AS (
INSERT INTO QUESTIONS (TEXT, WIDGET, REQUIRED, POSITION)
SELECT
UNNEST(ARRAY[TEXT_ARR]) AS TEXT,
UNNEST(ARRAY[WIDGET_ARR]) AS WIDGET,
UNNEST(ARRAY[REQUIRED_ARR]) AS REQUIRED,
UNNEST(ARRAY[POSITION_ARR]) AS POSITION
RETURNING ID
)
--
INSERT INTO factors_questions_relationship (FACTOR_ID, QUESTION_ID)
SELECT FACTOR_IDENTIFIER FACTOR_ID, QUESTION_ID FROM UNNEST(ARRAY[array_agg(SELECT ID FROM RESULT_SET)]) QUESTION_ID
--
SELECT ID FROM RESULT_SET;
END;
$$ LANGUAGE plpgsql;
You can simply unnest them in columns
select
unnest(array['quick','brown','fox']) as question_text,
unnest(array[1,2,3]) as widget_id
Whereas putting them in FROM clause, would result to cartesian product:
select question_text, widget_id
from
unnest(array['quick','brown','fox']) as question_text,
unnest(array[1,2,3]) as widget_id
Output:
To obtain the Ids generated from newly-inserted records, use return query + returning id combo. Sample test:
create table z
(
id int generated by default as identity primary key,
question_text text
);
create or replace function insert_multiple_test()
returns table (someid int) as
$$
begin
return query
with resulting_rows as
(
insert into z(question_text) values
('hello'),
('你好'),
('hola')
returning id
)
select id from resulting_rows;
end;
$$ language 'plpgsql';
select * from insert_multiple_test();
Output:
Here's for SETOF:
create table z(id int generated by default as identity primary key, question_text text);
create or replace function insert_multiple_test()
returns setof int
as
$$
begin
return query
with resulting_rows as
(
insert into z(question_text) values
('hello'),
('你好'),
('hola')
returning id
)
select id from resulting_rows;
end;
$$ language 'plpgsql';
select x.the_id from insert_multiple_test() as x(the_id);
Output:
If you don't need to run multiple queries, you can just use LANGUAGE 'sql', it's simpler:
create table z
(
id int generated by default as identity primary key,
question_text text
);
create or replace function insert_multiple_test()
returns setof int as
$$
insert into z(question_text) values
('hello'),
('你好'),
('hola')
returning id;
$$ language 'sql';
select x.id_here from insert_multiple_test() as x(id_here);
Output:

Inserting an auto generated value into a column with specific pattern

I have a table named tblSample which has columns ID, PID etc. I want to auto generate those two columns with a specific pattern.
For example:
ID PID
------ ------
ABC001 PAB001
ABC002 PAB002
ABC003 PAB003
ABC004 PAB004
| |
| |
ABC999 PAB999
As you can see, the pattern 'ABC' in ID and 'PAB' in PID is the same. How can I insert those records into a table automatically and the range between those three digits after 'ABC' or 'PAB' is 001-999?
My suggestion is to create table structure as below with one identity column as testID and other computed by using that column ID and PID:
CREATE TABLE #tmpOne(testID INT IDENTITY (1,1),
ID AS ('ABC'+ (CASE WHEN len(testID) <=3 THEN CAST(RIGHT(0.001*testID, 3) AS VARCHAR) ELSE CAST(testID AS VARCHAR) END)),
Ename VARCHAR(20))
INSERT INTO #tmpOne(Ename)
SELECT 'Test'
SELECT * FROM #tmpOne
CREATE TABLE #tt(ID VARCHAR(100),PID VARCHAR(100))
GO
INSERT INTO #tt(ID,PID)
SELECT 'ABC'+RIGHT('000'+LTRIM(a.ID),3),'PAB'+RIGHT('000'+LTRIM(a.ID),3) FROM (
SELECT ISNULL(MAX(CASE WHEN SUBSTRING(t.id,4,LEN(ID))> SUBSTRING(t.id,4,LEN(PID)) THEN SUBSTRING(t.id,4,LEN(ID)) ELSE SUBSTRING(t.id,4,LEN(PID)) END )+1,1) AS id
FROM #tt AS t
) AS a
GO 999

Return a collect type in a sql query

I dont know hot to get a Collect Type in query sentece.
for example
I defined my type and all...
When i do:
SELECT
MyType(att1,att2)
FROM
table
it return
|att1 | att2 |
| a | b |
| c | d |
.
.
When i do:
SELECT
MyTABLE_COLLECT_TYPE (MyType(att1,att2))
FROM
table
it return
|MyTABLE_COLLECT_TYPE |
|collection |
|collection |
.
.
And each collection have one record MyType(att1,att2)
So my questio is how i can get only one collection of all type MyType(att1,att2), a single row that return the whole table. ( I dont want to use a function if it possible :P )
Thanks :)
you need use TABLE(t.collection_field)
see Unnesting Results of Collection Queries
example from the link above:
SELECT e.*
FROM department_persons d, TABLE(d.dept_emps) e;
IDNO NAME PHONE
---------- ------------------------------ ---------------
1 John Smith 1-650-555-0135
2 Diane Smith 1-650-555-0135
extended sample from the link above
CREATE TYPE person_typ AS OBJECT (
idno NUMBER,
name VARCHAR2(30),
phone VARCHAR2(20),
MAP MEMBER FUNCTION get_idno RETURN NUMBER,
MEMBER PROCEDURE display_details ( SELF IN OUT NOCOPY person_typ ) );
CREATE TYPE BODY person_typ AS
MAP MEMBER FUNCTION get_idno RETURN NUMBER IS
BEGIN
RETURN idno;
END;
MEMBER PROCEDURE display_details ( SELF IN OUT NOCOPY person_typ ) IS
BEGIN
-- use the put_line procedure of the DBMS_OUTPUT package to display details
DBMS_OUTPUT.put_line(TO_CHAR(idno) || ' - ' || name || ' - ' || phone);
END;
END;
CREATE TYPE people_typ AS TABLE OF person_typ; -- nested table type
CREATE TABLE department_persons (
dept_no NUMBER PRIMARY KEY,
dept_name CHAR(20),
dept_mgr person_typ DEFAULT person_typ(10,'John Doe',NULL),
dept_emps people_typ DEFAULT people_typ() ) -- instance of nested table type
NESTED TABLE dept_emps STORE AS dept_emps_tab;
INSERT INTO department_persons VALUES
( 101, 'Physical Sciences', person_typ(65,'Vrinda Mills', '1-650-555-0125'),
people_typ( person_typ(1, 'John Smith', '1-650-555-0135'),
person_typ(2, 'Diane Smith', NULL) ) );
INSERT INTO department_persons VALUES
( 104, 'Life Sciences', person_typ(70,'James Hall', '1-415-555-0101'),
people_typ() ); -- an empty people_typ table
select * from department_persons
return Collection as in your description
DEPT_NO DEPT_NAME DEPT_MGR.IDNO DEPT_MGR.NAME DEPT_MGR.PHONE DEPT_EMPS
1 101 Physical Sciences 65 Vrinda Mills 1-650-555-0125 <Collection>
2 104 Life Sciences 70 James Hall 1-415-555-0101 <Collection>
if you add TABLE you have what you need
SELECT e.*
FROM department_persons d, TABLE(d.dept_emps) e;
IDNO NAME PHONE
1 1 John Smith 1-650-555-0135
2 2 Diane Smith

Select statement REF oracle

I need some help with creating a select statement that will use a ref.
I managed to insert the values fine but when i try to extract the values with a where statement the output is either that the datatype is wrong or it will output the two tables and the data they both contain.
this is just an example:
Create or replace table1_Type as object {
id integer,
dateStart date,
etc varchar2(20));
}
/
create table table1 of table1_type;
Create or replace table2_type as object
id integer,
items varchar2(30),
datePurchased varchar2(20),
table1_Ref REF table1_type);
/
create table table2 of table2_type;
so i tried
Select * from table2 a, table1 b where table1.id = table2.table1_ref
Select * from table2 a, table1 b where table1.id = deref(b.table1_ref)
which does not work.
Iam new to this so sorry if i didnt explain properly. What i am trying to do is to select the items for example that were purchased by table1/id so the output should only display
the items that were purchased by a certain id.
Create or replace type table1_Type as object (
id integer,
dateStart date,
etc varchar2(20));
-- TYPE TABLE1_TYPE compiled
create table table1 of table1_type;
-- table TABLE1 created.
Create or replace type table2_type as object(
id integer,
items varchar2(30),
datePurchased varchar2(20),
table1_Ref REF table1_type);
-- TYPE TABLE2_TYPE compiled
create table table2 of table2_type;
--table TABLE2 created.
INSERT INTO table1 VALUES(table1_Type(1, SYSDATE, 'etc1...'));
INSERT INTO table1 VALUES(table1_Type(2, SYSDATE, 'etc2...'));
SELECT REF(t)
FROM table1 t
WHERE id = 1;
-- [TST.TABLE1_TYPE]
DECLARE
l_table_1_id_1 REF table1_Type;
l_table_1_id_2 REF table1_Type;
BEGIN
SELECT REF(t)
INTO l_table_1_id_1
FROM table1 t
WHERE id = 1;
SELECT REF(t)
INTO l_table_1_id_2
FROM table1 t
WHERE id = 2;
INSERT INTO table2 VALUES (21, 'item21', SYSDATE, l_table_1_id_1);
INSERT INTO table2 VALUES (22, 'item22', SYSDATE, l_table_1_id_2);
END;
-- anonymous block completed
SELECT COUNT(*) FROM table1;
-- 2
SELECT COUNT(*) FROM table2;
-- 2
SELECT * FROM table1;
/*
1 2013-06-16 03:51:50 etc1...
2 2013-06-16 03:52:05 etc2...
*/
SELECT * FROM table2;
/*
21 item21 2013-06-16 04:06:26 [TST.TABLE1_TYPE]
22 item22 2013-06-16 04:06:26 [TST.TABLE1_TYPE]
*/
SELECT *
FROM table1 t1
JOIN table2 t2
ON REF(t1) = t2.table1_Ref;
/*
1 2013-06-16 03:51:50 etc1... 21 item21 2013-06-16 04:06:26 [TST.TABLE1_TYPE]
2 2013-06-16 03:52:05 etc2... 22 item22 2013-06-16 04:06:26 [TST.TABLE1_TYPE]
*/