GBQ Execute Immediate Save Results to Table - google-bigquery

Hi All I am trying to same the results from this query into a table on GBQ. I have linked the previous question for reference
GBQ Convert Data types en Mass
Starting Code
execute immediate (select '''
select `Group`, ''' || (select string_agg('cast(' || Fruit || ' as Numeric) as ' || Fruit ) from (
select regexp_extract_all(to_json_string((select as struct * except(`Group`) from unnest([t]))), r'"([^"]+)":') Fruits
from `project.dataset.table` t limit 1), unnest(Fruits) Fruit) ||
''' from `project.dataset.table`''');
Various Attempts
Create or Replace Table1 as
execute immediate (select '''
select `Group`, ''' || (select string_agg('cast(' || Fruit || ' as Numeric) as ' || Fruit ) from (
select regexp_extract_all(to_json_string((select as struct * except(`Group`) from unnest([t]))), r'"([^"]+)":') Fruits
from `project.dataset.table` t limit 1), unnest(Fruits) Fruit) ||
''' from `project.dataset.table`''');
execute immediate (select '''
select `Group`, ''' || (select string_agg('cast(' || Fruit || ' as Numeric) as ' || Fruit )
INTO Table1
from (
select regexp_extract_all(to_json_string((select as struct * except(`Group`) from unnest([t]))), r'"([^"]+)":') Fruits
from `project.dataset.table` t limit 1), unnest(Fruits) Fruit) ||
''' from `project.dataset.table`''');
execute immediate (select '''
select `Group`, ''' || (select string_agg('cast(' || Fruit || ' as Numeric) as ' || Fruit ) from (
select regexp_extract_all(to_json_string((select as struct * except(`Group`) from unnest([t]))), r'"([^"]+)":') Fruits
from `project.dataset.table` t limit 1), unnest(Fruits) Fruit) ||
''' from `project.dataset.table`''') INTO Table1;
execute immediate (select '''
select `Group`, ''' || (select string_agg('cast(' || Fruit || ' as Numeric) as ' || Fruit ) from (
select regexp_extract_all(to_json_string((select as struct * except(`Group`) from unnest([t]))), r'"([^"]+)":') Fruits
from `project.dataset.table` t limit 1), unnest(Fruits) Fruit) ||
''' from `project.dataset.table`''' INTO Table1);

Use below approach
execute immediate (select '''create or replace table `project.dataset.table2` as
select `Group`, ''' || (select string_agg('cast(' || Fruit || ' as float64) as ' || Fruit ) from (
select regexp_extract_all(to_json_string((select as struct * except(`Group`) from unnest([t]))), r'"([^"]+)":') Fruits
from `project.dataset.table` t limit 1), unnest(Fruits) Fruit) ||
''' from `project.dataset.table`''');

Related

GBQ Execute Immediate into a CTE

