Skip to Main Content
  • Questions
  • Dynamically Creating Next Month as Column in Table

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Laura.

Asked: July 09, 2002 - 11:00 am UTC

Last updated: May 18, 2004 - 2:40 pm UTC

Version: 8.1.7

Viewed 1000+ times

You Asked

I want to dynamically create the next month. I do not want to copy and paste the last line and change the month to the next month manually. For example, the next line would be SUM((DECODE(YEAR_MONTH,'200206',SUM_SAMPLES,NULL))) SAMPLES_MADE_200206. I am not asking for you to do it. What I am asking is where I can find a good example to learn from. I was reading the questions on www.orafaq.org and I must admit I am getting confused. I am assuming that I would want to use a cursor. If my question does not belong here, I apologize. Any assistance would be greatly appreciated. I do not have much experience in cursors.

INSERT INTO LJC_TABLE
SELECT ACM_MSTRACCOUNTKEY,
ACM_MEDPROVNO,
ACM_EXTPHYSRXID,
SMPL_ID,
SMPL_DESC_TEXT,
SUM((DECODE(YEAR_MONTH,'200001',SUM_SAMPLES,NULL))) SAMPLES_MADE_200001,
SUM((DECODE(YEAR_MONTH,'200002',SUM_SAMPLES,NULL))) SAMPLES_MADE_200002,
SUM((DECODE(YEAR_MONTH,'200003',SUM_SAMPLES,NULL))) SAMPLES_MADE_200003,
SUM((DECODE(YEAR_MONTH,'200004',SUM_SAMPLES,NULL))) SAMPLES_MADE_200004,
SUM((DECODE(YEAR_MONTH,'200005',SUM_SAMPLES,NULL))) SAMPLES_MADE_200005,
SUM((DECODE(YEAR_MONTH,'200006',SUM_SAMPLES,NULL))) SAMPLES_MADE_200006,
SUM((DECODE(YEAR_MONTH,'200007',SUM_SAMPLES,NULL))) SAMPLES_MADE_200007,
SUM((DECODE(YEAR_MONTH,'200008',SUM_SAMPLES,NULL))) SAMPLES_MADE_200008,
SUM((DECODE(YEAR_MONTH,'200009',SUM_SAMPLES,NULL))) SAMPLES_MADE_200009,
SUM((DECODE(YEAR_MONTH,'200010',SUM_SAMPLES,NULL))) SAMPLES_MADE_200010,
SUM((DECODE(YEAR_MONTH,'200011',SUM_SAMPLES,NULL))) SAMPLES_MADE_200011,
SUM((DECODE(YEAR_MONTH,'200012',SUM_SAMPLES,NULL))) SAMPLES_MADE_200012,
SUM((DECODE(YEAR_MONTH,'200101',SUM_SAMPLES,NULL))) SAMPLES_MADE_200101,
SUM((DECODE(YEAR_MONTH,'200102',SUM_SAMPLES,NULL))) SAMPLES_MADE_200102,
SUM((DECODE(YEAR_MONTH,'200103',SUM_SAMPLES,NULL))) SAMPLES_MADE_200103,
SUM((DECODE(YEAR_MONTH,'200104',SUM_SAMPLES,NULL))) SAMPLES_MADE_200104,
SUM((DECODE(YEAR_MONTH,'200105',SUM_SAMPLES,NULL))) SAMPLES_MADE_200105,
SUM((DECODE(YEAR_MONTH,'200106',SUM_SAMPLES,NULL))) SAMPLES_MADE_200106,
SUM((DECODE(YEAR_MONTH,'200107',SUM_SAMPLES,NULL))) SAMPLES_MADE_200107,
SUM((DECODE(YEAR_MONTH,'200108',SUM_SAMPLES,NULL))) SAMPLES_MADE_200108,
SUM((DECODE(YEAR_MONTH,'200109',SUM_SAMPLES,NULL))) SAMPLES_MADE_200109,
SUM((DECODE(YEAR_MONTH,'200110',SUM_SAMPLES,NULL))) SAMPLES_MADE_200110,
SUM((DECODE(YEAR_MONTH,'200111',SUM_SAMPLES,NULL))) SAMPLES_MADE_200111,
SUM((DECODE(YEAR_MONTH,'200112',SUM_SAMPLES,NULL))) SAMPLES_MADE_200112,
SUM((DECODE(YEAR_MONTH,'200201',SUM_SAMPLES,NULL))) SAMPLES_MADE_200201,
SUM((DECODE(YEAR_MONTH,'200202',SUM_SAMPLES,NULL))) SAMPLES_MADE_200202,
SUM((DECODE(YEAR_MONTH,'200203',SUM_SAMPLES,NULL))) SAMPLES_MADE_200203,
SUM((DECODE(YEAR_MONTH,'200204',SUM_SAMPLES,NULL))) SAMPLES_MADE_200204,
SUM((DECODE(YEAR_MONTH,'200205',SUM_SAMPLES,NULL))) SAMPLES_MADE_200205
FROM LJC_SAMPLES_PREPIVOT
WHERE SMPL_ID=1900000520
GROUP BY ACM_MSTRACCOUNTKEY, ACM_MEDPROVNO, ACM_EXTPHYSRXID, SMPL_ID, SMPL_DESC_TEXT
ORDER BY ACM_MSTRACCOUNTKEY, ACM_MEDPROVNO, ACM_EXTPHYSRXID, SMPL_ID, SMPL_DESC_TEXT


