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

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.

Related

Postgresql For Loop Problems :(

I wanted to make a table that sanity checked record integrity for any duplications among my db.
I have a table currently with object names (tables) and their primary keys:
I want to create a procedure that loops through those objects with their keys, and inserts into a separate table the count of duplicates:
below is my code, but I've never done anything like this before and am new to postgres. What I have is from hours of googling/researching but every time I get closer I get a new error and am quite stuck :( Any insights would be greatly appreciated.
My newest error is I believe from my quote_ident(object_names). I don't want to query the column as postgres is reading it, I'd want that to be a raw string:
code:
do $$
declare
object_names varchar;
keys varchar;
rec record;
begin
for rec in select object_name, key from mfr_incentives.public.t_jh_dup_check
loop
object_names = rec.object_name;
keys = rec.key;
execute 'insert into mfr_incentives.public.t_jh_dup_check_final_output
select * from
(select ' || quote_ident(object_names) || ', ' || quote_ident(keys) || ', ' || ' count(*), current_date from
( select ' || keys || ', count(*)
from ' || object_names ||
' group by ' || keys || ' having count(*) > 1
) a
) a';
end loop;
end;
$$;
Found out my problem!
Being unfamiliar with the topic I finally found that I wanted quote_literal() instead of quote_ident().
The below works:
create or replace procedure public.proc_jh_dup_check()
language plpgsql
--IT WORKS NOW
as $$
declare
rec record;
begin
for rec in select object_name, key from mfr_incentives.public.t_jh_dup_check
loop
execute 'insert into mfr_incentives.public.t_jh_dup_check_final_output
select * from
(select ' || quote_literal(rec.object_name) || ', ' || quote_literal(rec.key) || ', ' || ' count(*), current_date from
( select ' || rec.key || ', count(*)
from ' || rec.object_name ||
' group by ' || rec.key || ' having count(*) > 1
) a
) a';
end loop;
end;
$$;

how to pass string values as parameter in PLSQL procedure

Below is an example query that I would like my procedure to generate
select *
from Registration
where Loc_ID = 6
AND CROP_ID = 163
AND REG_NAME = 'Apiro MX';
REG_NAME is varchar2()
I have created one procedure, where I want to execute one query like below
query := 'select REG_ID from Registration where loc_id = ' ||
countryid || ' AND Crop_id = ' || cropid ||
' AND Reg_name = '|| ''' || productid || ''' || ';
I am getting error in REG_NAME part, where it is taking productid as " || productid ||"
can you please help me with the exact query for that.
You don't need to use dynamic sql:
CREATE PROCEDURE get_registration (
i_countryid IN REGISTRATION.LOC_ID%TYPE,
i_crop_id IN REGISTRATION.CROP_ID%TYPE,
i_reg_name IN REGISTRATION.REG_NAME%TYPE,
o_cursor OUT SYS_REFCURSOR
)
AS
BEGIN
OPEN o_cursor FOR
SELECT *
FROM Registration
WHERE Loc_ID = i_countryid
AND CROP_ID = i_crop_id
AND REG_NAME = i_reg_name;
END;
/
If you do need dynamic SQL (however, you can almost always do it without):
CREATE PROCEDURE get_registration (
i_countryid IN REGISTRATION.LOC_ID%TYPE,
i_crop_id IN REGISTRATION.CROP_ID%TYPE,
i_reg_name IN REGISTRATION.REG_NAME%TYPE,
o_cursor OUT SYS_REFCURSOR
)
AS
BEGIN
OPEN o_cursor
FOR 'SELECT *
FROM Registration
WHERE Loc_ID = :i
AND CROP_ID = :j
AND REG_NAME = :k'
USING i_countryid, i_crop_id, i_reg_name;
END;
/
In PLSQL you can escape quote by adding another one, therefore,
you should put double quotes around productid.
Try something like:
query := 'select REG_ID from Registration where loc_id = ' ||
countryid || ' AND Crop_id = ' || cropid ||
' AND Reg_name = '|| '''' || productid || '''' || ';
Or don't use dynamic SQL and try creating stored procedure
(check the link for the instruction).
http://plsql-tutorial.com/plsql-procedures.htm

Count the number of rows in a schema based on a where condition

I have engine_id column in various tables in the schema. So I want to count the number of rows based on engine_id column in the whole schema where this column exists.
Select count(*)
from table_name
where table_name.engine_id = 8;
Without user-defined PL/SQL, using only built-in Oracle functionality...
Oracle 11g (and maybe even lower) query:
select TC.table_name, X.*
from user_tab_columns TC
cross join xmltable(
'/ROWSET/ROW/CNT'
passing
dbms_xmlgen.getXMLType('
select count(1) as cnt
from '||TC.table_name||'
where &columnName = &columnValueAsLiteral
')
columns
cnt integer
) X
where TC.column_name = '&columnName'
;
Oracle 12c+ query:
select TC.table_name, X.*
from user_tab_columns TC
cross apply xmltable(
'/ROWSET/ROW/CNT'
passing
dbms_xmlgen.getXMLType('
select count(1) as cnt
from '||TC.table_name||'
where &columnName = &columnValueAsLiteral
')
columns
cnt integer
) X
where TC.column_name = '&columnName'
;
In your case supply
&columnName as ENGINE_ID,
&columnValueAsLiteral as 8.
Note: It could also be possible with the with-PLSQL clause of 12c's but I somehow can't make it work, hence I'm not posting that solution here.
These are the steps which you should be following
Get all the tables which have the column.
select table_name from all_tab_columns
where column_name = 'ENGINE_ID';
Create the above as a cursor and run a loop on it
for records in above_cursor
loop
execute immediate 'select count(*) from ' || records.table_name || 'where engine_id = 8' into some_temp_number_var;
some_total_number_var := some_temp_number_var + some_total_number_var;
end loop;
You will need PL code that would query the data dictionary view USER_TABLES to find out which tables have the column you seek and then build a dynamic SQL query as per your filter requirements.
Your function would be something like this:
create or replace function find_count (p_column_name varchar2,
p_column_value number)
return number is
v_sql clob; HERE
v_count number;
begin
for i in (select table_name
from user_tab_cols
where column_name = upper(v_column_name)) loop
v_sql :=
v_sql
|| 'select count(*) as cnt from '
|| i.table_name
|| ' where '
|| p_column_name
|| ' = '
|| p_column_value;
v_sql := v_sql || chr (10) || 'union all ';
end loop;
v_sql := substr (v_sql, 1, length (v_sql) - 11);
v_sql := 'select sum(cnt) from (' || v_sql || ')';
execute immediate v_sql into v_count;
return v_count;
end find_count;
/
You can trigger this function using a query like this:
select find_count('ENTITY_ID', 1012) as engine_count from dual;

How can i build a dynamic order by in oracle sql

NOTE:p_sort_field & p_order_by are NVARCHAR2 parameters to the stored procedure
SELECT ... FROM table
ORDER BY CASE WHEN p_sort_field = 'name' THEN table.Name END ASC
This works...
ORDER BY CASE WHEN p_sort_field = 'name' THEN table.Name ASC END
This don't work..
So I need to do soemthing like this
SELECT ... FROM table
ORDER BY CASE WHEN p_sort_field = 'name' THEN table.Name END
CASE WHEN p_order_by = 'asc' THEN
ASC
ELSE
DESC
END
And this don't work,.. anyone know how I can get this to work?
I need to append ASC or DESC to the ORDER BY dynamically.
Thanks in Advance.
EDIT
I took your advice, it certainly should work. But with the fully modified stored procedure posted below, my client is getting the below error at: using(OracleDataReader reader = cmd.ExecuteReader() { .... }
ORA-00604: error occurred at recursive SQL level 1,
ORA-01003: no statement parsed
As you can see I am attempting to do paging and sorting. The paging works perfectly and always has, but with this post, I'm trying to integrate sorting (by multiple fields)
Any help solving the above error is much appreciated!
PROCEDURE sp_se_paged_list
(
p_sort_field IN VARCHAR2,
p_order_by IN VARCHAR2,
p_page_index IN NUMBER,
p_page_size IN NUMBER,
p_total_count OUT NUMBER,
list_cursor OUT SYS_REFCURSOR
)
IS
v_select_stmt VARCHAR2(4000);
BEGIN
v_select_stmt := 'SELECT * FROM (';
v_select_stmt := v_select_stmt || 'SELECT p.*, RowNum rnum FROM(';
v_select_stmt := v_select_stmt || 'SELECT * FROM se s ORDER BY s.';
v_select_stmt:= v_select_stmt || p_sort_field;
v_select_stmt:= v_select_stmt ||' '|| p_order_by;
v_select_stmt:= v_select_stmt || ') p';
v_select_stmt:= v_select_stmt || 'WHERE RowNum <= p_page_index + p_page_size)';
v_select_stmt:= v_select_stmt || 'WHERE rnum >= p_page_index + 1';
OPEN list_cursor FOR v_select_stmt;
SELECT COUNT(*) into p_total_count from se;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('ERROR'||SQLERRM);
END sp_se_paged_list;
--I think your procedure should look like this as shown below
--Is your p_sort_field is fixed ,then go for your solution of case
--,but then what is the use of p_sort_fielf as input parameter.
CREATE OR REPLACE PROCEDURE temp
(p_sort_field IN VARCHAR2
,p_sort_order IN VARCHAR2
,p_ref_cur OUT SYS_REFCURSOR
)
IS
v_select_stmt VARCHAR2(2000);
BEGIN
v_select_stmt:='SELECT * FROM table ORDER BY table.';
v_select_stmt := v_select_stmt||p_sort_field ;
v_select_stmt := v_select_stmt||' '||p_order_by ;
OPEN p_ref_cur FOR v_select_stmt ;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('ERROR'||SQLERRM);
END temp;

Execute Immediate to UPDATE via for loop

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