I am building an application/script for users that do not have write access to the database. Normally I would use Execute Immediate and save that result into a table, and then pull from that table and continue on with the script. Is there a way to save results from Execute Immediate in either a sub query or CTE so that the script can continue on?
Example code where results are put into a table
execute immediate (select '''create or replace table `project.dataset.table2` as
select `Group`, ''' || (select string_agg('cast(' || Fruit || ' as float64) as ' || Fruit ) from (
select regexp_extract_all(to_json_string((select as struct * except(`Group`) from unnest([t]))), r'"([^"]+)":') Fruits
from `project.dataset.table` t limit 1), unnest(Fruits) Fruit) ||
''' from `project.dataset.table`''')
;
I would need something more along the lines of this, but it doesn't work
WITH CTEtable2 as (
execute immediate (select `Group`, ''' || (select string_agg('cast(' || Fruit || ' as float64) as ' || Fruit ) from (
select regexp_extract_all(to_json_string((select as struct * except(`Group`) from unnest([t]))), r'"([^"]+)":') Fruits
from `project.dataset.table` t limit 1), unnest(Fruits) Fruit) ||
''' from `project.dataset.table`''')
)
SELECT *
FROM CTEtable2

ORA-01489 : result of string concatenation to long

I am creating a dynamic query from a list of tables which are their in my table called : get_table_names
My Query :
SELECT listagg('SELECT '
|| ''''
|| tbl_name
|| ''''
|| ' AS TBL , COUNT(*) as CNT FROM '
|| 'TGT_MB.'
|| tbl_name
|| ' union all'
|| CHAR(10) ) WITH GROUP ( ORDER BY tbl_name) AS sql_str
FROM get_table_names;
My get_table_names as lots of table , at least 70 table names.
The query works fine for 10 tables but more then that it throws the error like below
ORA-01489 : result of string concatenation to long
Their is some option called EXTEND that option I cannot touch as I have low level privileges.
cannot make changes to it until DBA.
Any other work around would be much appreciated like using some XMLAGG or CLOB , BLOB
Can I respectively suggest that when posting a code snippet, make it sure it runs so that people can help you more easily. eg your code above took the following debugging before we could even start to see what you want to do
SQL> SELECT listagg('SELECT '
2 || ''''
3 || table_name
4 || ''''
5 || ' AS TBL , COUNT(*) as CNT FROM '
6 || 'TGT_MB.'
7 || table_name
8 || ' union all'
9 || CHAR(10) ) WITH GROUP ( ORDER BY table_name) AS sql_str
10 FROM dba_tables
11 where owner = 'SCOTT';
|| CHAR(10) ) WITH GROUP ( ORDER BY table_name) AS sql_str
*
ERROR at line 9:
ORA-00936: missing expression
SQL> SELECT listagg('SELECT '
2 || ''''
3 || table_name
4 || ''''
5 || ' AS TBL , COUNT(*) as CNT FROM '
6 || 'TGT_MB.'
7 || table_name
8 || ' union all'
9 || chr(10) ) WITH GROUP ( ORDER BY table_name) AS sql_str
10 FROM dba_tables
11 where owner = 'SCOTT';
|| chr(10) ) WITH GROUP ( ORDER BY table_name) AS sql_str
*
ERROR at line 9:
ORA-00923: FROM keyword not found where expected
Once those fixes are done, we end up here
SQL> SELECT listagg('SELECT '
2 || ''''
3 || table_name
4 || ''''
5 || ' AS TBL , COUNT(*) as CNT FROM '
6 || 'TGT_MB.'
7 || table_name
8 || ' union all'
9 || chr(10),'' ) WITHIN GROUP ( ORDER BY table_name) AS sql_str
10 FROM dba_tables
11 where owner = 'SCOTT';
SQL_STR
-------------------------------------------------------------------------------------
SELECT 'BONUS' AS TBL , COUNT(*) as CNT FROM TGT_MB.BONUS union all
SELECT 'DEPT' AS TBL , COUNT(*) as CNT FROM TGT_MB.DEPT union all
SELECT 'EMP' AS TBL , COUNT(*) as CNT FROM TGT_MB.EMP union all
SELECT 'EMP2' AS TBL , COUNT(*) as CNT FROM TGT_MB.EMP2 union all
SELECT 'SALGRADE' AS TBL , COUNT(*) as CNT FROM TGT_MB.SALGRADE union all
which ultimately blows up when we get too many tables
SQL> SELECT listagg('SELECT '
2 || ''''
3 || table_name
4 || ''''
5 || ' AS TBL , COUNT(*) as CNT FROM '
6 || 'TGT_MB.'
7 || table_name
8 || ' union all'
9 || chr(10),'' ) WITHIN GROUP ( ORDER BY table_name) AS sql_str
10 FROM dba_tables;
FROM dba_tables
*
ERROR at line 10:
ORA-01489: result of string concatenation is too long
Now even if we use a tool (eg
https://github.com/connormcd/listagg_clob) to get the results as a clob, then I imagine you are just going to fire that SQL at the database to get a count of all tables. If that is the case, then why do you need to LISTAGG at all, just build a script to do it, ie
SELECT 'SELECT '
|| ''''
|| table_name
|| ''''
|| ' AS TBL , COUNT(*) as CNT FROM '
|| 'TGT_MB.'
|| table_name
|| ' union all'
FROM dba_tables
Spool that to a file, trim the last union all and run it as a script.
Or even better...think about what benefit the output gives you. Why does anyone need the exact row count? Querying NUM_ROWS from USER_TABLES is probably sufficient.

Query optimization for multiple subsets

I have a "personnel basic information table", and it will be referenced by many "personnel subset tables" (there is no relationship between these subset tables, they are just a subset of the basic information of the personnel).
Now there is a requirement to merge the "personnel subset" into a column based on the person's foreign key and display it as a field of the "personnel basic information" (after the "personnel subset" is merged, each person record will only display one row) .
And the basic personnel information and personnel subset information will be passed into the query conditions, so these conditions are dynamically assembled.
It has been implemented now, but the problem is that the query needs to be optimized. Execution will be particularly slow once.
Database is oracle 11g.
Here is the sql generated by a certain query:
WITH in_hr_work_party_current AS (
SELECT
*
FROM
hr_work_party
WHERE
status = '1'
AND removaltime IS NULL
AND (
appointorg IS NOT NULL
OR NAME IS NOT NULL
)
),
qv_hr_work_party_current AS (
SELECT
empid,
listagg (
DECODE (
appointorg, NULL, '', 'oidstart' || appointorg || 'oidend-'
) || NVL (NAME, 'xx'),
','
) WITHIN GROUP (
ORDER BY
positiongrade ASC,
positionorder ASC,
appointtime ASC
) AS party_current_info
FROM
in_hr_work_party_current
GROUP BY
empid
),
in_position_postwork_main AS (
SELECT
po.empid,
working_coid_name AS post_main_coid_name,
working_poid_name AS post_main_poid_name,
officedepname AS post_main_officedepname,
post AS post_main_post,
posttype AS post_main_posttype,
workprof AS post_main_workprof,
starttime AS post_main_starttime,
FLOOR (
CEIL (
MONTHS_BETWEEN (
NVL (
TO_DATE (po.endtime, 'yyyy-MM-dd'),
SYSDATE
),
TO_DATE (po.starttime, 'yyyy-MM-dd')
)
) / 12
) || 'xx' AS post_main_years,
epo.entrytime AS post_main_entrytime,
ROW_NUMBER () OVER (
PARTITION BY po.empid
ORDER BY
po.opttime DESC
) AS rn
FROM
hr_work_postworkinfo po,
hr_work_empposition epo,
v_waf_ac_organ_base dep
WHERE
DEP.OID = PO.officedepid
AND epo.wepid = po.wepid
AND po.status = '1'
AND po.officetype = '00'
AND is_date (po.starttime) = 1
AND (
is_date (po.endtime) = 1
OR po.endtime IS NULL
)
AND epo.empsort = '01'
AND epo.subrelation = '01'
AND dep.orule LIKE '-xxxxx-%'
),
qv_position_postwork_main AS (
SELECT
*
FROM
in_position_postwork_main
WHERE
rn = 1
),
in_postworkinfo AS (
SELECT
po.*,
epo.entrytime,
epo.leavetime,
epo.empsort,
epo.empstatus,
epo.subrelation,
(
working_coid_name || DECODE (
working_poid_name, NULL, '', '-' || working_poid_name
) || DECODE (
officedepname, NULL, '', '-' || officedepname
)
) orgname,
FLOOR (
CEIL (
MONTHS_BETWEEN (
NVL (
TO_DATE (po.endtime, 'yyyy-MM-dd'),
SYSDATE
),
TO_DATE (po.starttime, 'yyyy-MM-dd')
)
) / 12
) postyear,
MOD (
CEIL (
MONTHS_BETWEEN (
NVL (
TO_DATE (po.endtime, 'yyyy-MM-dd'),
SYSDATE
),
TO_DATE (po.starttime, 'yyyy-MM-dd')
)
),
12
) postmonth,
ROW_NUMBER () OVER (
PARTITION BY po.empid
ORDER BY
po.starttime DESC
) rn,
COUNT (*) OVER (PARTITION BY po.empid) ct
FROM
hr_work_postworkinfo po,
HR_WORK_EMPPOSITION EPO,
V_WAF_AC_ORGAN_BASE DEP
WHERE
po.wepid = EPO.wepid
AND PO.officedepid = DEP.OID
AND is_date (starttime) = 1
AND (
is_date (endtime) = 1
OR endtime IS NULL
)
AND epo.empstatus != '05'
AND DEP.orule LIKE '-xxxxxx-%'
),
qv_hr_position_postwork AS (
SELECT
empid,
listagg (
'[' || starttime || 'xx' || NVL (endtime, 'xx') || 'xx' || orgname || 'xx' || NVL (post, 'xx') || 'xx' || NVL (posttype, 'xx') || 'xx' || DECODE (
postyear, 0, '', postyear || 'xx'
) || DECODE (
postmonth, 0, '', postmonth || 'xxxx'
) || 'xx' || NVL (postgradation, 'xx') || 'xxxx' || entrytime || 'xx' || empsort || 'xx' || empstatus || 'xx' || subrelation || ']',
','
) WITHIN GROUP (
ORDER BY
starttime DESC
) || (
CASE WHEN ct >= 5 THEN ',...' ELSE '' END
) work_info
FROM
in_postworkinfo
WHERE
rn <= 5
GROUP BY
empid,
ct
),
in_hr_emp_basicinfo AS (
SELECT
emp.*
FROM
hr_emp_basicinfo emp
),
qv_hr_emp_basicinfo_view AS (
SELECT
in_hr_emp_basicinfo.*
FROM
in_hr_emp_basicinfo
),
in_party AS (
SELECT
hr_work_party.NAME,
hr_work_party.empid,
hr_work_party.appointno,
hr_work_party.appointtime,
hr_work_party.removaltime,
hr_work_party.positiongrade,
NVL (
waf_ac_organ.NAME, hr_work_party.appointorg
) apporg,
ROW_NUMBER () OVER (
PARTITION BY hr_work_party.empid
ORDER BY
appointtime DESC,
removaltime DESC,
positiongrade ASC,
positionorder ASC
) rn,
COUNT (*) OVER (PARTITION BY hr_work_party.empid) ct
FROM
hr_work_party,
waf_ac_organ
WHERE
hr_work_party.status = '1'
AND hr_work_party.appointorg = waf_ac_organ.OID (+)
AND (
hr_work_party.appointorg IS NOT NULL
OR hr_work_party.NAME IS NOT NULL
)
),
qv_hr_work_party AS (
SELECT
empid,
listagg (
'[' || appointtime || 'xx' || NVL (removaltime, 'xx') || ' ' || apporg || '-' || NVL (NAME, 'xx') || DECODE (
NVL (appointno, 'xx'),
'xx',
'',
' - ' || appointno
) || ']',
','
) WITHIN GROUP (
ORDER BY
rn ASC
) || (
CASE WHEN ct > 10 THEN ',...' ELSE '' END
) app_party,
listagg (
'[' || appointtime || 'xx' || NVL (removaltime, 'xx') || ' ' || apporg || '-' || NVL (NAME, 'xx') || DECODE (
positiongrade, NULL, '', '(' || positiongrade || ')'
) || ']',
','
) WITHIN GROUP (
ORDER BY
rn ASC
) || (
CASE WHEN ct > 10 THEN ',...' ELSE '' END
) AS party_grade
FROM
in_party
WHERE
rn <= 10
GROUP BY
empid,
ct
)
SELECT
*
FROM
(
SELECT
dumb_table.*,
ROWNUM dumb_rn
FROM
(
SELECT
qv_hr_work_party_current.party_current_info,
qv_position_postwork_main.post_main_coid_name,
qv_position_postwork_main.post_main_poid_name,
qv_position_postwork_main.post_main_officedepname,
qv_position_postwork_main.post_main_years,
qv_hr_emp_basicinfo_view.age,
qv_hr_emp_basicinfo_view.NAME,
qv_hr_position_postwork.work_info
FROM
qv_hr_work_party_current,
qv_position_postwork_main,
qv_hr_work_party,
qv_hr_position_postwork,
qv_hr_emp_basicinfo_view
WHERE
1 = 1
AND qv_hr_emp_basicinfo_view.empid = qv_hr_work_party_current.empid(+)
AND qv_hr_emp_basicinfo_view.empid = qv_position_postwork_main.empid(+)
AND qv_hr_emp_basicinfo_view.empid = qv_hr_work_party.empid (+)
AND qv_hr_emp_basicinfo_view.empid = qv_hr_position_postwork.empid
AND qv_hr_emp_basicinfo_view.empid IS NOT NULL
ORDER BY
qv_hr_emp_basicinfo_view.sno
) dumb_table
)
WHERE
dumb_rn <= 30
AND dumb_rn > 0
enter image description here

Comparing column by column between two rows in Oracle DB

I need to write a query to compare column by column (ie: find differences) between two rows in the database. For example:
row1: 10 40 sometext 24
row2: 10 25 sometext 24
After the query executed, it should shows only the fields that have difference (ie: the second field)
Here's what I have done so far:
select table1.column1, table1.column2, table1.column3, table1.column4
from table1
where somefield in (field1, field2);
The above query will show me two rows one above another like this:
10 40 sometext 24
10 25 sometext 24
Then I have to manually do the comparison and it takes a lot of time b/c the row contains a lot of column.
So again my question is: How can I write a query that will show me only the columns that have differences??
Thanks
Use UNPIVOT clause (see http://www.oracle-developer.net/display.php?id=506) to turn columns into rows, then filter out the same rows (using GROUP BY HAVING COUNT and finally use PIVOT to get rows with different columns only.
To do this easily you need to query the metadata for the table to get each row. You can use the following code as a script.
Replace the define table_name with your table name and define yes_drop_it = NO. Put your raw WHERE syntax into the where_clause. The comparison logic always compares the first two rows returned for the where clause.
whenever sqlerror exit failure rollback;
set linesize 150
define test_tab_name = tst_cf_cols
define yes_drop_it = YES
define order_by = 1, 2
define where_clause = 1 = 1
define tab_owner = user
<<clearfirst>> begin
for clearout in (
select 'drop table ' || table_name as cmd
from all_tables
where owner = &&tab_owner and table_name = upper('&&test_tab_name')
and '&&yes_drop_it' = 'YES'
) loop
execute immediate clearout.cmd;
execute immediate '
create table &&test_tab_name as
select 10 as column1, 40 as column2, ''sometext'' as column3, 24 as column4 from dual
union all
select 10 as column1, 25 as column2, ''sometext'' as column3, 24 as column4 from dual
';
end loop;
end;
/
column cfsynt format a4000 word_wrap new_value comparison_syntax
with parms as (select 'parmquery' as cte_name, 'row_a' as corr_name_1, 'row_b' as corr_name_2 from dual)
select
'select * from (select ' || LISTAGG(cfcol || ' AS cf_' || trim (to_char (column_id, '000')) || '_' || column_name
, chr(13) || ', ') WITHIN GROUP (order by column_id)
|| chr(13) || ' from (select * from parmquery where row_number = 1) ' || corr_name_1
|| chr(13) || ', (select * from parmquery where row_number = 2) ' || corr_name_2
|| chr(13) || ') where ''DIFFERENT'' IN (' || LISTAGG ('cf_' || trim (to_char (column_id, '000')) || '_' || column_name, chr(13) || ', ') within group (order by column_id) || ')'
as cfsynt
from parms, (
select
'decode (' || corr_name_1 || '.' || column_name || ', ' || corr_name_2
|| '.' || column_name || ', ''SAME'', ''DIFFERENT'')'
as cfcol,
column_name,
column_id
from
parms,
all_tab_columns
where
owner = &&tab_owner and table_name = upper ('&&test_tab_name')
);
with parmquery as (select rownum as row_number, vals.* from (
select * from &&test_tab_name
where &&where_clause
order by &&order_by
) vals
) &&comparison_syntax
;

Transposing Undefined Number of Rows to Columns

I have a table that looks like the following:
Member, Contract_Start, Contract_End
1, 1/1/2011, 12/30/2011
1, 1/1/2012, 12/30/2012
1, 1/1/2013, 12/30/2013
2, 7/1/2012, 12/30/2012
2, 1/1/2013, 12/30/2013
Members could have as few as 1 contract and there is no upper limit on number of contracts.
I'd like to switch the table to look like below:
Member, Contract_Start1, Contract_End1, Contract_Start2, Contract_End2.....
1, 1/1/2011, 12/30/2011, 1/1/2012, 12/30/2012
2, 7/1/2012, 12/30/2012, 1/1/2013, 12/30/2013
Thanks for any help you can give.
I have used such solution and would advise not to go this path because maintenance/debugging of such dynamic sql becomes to hard over long time.
you can try the demo Here
IF object_id('Test_Transpose') IS NOT NULL
DROP TABLE Test_Transpose
GO
CREATE TABLE Test_Transpose
(
memberID INT NOT NULL
,csdate datetime2(2) NULL
,cedate datetime2(2) NULL
)
INSERT INTO Test_Transpose (memberid,csdate,cedate)
SELECT 1,'1/1/2001','1/1/2001'
UNION ALL SELECT 1,'1/2/2001','1/2/2001'
UNION ALL SELECT 1,'1/3/2001','1/2/2001'
UNION ALL SELECT 1,'1/4/2001','1/2/2001'
UNION ALL SELECT 1,'1/5/2001','1/2/2001'
UNION ALL SELECT 2,'1/2/2001','1/2/2001'
UNION ALL SELECT 2,'1/3/2001','1/3/2001'
UNION ALL SELECT 3,'1/2/2001','1/2/2001'
UNION ALL SELECT 3,'1/3/2001','1/3/2001'
UNION ALL SELECT 3,'1/4/2001','1/4/2001'
UNION ALL SELECT 4,'1/2/2001','1/2/2001'
DECLARE #SQL NVARCHAR(MAX)=''
,#Startdate_SelectColumnList NVARCHAR(MAX)=''
,#EndDate_SelectColumnList NVARCHAR(MAX)=''
,#Final_SelectColumnList NVARCHAR(MAX)=''
,#PivotINColumnList NVARCHAR(MAX)=''
,#MaxContractDateCount INT=1
SELECT TOP 1 #MaxContractDateCount = COUNT(1)
FROM Test_Transpose
GROUP BY memberid
ORDER BY COUNT(1) desc
WHILE #MaxContractDateCount > 0
BEGIN
SELECT #Startdate_SelectColumnList = N'['+CAST(#MaxContractDateCount AS sysname)+N'] AS '
+N'StartDate'+CAST(#MaxContractDateCount AS sysname)
+CASE WHEN #Startdate_SelectColumnList=N'' THEN N'' ELSE N',' END
+#Startdate_SelectColumnList
SELECT #Enddate_SelectColumnList = N'['+CAST(#MaxContractDateCount AS sysname)+N'] AS '
+N'EndDate'+CAST(#MaxContractDateCount AS sysname)
+CASE WHEN #Enddate_SelectColumnList=N'' THEN N'' ELSE N',' END
+#Enddate_SelectColumnList
SELECT #Final_SelectColumnList = N'StartDate'+CAST(#MaxContractDateCount AS sysname)+N','
+N'EndDate'+CAST(#MaxContractDateCount AS sysname)
+CASE WHEN #Final_SelectColumnList=N'' THEN N'' ELSE N',' END
+#Final_SelectColumnList
SELECT #PivotINColumnList = N'['+CAST(#MaxContractDateCount AS sysname)+N']'
+CASE WHEN #PivotINColumnList=N'' THEN N'' ELSE N',' END
+#PivotINColumnList
SET #MaxContractDateCount=#MaxContractDateCount-1
END
--debug stmt
--SELECT #Startdate_SelectColumnList,#Enddate_SelectColumnList,#Final_SelectColumnList,#PivotINColumnList
SET #SQL = N'
SELECT q1.memberid,'
+#Final_SelectColumnList
+N'
FROM
(
SELECT memberid,'
+#Startdate_SelectColumnList
+N'
FROM
(
SELECT memberid,csdate,ROW_NUMBER() OVER (PARTITION BY memberid ORDER BY memberid,csdate) rowid
FROM test_transpose
)q
PIVOT
(MAX(csdate) FOR rowid IN ('+#PivotINColumnList+N'))pvt
)q1
JOIN
(
SELECT memberid,'
+#Enddate_SelectColumnList
+N'
FROM
(
SELECT memberid,cedate,ROW_NUMBER() OVER (PARTITION BY memberid ORDER BY memberid,csdate) rowid
FROM test_transpose
)q
PIVOT
(MAX(cedate) FOR rowid IN ('+#PivotINColumnList+N'))pvt
)q2
ON q1.memberid = q2.memberid
'
PRINT #SQL
EXECUTE sp_executesql #SQL
I have a version of Postgres db wise doing this work. Here is my sample of code.
DROP TABLE IF EXISTS x;
CREATE TABLE x ( member NUMERIC , contract_start DATE, contract_end DATE);
INSERT INTO x VALUES( 1, '1/1/2011', '12/30/2011' )
,( 1, '1/1/2012', '12/30/2012' )
,( 1, '1/1/2013', '12/30/2013' )
,( 2, '7/1/2012', '12/30/2012' )
,( 2, '1/1/2013', '12/30/2013' )
,( 3, '1/1/2012', '12/30/2012' )
,( 3, '1/1/2013', '12/30/2013' )
,( 3, '8/1/2013', '12/30/2013' )
,( 3, '8/1/2013', '12/30/2013' );
-- CREATE LANGUAGE 'plpgsql';
-- DROP SEQUENCE IF EXISTS seq;
-- CREATE SEQUENCE seq;
CREATE OR REPLACE FUNCTION make_table() RETURNS void AS
$BODY$
DECLARE
i RECORD;
colcnt INTEGER;
BEGIN
DROP TABLE IF EXISTS tmptable;
SELECT max(count) INTO colcnt FROM (SELECT count(*) FROM x GROUP BY member) a;
EXECUTE 'CREATE TABLE tmptable (member integer ,' ||
(SELECT array_to_string(array_agg(x1.col1 || ' date, ' || x1.col2 || ' date'), ', ')
FROM (SELECT 'contract_start' || col col1
, 'contract_end'|| col col2 FROM generate_series(1,colcnt) t(col) ) x1 ) || ')';
EXECUTE 'INSERT INTO tmptable (member) SELECT DISTINCT member FROM x';
FOR i IN SELECT * FROM x ORDER BY member LOOP
PERFORM setval('seq',1);
EXECUTE 'UPDATE tmptable SET ' || x1.col
FROM (SELECT array_to_string(array_agg(' contract_start'
|| nextval('seq')-1 || ' = ''' || i.contract_start || ''', contract_end'
|| currval('seq')-1 || ' = ''' || i.contract_end ), ''', ') || ''' WHERE member = ' || member || ';' col
FROM x WHERE member = i.member GROUP BY member) x1;
END LOOP;
END;
$BODY$ LANGUAGE plpgsql;
SELECT * FROM make_table();
SELECT * FROM tmptable;
This is the first time I am answering. Hope this is relevant. (please run the commented only once).
Further the process of calling function each time on insert, update or delete can be automated with trigger. The following code will do that on replace of above one.
-- following commented lines for first time run :
-- CREATE LANGUAGE 'plpgsql';
-- DROP SEQUENCE IF EXISTS seq;
-- CREATE SEQUENCE seq;
-- DROP FUNCTION IF EXISTS make_table();
CREATE OR REPLACE FUNCTION make_table() RETURNS TRIGGER AS
$BODY$
DECLARE
i RECORD;
colcnt INTEGER;
BEGIN
DROP TABLE IF EXISTS tmptable;
SELECT max(count) INTO colcnt FROM (SELECT count(*) FROM x GROUP BY member) a;
EXECUTE 'CREATE TABLE tmptable (member integer ,' ||
(SELECT array_to_string(array_agg(x1.col1 || ' date, ' || x1.col2 || ' date'), ', ')
FROM (SELECT 'contract_start' || col col1
, 'contract_end'|| col col2 FROM generate_series(1,colcnt) t(col) ) x1 ) || ')';
EXECUTE 'INSERT INTO tmptable (member) SELECT DISTINCT member FROM x';
FOR i IN SELECT * FROM x ORDER BY member LOOP
PERFORM setval('seq',1);
EXECUTE 'UPDATE tmptable SET ' || x1.col
FROM (SELECT array_to_string(array_agg(' contract_start'
|| nextval('seq')-1 || ' = ''' || i.contract_start || ''', contract_end'
|| currval('seq')-1 || ' = ''' || i.contract_end ), ''', ') || ''' WHERE member = ' || member || ';' col
FROM x WHERE member = i.member GROUP BY member) x1;
END LOOP;
RETURN new;
END;
$BODY$ LANGUAGE plpgsql;
CREATE TRIGGER create_tmptable AFTER INSERT OR UPDATE OR DELETE OR TRUNCATE
ON x FOR STATEMENT EXECUTE PROCEDURE make_table();
-- For Test insert:
INSERT INTO x VALUES( 4, '1/1/2011', '12/30/2011' );
SELECT * FROM tmptable;