Execute Immediate to UPDATE via for loop - sql

I want to do a number of data updates as follows:
CREATE OR REPLACE PROCEDURE PROC_MDM_RUN_DATA_MAPPING
IS
sqlString VARCHAR2(2000) := '';
CURSOR csr_updates
IS
SELECT trim(table_name) AS table_name,
trim(column_name) AS column_name,
trim(old_value) AS old_value,
trim(new_value) AS new_value
FROM C_MDM_MAPPING_TABLE
WHERE area = 'MDM' and rownum < 10
AND run_update = 'Y' ;
BEGIN
FOR rec IN csr_updates
LOOP
BEGIN
sqlString := 'UPDATE ' || rec.table_name ||
' SET ' || rec.column_name || ' = ''' || rec.new_value || '''
WHERE TRIM(' || rec.column_name || ') = ''' || rec.old_value || ''';' ;
INSERT INTO C_MDM_ERROR_LOG ( msg ) VALUES ( sqlString );
dbms_output.put_line(sqlString) ; -- works, giving correct SQL
execute immediate sqlString ; -- fails
END;
END LOOP;
COMMIT;
END PROC_MDM_RUN_DATA_MAPPING;
/
The procedure compiles and generates the correct SQL for each of the data updates - example below:
UPDATE S_CFM_UNIVERSITY_ALL SET MASTER_UNI_NAME = 'Chung-Ang University'
WHERE TRIM(MASTER_UNI_NAME) = 'Chung Ang University';
but the execute immediate statement gives the error
"PLS-00103: Encountered the symbol "end-of-file" when expecting one of the following:
;
The symbol ";" was substituted for "end-of-file" to continue."
I have tried excluding the trailing semi colon, but that returns the error
ORA-00933: SQL command not properly ended
Any help appreciated, Thanks.

Try generating the SQL without the embedded newline, as in:
CREATE OR REPLACE PROCEDURE PROC_MDM_RUN_DATA_MAPPING IS
sqlString VARCHAR2(2000) := '';
CURSOR csr_updates IS
SELECT trim(table_name) AS table_name,
trim(column_name) AS column_name,
trim(old_value) AS old_value,
trim(new_value) AS new_value
FROM C_MDM_MAPPING_TABLE
WHERE area = 'MDM' AND
rownum < 10 AND
run_update = 'Y';
BEGIN
FOR rec IN csr_updates LOOP
sqlString := 'UPDATE ' || rec.table_name ||
' SET ' || rec.column_name || ' = ''' || rec.new_value ||
''' WHERE TRIM(' || rec.column_name || ') = ''' ||
rec.old_value || '''';
INSERT INTO C_MDM_ERROR_LOG ( msg ) VALUES ( sqlString );
dbms_output.put_line(sqlString) ; -- works, giving correct SQL
execute immediate sqlString ;
END LOOP;
COMMIT;
END PROC_MDM_RUN_DATA_MAPPING;
I removed the semicolon in the generated SQL as I believe it's not needed, and got rid of the BEGIN...END pair inside the loop which is also not needed.
I hope this helps. Share and enjoy.

Thanks for all the help - the answer above are correct in that the trailing semi colon is not needed, but as a follow up - there was a 2nd issue which was single quotes in the data.
Changing the cursor as follows fixed that issue
SELECT trim(table_name) AS table_name,
trim(column_name) AS column_name,
REPLACE(trim(old_value),'''','''''') AS old_value,
REPLACE(trim(new_value),'''','''''') AS new_value
FROM C_MDM_MAPPING_TABLE
Table and column names are known to be ok (they come from the system), but it gets back to "Never trust the input".
Thanks

Related

How to declare a number variable where I can save th count of table in my loop

I work wirh oracle Database. I have a plsql code where i run a query in a loop for multiple tables. so, table name is a variable in my code. I would like to have another variable (a single number) that I can call inside the loop and every time it counts the total rows of each table for me
declare
Cursor C_TABLE is
select trim(table_name) as table_name
from all_tables
where table_name in ('T1', 'T2', 'T3');
V_ROWNUM number;
begin
for m in C_TABLE
loop
for i in ( select column_name
from (
select c.column_name
from all_tab_columns c
where c.table_name = m.table_name
and c.owner = 'owner1'
)
)
loop
--I have this:
execute immediate ' insert into MY-table value (select ' || i.column_name || ' from ' || m.table_name || ')';
--I want this but it does not work of course:
V_ROWNUM := execute immediate 'select count(*) from ' || m.table_name;
execute immediate ' insert into MY-table value (select ' || i.column_name || ', ' || V_ROWNUM || ' from ' || m.table_name || ')';
end loop;
end loop;
end;
/
I count not use the "insert into" because I am not selecting from 1 table but the table I want to select from changes every round.
There are three things wrong with your dynamic SQL.
EXECUTE IMMEDIATE is not a function: the proper syntax is execute immediate '<<query>>' into <<variable>>.
An INSERT statement takes a VALUES clause or a SELECT but not both. SELECT would be very wrong in this case. Also note that it's VALUES not VALUE.
COLUMN_NAME is a string literal in the dynamic SQL so it needs to be in quotes. But because the SQL statement is itself a string, quotes in dynamic strings need to be escaped so it should be `'''||column_name||'''.
So the corrected version will look something like this
declare
Cursor C_TABLE is
select trim(table_name) as table_name
from all_tables
where table_name in ('T1', 'T2', 'T3');
V_ROWNUM number;
begin
for m in C_TABLE
loop
for i in ( select column_name
from (
select c.column_name
from all_tab_columns c
where c.table_name = m.table_name
and c.owner = 'owner1'
)
)
loop
execute immediate 'select count(*) from ' || m.table_name into V_ROWNUM;
execute immediate 'insert into MY_table values ( ''' || i.column_name || ''', ' || V_ROWNUM || ')';
end loop;
end loop;
end;
/
Dynamic SQL is hard because it turns compilation errors into runtime errors. It is good practice to write the statements first as static SQL. Once you have got the basic syntax right you can convert it into dynamic SQL.
you can't assign the result of execute immediate to a variable. it is not a function.
but you can do it by using the into_clause e.g.
execute immediate 'select count(*) from ' || m.table_name into V_ROWNUM ;

Oracle SQL Dynamic Query non UTF-8 Characters

I am trying to write a query that will provide all non UTF-8 encoded characters in a table, that is not specific to a column name. I am doing so by comparing the length of a column not equal to the byte length. %1 is the table name I want to check entered in a parameter. I am joining to user_tab_columns to get the COLUMN_NAME. I then want to take the COLUMN_NAME results and filter down to only show rows that have bad UTF-8 data (where length of a column is not equal to the byte length). Below is what I have come up with but it's not functioning. Can somebody help me tweak this query to get desired results?
SELECT
user_tab_columns.TABLE_NAME,
user_tab_columns.COLUMN_NAME AS ColumnName,
a.*
FROM %1 a
JOIN user_tab_columns
ON UPPER(user_tab_columns.TABLE_NAME) = UPPER('%1')
WHERE (SELECT * FROM %1 WHERE LENGTH(a.ColumnName) != LENGTHB(a.ColumnName))
In your query LENGTH(a.ColumnName) would represent the length of the column name, not the contents of that column. You can't use a value from one table as the column name in another table in static SQL.
Here's a simple demonstration of using dynamic SQL in an anonymous block to report which columns contain any multibyte characters, which is what comparing length with lengthb will tell you (discussed in comments to not rehashing that here):
set serveroutput on size unlimited
declare
sql_str varchar2(256);
flag pls_integer;
begin
for rec in (
select utc.table_name, utc.column_name
from user_tab_columns utc
where utc.table_name = <your table name or argument>
and utc.data_type in ('VARCHAR2', 'NVARCHAR2', 'CLOB', 'NCLOB')
order by utc.column_id
) loop
sql_str := 'select nvl(max(1), 0) from "' || rec.table_name || '" '
|| 'where length("' || rec.column_name || '") '
|| '!= lengthb("' || rec.column_name || '") and rownum = 1';
-- just for debugging, to see the generated query
dbms_output.put_line(sql_str);
execute immediate sql_str into flag;
-- also for debugging
dbms_output.put_line (rec.table_name || '.' || rec.column_name
|| ' flag: ' || flag);
if flag = 1 then
dbms_output.put_line(rec.table_name || '.' || rec.column_name
|| ' contains multibyte characters');
end if;
end loop;
end;
/
This uses a cursor loop to get the column names - I've included the table name too in case you want to wild-card or remove the filter - and inside that loop constructs a dynamic SQL statement, executes it into a variable, and then checks that variable. I've left some debugging output in to see what's happening. With a dummy table created as:
create table t42 (x varchar2(20), y varchar2(20));
insert into t42 values ('single byte test', 'single byte');
insert into t42 values ('single byte test', 'single byte');
insert into t42 values ('single byte test', 'single byte');
insert into t42 values ('single byte test', 'single byte');
insert into t42 values ('single byte test', 'multibyte ' || unistr('\00FF'));
running that block gets the output:
anonymous block completed
select nvl(max(1), 0) from "T42" where length("X") != lengthb("X") and rownum = 1
T42.X flag: 0
select nvl(max(1), 0) from "T42" where length("Y") != lengthb("Y") and rownum = 1
T42.Y flag: 1
T42.Y contains multibyte characters
To display the actual multibyte-containing values you could use a dynamic loop over the selected values:
set serveroutput on size unlimited
declare
sql_str varchar2(256);
curs sys_refcursor;
val_str varchar(4000);
begin
for rec in (
select utc.table_name, utc.column_name
from user_tab_columns utc
where utc.table_name = 'T42'
and utc.data_type in ('VARCHAR2', 'NVARCHAR2', 'CLOB', 'NCLOB')
order by utc.column_id
) loop
sql_str := 'select "' || rec.column_name || '" '
|| 'from "' || rec.table_name || '" '
|| 'where length("' || rec.column_name || '") '
|| '!= lengthb("' || rec.column_name || '")';
-- just for debugging, to see the generated query
dbms_output.put_line(sql_str);
open curs for sql_str;
loop
fetch curs into val_str;
exit when curs%notfound;
dbms_output.put_line (rec.table_name || '.' || rec.column_name
|| ': ' || val_str);
end loop;
end loop;
end;
/
Which with the same table gets:
anonymous block completed
select "X" from "T42" where length("X") != lengthb("X")
select "Y" from "T42" where length("Y") != lengthb("Y")
T42.Y: multibyte ÿ
As a starting point anyway; it would need some tweaking if you have CLOB values, or NVARCHAR2 or NCLOB - for example you could have one local variable of each type, include the data type in the outer cursor query, and fetch into the appropriate local variable.

Execute immediate select statement in Oracle

I am using execute immediate statement in one of my queries.
procedure p1 (p_pk1_column, p_pk2_column , p_conv_table_name ,p_MODUE_NAME )
is
v_select_string := 'SELECT'''||p_MODUE_NAME||''',''' ||p_pk1_column || ''',''' || p_pk2_column ||''' FROM ' ||p_conv_table_name || v_where_condition;
execute immediate v_select_string ;
dbms_output.put_line('string:'||v_select_string );
end p1;
Here I am calling p1 procedure in another procedure p2
PROCEDURE P2 IS
v_pk1_column:='a';
v_pk2_columnm:='b';
v_mod_name:='mOD1';
p1(v_pk1_column,v_pk2_columnm);
end p2;
/
In p2 procedure a, b are the column names of p_conv_table_name . I want to execute the select statement like select p_mod_name, a, b from p_conv_table_name where condition; so that it should give values for a and b columns in p_conv_table_name .
But it is executing like select p_mod, p_pk1_col,p_pk2_col from p_conv_table_name where condition;
So simply column names are selecting instead of values in that column.
Please suggest some approach to achieve values in that column.
Thanks in advance
When the SELECT statement is built the column names are surrounded in single-quotes, which turns them into string literals. Change your procedure to something like:
CREATE OR REPLACE PROCEDURE P1 (p_pk1_column IN VARCHAR2,
p_pk2_column IN VARCHAR2,
p_conv_table_name IN VARCHAR2,
p_MODUE_NAME IN VARCHAR2)
IS
v_select_string VARCHAR2(2000);
v_where_condition VARCHAR2(2000) := ' WHERE SOMETHING = SOMETHING_ELSE';
csr SYS_REFCURSOR;
v_val_1 VARCHAR2(2000);
v_val_2 VARCHAR2(2000);
v_mod_name VARCHAR2(2000);
BEGIN
v_select_string := 'SELECT ' || p_MODUE_NAME || ',' ||
p_pk1_column || ',' ||
p_pk2_column ||
' FROM ' || p_conv_table_name ||
v_where_condition;
dbms_output.put_line('string:' || v_select_string);
OPEN csr FOR v_select_string;
LOOP
FETCH csr INTO v_mod_name, v_val_1, v_val_2;
EXIT WHEN csr%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('v_mod_name=''' || v_mod_name || ''' ' ||
'v_val_1=''' || v_val_1 || ''' ' ||
'v_val_2=''' || v_val_2 || '''');
END LOOP;
CLOSE csr;
END P1;
I've also changed the code to OPEN and FETCH a cursor rather than using EXECUTE IMMEDIATE. OPEN and FETCH are generally more appropriate for use with a dynamic SELECT statement.
Share and enjoy.

Runtime Query in Oracle store procedures not returning proper result

This is first my stint with procedure and I am trying to execute below oracle procedure but facing some issue. Any inputs on this would be really helpful:
Issue:-----
I have a select query that returns two values:
src_Columns contains:
ID_ELEMENT
ID_ELEMENT_SA
Now, When I am trying to travesre a select-query(single column) result using "For Loop", I am not getting the values of the column rather I am getting its name only.
FOR columnItem IN (SELECT src_Columns FROM ELEM90_LNK_ELEM_BOSE)
LOOP
dbms_output.put_line('src_Columns 3: ' || columnItem.src_Columns);
query_test:= 'insert into ' || destination_Table || '(NAME,' || dest_Columns_Value || ') VALUES( ''' || src_name_Value || ''',''' || columnItem.dummyValue || ''')';
dbms_output.put_line('query_test:' || query_test);
execute immediate query_test;
END LOOP;
I mean the test query generated is following when i use variable name(src_columns):
insert into ATT_WTPART(NAME,STRINGVALUE) VALUES( 'ID_ELEMENT_SA','ID_ELEMENT_SA')
whereas if I use ID_ELEMENT_SA instead of src_Columns in FOR LOOP
FOR columnItem IN (SELECT ID_ELEMENT FROM ELEM90_LNK_ELEM_BOSE)
then I get proper values that are desired like
insert into ATT_WTPART(NAME,STRINGVALUE) VALUES( 'ID_ELEMENT_SA','ID05')
How can I make sure that I get the values even when I am using the variable name instead of any hard-coding
Below is the Complete Procedure:-------------
create or replace
PROCEDURE ELEM90_Lnk_Elem_ATT_WTPART_MK
AS
CURSOR targ_dest_relation IS
SELECT sourcecolumn FROM mapping where destinationtable='ATT_WTPART';
BEGIN
DECLARE
dest_Columns varchar2(1000);
src_Columns varchar2(1000);
src_Type varchar2(1000);
destination_Table varchar2(1000) := 'ATT_WTPART';
source_Table varchar2(1000) := 'ELEM90_LNK_ELEM_BOSE';
query_test varchar2(1000);
query_test2 varchar2(1000);
src_name varchar2(255);
src_Type_Value varchar2(255);
dest_Columns_Value varchar2(255);
src_name_Value varchar2(255);
for_query varchar2(1000);
for_query_data varchar2(1000);
dummyValue varchar2(1000);
BEGIN
FOR rec IN targ_dest_relation loop
dbms_output.put_line('destination_Table: ' || destination_Table);
dbms_output.put_line('source_Table: ' || source_Table);
src_Columns := rec.sourcecolumn;
dbms_output.put_line('src_Columns: ' || src_Columns);
src_Type := 'select data_type from user_tab_columns where table_name ='''||source_Table||'''and column_name='''|| src_Columns ||'''';
dbms_output.put_line('src_Type: ' || src_Type);
execute immediate src_Type INTO src_Type_Value;
dbms_output.put_line('src_Type_Value: ' || src_Type_Value);
dest_Columns := 'select DEST_COLUMN from ATT_PART_MAPPING where SOURCETYPE='''|| src_Type_Value || '''';
dbms_output.put_line('dest_Columns: ' || dest_Columns);
execute immediate dest_Columns INTO dest_Columns_Value;
dbms_output.put_line('dest_Columns_Value: ' || dest_Columns_Value);
src_name := 'select column_name from user_tab_columns where table_name ='''|| source_Table ||''' and column_name= ''' || src_Columns || '''';
dbms_output.put_line('src_name: ' || src_name);
execute immediate src_name INTO src_name_Value;
dbms_output.put_line('src_name_Value: ' || src_name_Value);
FOR columnItem IN (SELECT src_Columns FROM ELEM90_LNK_ELEM_BOSE)
LOOP
dbms_output.put_line('src_Columns 3: ' || columnItem.src_Columns);
query_test:= 'insert into ' || destination_Table || '(NAME,' || dest_Columns_Value || ') VALUES( ''' || src_name_Value || ''',''' || columnItem.dummyValue || ''')';
dbms_output.put_line('query_test:' || query_test);
execute immediate query_test;
END LOOP;
END loop;
END;
END;
The problem with the line
FOR columnItem IN (SELECT src_Columns FROM ELEM90_LNK_ELEM_BOSE)
is that src_Columns is a local variable. As a result, you end up selecting the same value for each row in ELEM90_LNK_ELEM_BOSE.
If you want the value of the local variable to be used as a column name in the query, you must use dynamic SQL instead. Try replacing FOR columnItem ... LOOP ... END LOOP with the following:
OPEN curs FOR 'SELECT ' || src_Columns || ' FROM ELEM90_LNK_ELEM_BOSE';
LOOP
FETCH curs INTO column_value;
EXIT WHEN curs%NOTFOUND;
dbms_output.put_line('src_Columns 3: ' || column_value);
query_test:= 'insert into ' || destination_Table || '(NAME,' || dest_Columns_Value || ') VALUES( ''' || src_name_Value || ''',''' || column_value || ''')';
dbms_output.put_line('query_test:' || query_test);
execute immediate query_test;
END LOOP;
CLOSE curs;
You will need to declare the following extra variables:
curs SYS_REFCURSOR;
column_value VARCHAR2(4000);
However, in truth it would probably be better to remove the loop entirely. You can replace it with an INSERT INTO ... SELECT ... FROM ... statement instead, such as the following:
EXECUTE IMMEDIATE 'INSERT INTO ' || destination_Table || ' (NAME,' ||
dest_Columns_Value || ') SELECT :name,' || src_Columns ||
' FROM ELEM90_LNK_ELEM_BOSE' USING src_name_Value;
This also gets rid of the need for the two local variables curs and column_value, and is also likely to be considerably faster, as there's no need to parse dynamic SQL once for each row in the destination table.
Is it working better if you try this one:
query_test:= 'insert into ' || destination_Table ||
'(NAME,'||dest_Columns_Value||') VALUES (:p1, :p2)';
execute immediate query_test USING src_name_Value, columnItem.dummyValue;
At least it should have a positive impact on performance.

oracle plsql select pivot without dynamic sql to group by [closed]

This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 9 years ago.
To whom it may respond to,
We would like to use SELECT function with PIVOT option at a 11g r2 Oracle DBMS.
Our query is like :
select * from
(SELECT o.ship_to_customer_no, ol.item_no,ol.amount
FROM t_order o, t_order_line ol
WHERE o.NO = ol.order_no and ol.item_no in (select distinct(item_no) from t_order_line))
pivot --xml
( SUM(amount) FOR item_no IN ( select distinct(item_no) as item_no_ from t_order_line));
As can be seen, XML is commented out, if run as PIVOT XML it gives the correct output in XML format, but we are required to get the data as unformatted pivot data, but this sentence throws error :
ORA-00936: missing expression
Any resolutions or ideas would be welcomed,
Best Regards
-------------dirty but working proc is below------------------------
updated the procedure by 17.01.2011 16:39 GMT :
PROCEDURE pr_pivot_item_by_ship_to (
p_location_code IN t_customer.location_code%TYPE,
p_customer_price_group IN t_customer.customer_price_group%TYPE,
p_shipment_date IN t_order.shipment_date%TYPE,
p_fasdat_status IN t_order.fasdat_status%TYPE,
p_order_type IN t_order.order_type%TYPE,
cur_pivot_item_by_ship_to OUT sys_refcursor
)
IS
v_sql VARCHAR2 (15000);
v_pivot_items VARCHAR2 (15000) := '';
v_query_items VARCHAR2 (15000) := '';
v_pivot_orders VARCHAR2 (15000) := '';
v_continue INT := 0;
BEGIN
/*GET ORDER NUMBERS*/
FOR cur_order_loop IN (SELECT DISTINCT (o.NO) AS order_no
FROM t_order o,
vw_customer_with_ship_to_info wwc
WHERE wwc.customer_price_group =
p_customer_price_group
AND wwc.location_code =
p_location_code
AND o.shipment_date = p_shipment_date
AND o.fasdat_status = p_fasdat_status
AND o.order_type = p_order_type
AND wwc.NO = o.customer_no)
LOOP
v_pivot_orders :=
''',''' || TO_CHAR (cur_order_loop.order_no)
|| v_pivot_orders;
v_pivot_orders := LTRIM (v_pivot_orders, ''',''');
END LOOP;
/*USE ORDER NUMBERS TO FIND ITEMS TO PIVOT BY SHIPMENT PLACE*/
FOR cur_loop IN
(SELECT DISTINCT (ol.item_no) AS item_no,
REPLACE
(REPLACE (SUBSTR (i.description, 1, 20), '''',
''),
'"',
' inch'
) AS description
FROM t_order_line ol, t_item i
WHERE ol.item_no = i.NO
AND ol.order_no IN (
SELECT DISTINCT (o.NO) AS order_no
FROM t_order o,
vw_customer_with_ship_to_info wwc
WHERE wwc.customer_price_group =
p_customer_price_group
AND wwc.location_code =
p_location_code
AND o.shipment_date = p_shipment_date
AND o.fasdat_status = p_fasdat_status
AND o.order_type = p_order_type
AND wwc.NO = o.customer_no))
LOOP
v_query_items := ',''' || cur_loop.item_no || '''' || v_query_items;
v_pivot_items :=
','''
|| cur_loop.item_no
|| ''' as "ad_'
|| cur_loop.description
|| '"'
|| v_pivot_items;
END LOOP;
v_query_items := LTRIM (v_query_items, ',');
v_pivot_items := LTRIM (v_pivot_items, ',');
v_sql :=
'select * from
(SELECT wwc.ship_to_customer_no||''-''|| wwc.ship_to_customer_name as "Müst. Adi ('
|| p_order_type
|| ')", ol.item_no,ol.amount
FROM t_order o, t_order_line ol,vw_customer_with_ship_to_info wwc
WHERE o.NO = ol.order_no
and wwc.no = o.customer_no
and ol.order_no in (
(SELECT DISTINCT (o.NO) AS order_no
FROM t_order o,
vw_customer_with_ship_to_info wwc
WHERE wwc.customer_price_group ='''
|| p_customer_price_group
|| '''
AND wwc.location_code =
'''
|| p_location_code
|| '''
AND o.shipment_date = '''
|| p_shipment_date
|| '''
AND o.fasdat_status = '
|| p_fasdat_status
|| '
AND o.order_type = '''
|| p_order_type
|| '''
AND wwc.NO = o.customer_no)
)
and OL.ITEM_NO in ('
|| v_query_items
|| ')
)
pivot
( SUM(amount) FOR item_no IN ('
|| v_query_items --v_pivot_items
|| '))';
--DBMS_OUTPUT.put_line ('TSQL ' || v_sql);
-- OPEN cur_pivot_item_by_ship_to FOR
-- SELECT v_sql
-- FROM DUAL;
BEGIN
OPEN cur_pivot_item_by_ship_to FOR v_sql;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
NULL;
WHEN OTHERS
THEN
IF SQLCODE = -936
THEN
NULL;
ELSE
pck_helper.pr_log_error
(SQLCODE,
'p_shipment_date:'
|| p_shipment_date
|| ','
|| 'cur_pivot_item_by_ship_to err. :'
|| SQLERRM,
'pr_pivot_item_by_ship_to'
);
END IF;
END;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
NULL;
WHEN OTHERS
THEN
pck_helper.pr_log_error (SQLCODE,
'p_shipment_date:'
|| p_shipment_date
|| ','
|| SQLERRM,
'pr_pivot_item_by_ship_to'
);
END pr_pivot_item_by_ship_to;
END pkg_report;
I don't have an Oracle 11 instance available to me now (PIVOT is new in Oracle 11) so I can only speculate. My guess is that the -- has ended up commenting out everything after the pivot, and Oracle has given you a 'missing expression' error because it was expecting to find something after pivot. Have you tried commenting out xml by surrounding it with /* and */ instead of putting -- before it?
If you were running this query from Java, say, then I would expect you to get a 'missing expression' error if you attempted to run the SQL in the following string:
String sql =
"select * from (SELECT o.ship_to_customer_no, ol.item_no,ol.amount " +
"FROM t_order o, t_order_line ol " +
"WHERE o.NO = ol.order_no and ol.item_no in (select distinct(item_no) from t_order_line)) " +
"pivot --xml " +
"( SUM(amount) FOR item_no IN ( select distinct(item_no) as item_no_ from t_order_line))";
Because this SQL string gets concatenated into one line, the -- in front of xml will cause Oracle to ignore everything in the query after it.
EDIT: In the procedure you added, what concerns me is this part (abbreviated):
v_sql := 'begin
select * from ...;
end;';
open DENEME for
select v_sql from dual;
I don't understand why you're using BEGIN and END within v_sql. Try removing them.
Also, take care not to leave a trailing semicolon in your SQL statement string. The following will give an ORA-00911: invalid character error if you try to run it using EXECUTE IMMEDIATE or suchlike:
v_sql := 'select * from dual;'; /* note the semicolon inside the string */
but the following will be OK:
v_sql := 'select * from dual';
Finally, it appears you want to return the results of this SQL query, not the query itself as a string. Replacing your open DENEME for ... statement with the following should do what you want:
open DENEME for v_sql;
EDIT 2: In your procedure, there is a commented-out call to DBMS_OUTPUT.PUT_LINE. Have you verified that the SQL generated here is correct? In particular, are you sure that none of the values used to form v_query_items and v_pivot_items have ' characters in them?
It may be that there is a problem using PIVOT with dynamic SQL. I don't know, and can't help much more because I don't have access to Oracle 11 here. Do you still get errors if you simplify the SQL to a much smaller query, but one that still has PIVOT in it? In other words, find a simple PIVOT query that works (e.g. you can run it successfully in SQL Developer or suchlike), write a procedure such as the following, and see whether you get any data back from it:
CREATE OR REPLACE PROCEDURE dynamic_pivot_test(
results OUT SYS_REFCURSOR
)
AS
BEGIN
OPEN results FOR 'SELECT ...'; /* put the PIVOT query here. */
END;
/
the procedure edited at 17.01.2011 is dirty but working, looping cursors should change, better err. handling for the dynamic sql ( currently second to none), will try to improve it when got time. Thank you all for your help.