and Tom said...

You need to dynamically generate this insert then -- it could look like this:


create or replace procedure do_insert( p_start_month in date )
as
l_stmt long;
l_month_to date default add_months( trunc(sysdate,'month'), -1 );
l_curr_month date default p_start_month;
begin
l_stmt := 'insert into ljc_taable
select CM_MSTRACCOUNTKEY, ACM_MEDPROVNO,
ACM_EXTPHYSRXID, SMPL_ID, SMPL_DESC_TEXT,';

for i in 0 .. months_between( l_month_to, p_start_month )
loop
l_stmt := l_stmt ||
'sum((decode(year_month,''' ||
to_char(l_curr_month, 'yyyymm' ) ||
''', sum_samples,0))) samples_made_' ||
to_char(l_curr_month,'yyyymm') || ',';
l_curr_month := add_months(l_curr_month,1);
end loop;

l_stmt := rtrim(l_stmt,',') || ' FROM LJC_SAMPLES_PREPIVOT
WHERE SMPL_ID=1900000520
GROUP BY ACM_MSTRACCOUNTKEY, ACM_MEDPROVNO, ACM_EXTPHYSRXID, SMPL_ID,
SMPL_DESC_TEXT
ORDER BY ACM_MSTRACCOUNTKEY, ACM_MEDPROVNO, ACM_EXTPHYSRXID, SMPL_ID,
SMPL_DESC_TEXT';

execute immediate l_stmt;
end;
/


exec do_insert( to_date( '200001','yyyymm' ) );


given that it is july right now -- that'll create an insert upto 200206


Rating

  (9 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Dynamically Creating Next Month as Column in Table

Laura Cerniglia, July 10, 2002 - 9:36 am UTC

Thank you very much for educating me. This is the type of example that I have been looking for.

Laura Cerniglia, July 12, 2002 - 1:23 pm UTC

I am new to this web site and I am not sure if I should place my follow up question here.

I have been working with the code that you provided.  It compiles sucessfully.  However, when I run it I get the following message:

13:18:21 SQL> exec do_insert( to_date( '200001','yyyymm' ) );
BEGIN do_insert( to_date( '200001','yyyymm' ) ); END;

*
ERROR at line 1:
ORA-00904: invalid column name
ORA-06512: at "ORASOPS.DO_INSERT", line 25
ORA-06512: at line 1

I have been trying to debug it and I am getting nowhere.  Please help.  I am very frustrated with myself.  Could it be an issue with the privileges set to the logon that I am using? 

Tom Kyte
July 12, 2002 - 5:37 pm UTC

Well, does the table you are inserting into HAVE those columns?

I guess we need to backup a moment -- the procedure above will create a sql statement to insert into an existing table, which always has an existing set of columns.

Does this procedure also need to dynamically "add" a column on the fly -- for the most recent month?

Laura Cerniglia, July 12, 2002 - 6:16 pm UTC

Does this procedure also need to dynamically "add" a column on the fly -- for
the most recent month?


Yes. This code is part of a larger script. In the script I drop the table and recreate it following your example. Then I want to insert into it following your example.


CREATE OR REPLACE PROCEDURE DO_INSERT1 (P_START_MONTH )
AS
L_STMT LONG;
L_MONTH_TO DATE DEFAULT ADD_MONTHS( TRUNC(SYSDATE,'MONTH'), -2 );
L_CURR_MONTH DATE DEFAULT TO_DATE(P_START_MONTH,'YYYYMM');

BEGIN
L_STMT := 'CREATE TABLE LJC_TABLE (
ACM_MSTRACCOUNTKEY NUMBER(10),
ACM_MEDPROVNO VARCHAR2(14),
ACM_EXTPHYSRXID NUMBER(10),
SMPL_ID NUMBER(16),
SMPL_DESC_TEXT VARCHAR2(80),' || CHR(10);

FOR I IN 0 .. MONTHS_BETWEEN( L_MONTH_TO, L_CURR_MONTH )
LOOP
L_STMT := L_STMT ||
'SAMPLES_MADE_''' ||
TO_CHAR(L_CURR_MONTH, 'YYYYMM' ) ||
'''NUMBER(5),' || CHR(10);

L_CURR_MONTH := ADD_MONTHS(L_CURR_MONTH,1);
END LOOP;

L_STMT := RTRIM(L_STMT,',') || ' )';

EXECUTE IMMEDIATE L_STMT;
END;




create or replace procedure do_insert( p_start_month in date )
as
l_stmt long;
l_month_to date default add_months( trunc(sysdate,'month'), -1 );
l_curr_month date default p_start_month;
begin
l_stmt := 'insert into ljc_table
select CM_MSTRACCOUNTKEY, ACM_MEDPROVNO,
ACM_EXTPHYSRXID, SMPL_ID, SMPL_DESC_TEXT,';

for i in 0 .. months_between( l_month_to, p_start_month )
loop
l_stmt := l_stmt ||
'sum((decode(year_month,''' ||
to_char(l_curr_month, 'yyyymm' ) ||
''', sum_samples,0))) samples_made_' ||
to_char(l_curr_month,'yyyymm') || ',';
l_curr_month := add_months(l_curr_month,1);
end loop;

l_stmt := rtrim(l_stmt,',') || ' FROM LJC_SAMPLES_PREPIVOT
WHERE SMPL_ID=1900000520
GROUP BY ACM_MSTRACCOUNTKEY, ACM_MEDPROVNO, ACM_EXTPHYSRXID, SMPL_ID,
SMPL_DESC_TEXT
ORDER BY ACM_MSTRACCOUNTKEY, ACM_MEDPROVNO, ACM_EXTPHYSRXID, SMPL_ID,
SMPL_DESC_TEXT';

execute immediate l_stmt;
end;


Something tells me that what you told me works for the second part (the inserting) but that for the creation of the table I need to do something different.


Tom Kyte
July 13, 2002 - 10:11 am UTC

QED:

create or replace procedure make_my_table_please( p_start_month in date )
as
l_stmt long;
l_month_to date default add_months( trunc(sysdate,'month'), -1 );
l_curr_month date default p_start_month;
begin
execute immediate 'drop table ljc_table';

l_stmt := 'create table ljc_table AS
select CM_MSTRACCOUNTKEY, ACM_MEDPROVNO,
ACM_EXTPHYSRXID, SMPL_ID, SMPL_DESC_TEXT,';

for i in 0 .. months_between( l_month_to, p_start_month )
loop
l_stmt := l_stmt ||
'sum((decode(year_month,''' ||
to_char(l_curr_month, 'yyyymm' ) ||
''', sum_samples,0))) samples_made_' ||
to_char(l_curr_month,'yyyymm') || ',';
l_curr_month := add_months(l_curr_month,1);
end loop;

l_stmt := rtrim(l_stmt,',') || ' FROM LJC_SAMPLES_PREPIVOT
WHERE SMPL_ID=1900000520
GROUP BY ACM_MSTRACCOUNTKEY, ACM_MEDPROVNO, ACM_EXTPHYSRXID, SMPL_ID,
SMPL_DESC_TEXT
ORDER BY ACM_MSTRACCOUNTKEY, ACM_MEDPROVNO, ACM_EXTPHYSRXID, SMPL_ID,
SMPL_DESC_TEXT';

execute immediate l_stmt;
end;

the procedure should therefore create the table after dropping it... The paranoid (or maybe better described as those that don't want to get a phone call in the middle of the night from someone asking "hey, where is my table") might use this instead:

create or replace procedure do_insert( p_start_month in date )
as
l_stmt long;
l_month_to date default add_months( trunc(sysdate,'month'), -1 );
l_curr_month date default p_start_month;
begin
l_stmt := 'create table ljc_table_temp AS
select CM_MSTRACCOUNTKEY, ACM_MEDPROVNO,
ACM_EXTPHYSRXID, SMPL_ID, SMPL_DESC_TEXT,';

for i in 0 .. months_between( l_month_to, p_start_month )
loop
l_stmt := l_stmt ||
'sum((decode(year_month,''' ||
to_char(l_curr_month, 'yyyymm' ) ||
''', sum_samples,0))) samples_made_' ||
to_char(l_curr_month,'yyyymm') || ',';
l_curr_month := add_months(l_curr_month,1);
end loop;

l_stmt := rtrim(l_stmt,',') || ' FROM LJC_SAMPLES_PREPIVOT
WHERE SMPL_ID=1900000520
GROUP BY ACM_MSTRACCOUNTKEY, ACM_MEDPROVNO, ACM_EXTPHYSRXID, SMPL_ID,
SMPL_DESC_TEXT
ORDER BY ACM_MSTRACCOUNTKEY, ACM_MEDPROVNO, ACM_EXTPHYSRXID, SMPL_ID,
SMPL_DESC_TEXT';

execute immediate 'rename ljc_table to ljc_table_' ||
to_char(sysdate,'YYYYMMDDHH24MISS');
execute immediate 'rename ljc_table_temp to ljc_table';
end;


don't drop the table and then fill it -- create a temp table, after that succeeds, rename (hide) the current table and rename the temp table to be current. That way, if something goes horribly wrong, at least you have the old one to revert to quickly. You can drop that later if and when you feel like it.



Dynamically Creating Next Month as Column in Table

Laura Cerniglia, July 17, 2002 - 4:46 pm UTC

I now have it working.

Thank you so very much for your help!!! You are an Oracle Guardian Angel.

I have also learned that it helps if your logon is set up right. This is what my DBA wrote to me:
"One of the problems with Execute Immediate is you can use commands like create table only if the user is granted that privilege directly rather than through a role, which you already had. I gave create table privilege to you directly, which should take care of the problem. "

A reader, August 14, 2002 - 3:30 pm UTC

Tom:
Refering to the original question and your response:

I did a similar crosstab query sometime back. And I was faced with the same problem of dynamically generating the timeperiod for the insert. I created a function which returns the corresponding time period (current month, prev month, etc....), but I found that the query became sooo slow when using the function. The reason was the recursive SQL performed for the function. Eventhough the function was very simple, it was called millions of times which probably contributed to the slowdown (am I correct?). I worked around that using macro substitution in SQLPlus. But as soon as I saw your function, I adopted it.

It was little unexpected for me to have the query slow down somuch due to the usage of function. Can you please comment on that? Thanks again your beautiful solution!

Tom Kyte
August 14, 2002 - 3:40 pm UTC

if the function was in plsql and it took 1/100 of a second to run (pretty good for plsql) and you called it a million tims that is 10,000 seconds. If it ran in 1/1000 of a second, that is still 1,000 seconds (over 16 minutes).

PLSQL called from SQL adds some overhead. In a simple query -- you don't see it. Do anything complex 1,000,000 times and it can add up!

Laura Cerniglia, September 22, 2002 - 7:30 pm UTC

Now my users want me to do the same thing at a day level instead of a month level.

Following your example, I have the below:

CREATE OR REPLACE PROCEDURE LJC_FAR_TEST
AS
STMT1 LONG;
P_START_DAY DATE DEFAULT TO_DATE( '20020101','YYYYMMDD');
L_DAY_TO DATE DEFAULT ADD_MONTHS( TRUNC(SYSDATE,'DAY'), 0 );
L_CURR_DAY1 DATE DEFAULT P_START_DAY;
---
BEGIN
---
STMT1 := 'CREATE TABLE LJC_FAR_SUM_TABLE_AT_TERRLEVEL (
TRM_NAME VARCHAR2(15),
CBU VARCHAR2(15),
ABM VARCHAR2(15),
RABM VARCHAR2(15),
DABM VARCHAR2(15),
DESCRIPTION VARCHAR2(80),';
FOR I IN 0 .. MONTHS_BETWEEN( L_DAY_TO, P_START_DAY )
LOOP
STMT1 := STMT1 ||
'DATE_' ||
TO_CHAR(L_CURR_DAY1, 'YYYYMMDD' ) ||
' NUMBER(5),';
L_CURR_DAY1 := ADD_MONTHS(L_CURR_DAY1,1);
END LOOP;
STMT1 := RTRIM(STMT1,',') || ' )';
---
EXECUTE IMMEDIATE STMT1;
---
COMMIT;
---
END;

Basically, I want to create date fields like the following:
DATE_20020801 NUMBER(5)
DATE_20020802 NUMBER(5)
DATE_20020803 NUMBER(5)
DATE_20020804 NUMBER(5)
DATE_20020805 NUMBER(5)
DATE_20020806 NUMBER(5)
DATE_20020807 NUMBER(5)
DATE_20020808 NUMBER(5)
DATE_20020809 NUMBER(5)
DATE_20020810 NUMBER(5)
DATE_20020811 NUMBER(5)
DATE_20020812 NUMBER(5)
DATE_20020813 NUMBER(5)
DATE_20020814 NUMBER(5)
DATE_20020815 NUMBER(5)
DATE_20020816 NUMBER(5)
DATE_20020817 NUMBER(5)
DATE_20020818 NUMBER(5)
DATE_20020819 NUMBER(5)
DATE_20020820 NUMBER(5)
DATE_20020821 NUMBER(5)
DATE_20020822 NUMBER(5)
DATE_20020823 NUMBER(5)
DATE_20020824 NUMBER(5)
DATE_20020825 NUMBER(5)
DATE_20020826 NUMBER(5)
DATE_20020827 NUMBER(5)
DATE_20020828 NUMBER(5)
DATE_20020829 NUMBER(5)
DATE_20020830 NUMBER(5)
DATE_20020831 NUMBER(5)
DATE_20020901 NUMBER(5)
DATE_20020902 NUMBER(5)
DATE_20020903 NUMBER(5)
DATE_20020904 NUMBER(5)
DATE_20020905 NUMBER(5)
DATE_20020906 NUMBER(5)
DATE_20020907 NUMBER(5)
DATE_20020908 NUMBER(5)
DATE_20020909 NUMBER(5)
DATE_20020910 NUMBER(5)
DATE_20020911 NUMBER(5)
DATE_20020912 NUMBER(5)
DATE_20020913 NUMBER(5)
DATE_20020914 NUMBER(5)
DATE_20020915 NUMBER(5)
DATE_20020916 NUMBER(5)
DATE_20020917 NUMBER(5)
DATE_20020918 NUMBER(5)
DATE_20020919 NUMBER(5)
DATE_20020920 NUMBER(5)
DATE_20020921 NUMBER(5)
DATE_20020922 NUMBER(5)


My code above obviously does not do it at the day level. I tried looking for functions to use. I found the NEXT_DAY and LAST_DAY functions. Would you use these? I am afraid that I am making this more difficult then it needs to be.


Tom Kyte
September 23, 2002 - 7:22 am UTC

FOR I IN 0 .. (l_day_to-p_start_day-1)
LOOP
STMT1 := STMT1 ||
'DATE_' ||
TO_CHAR( p_start_day+i, 'YYYYMMDD' ) ||
' NUMBER(5),';
END LOOP;
....


(the commit you have is superflorous, DDL commits for you)

Laura Cerniglia, October 09, 2002 - 3:41 pm UTC

Thanks!!!

dynamically creating a CTAS

kit, May 18, 2004 - 7:43 am UTC

Tom,
If I wanted to search for a table in all_objects, if foound then do an insert append else do a CTAS supplying the tablespace and initial next.
do yu have elegant solution

Tom Kyte
May 18, 2004 - 2:40 pm UTC

do a CTAS and upon failure due to "already exists", insert /*+ append */

Dynamic columns in cross tab

Soni Kumari, November 20, 2006 - 7:58 am UTC

Hi Tom,
I have to write a query which needs to give a report like a cross-tab report where the column is generated dynamically. The column is a date column where I know the startdate and end date and there is difference of 7 days.
I need qty data against those columns.

I want to give you the data but its coming coming in a format.
Please see the data format on the link.
</code> http://www.orafaq.com/forum/r/msg_id/204341/86219/ <code>

There are three other columns.
First column gives an ID, second gives respid and third gives description filed data. The data under date columns is the quantity data based on a date columnwise and on ID, respID and Descr row wise.
The dates column are created dynamically and the data needs to be displayed under them dynamically.
Please help me to work on this report.

Thanks,
Soni

More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library