Oracle pipelined function cannot access remote table (ORA-12840) when used in a union - oracle

I have created a pipelined function which returns a table. I use this function like a dynamic view in another function, in a with clause, to mark certain records. I then use the results from this query in an aggregate query, based on various criteria. What I want to do is union all these aggregations together (as they all use the same source data, but show aggregations at different heirarchical levels).
When I produce the data for individual levels, it works fine. However, when I try to combine them, I get an ORA-12840 error: cannot access a remote table after parallel/insert direct load txn.
(I should note that my function and queries are looking at tables on a remote server, via a DB link).
Any ideas what's going on here?
Here's an idea of the code:
function getMatches(criteria in varchar2) return myTableType pipelined;
...where this function basically executes some dynamic SQL, which references remote tables, as a reference cursor and spits out the results.
Then the factored queries go something like:
with marked as (
select id from table(getMatches('OK'))
),
fullStats as (
select mainTable.id,
avg(nvl2(marked.id, 1, 0)) isMarked,
sum(mainTable.val) total
from mainTable
left join marked
on marked.id = mainTable.id
group by mainTable.id
)
The reason for the first factor is speed -- if I inline it, in the join, the query goes really slowly -- but either way, it doesn't alter the status of whatever's causing the exception.
Then, say for a complete overview, I would do:
select sum(total) grandTotal
from fullStats
...or for an overview by isMarked:
select sum(total) grandTotal
from fullStats
where isMarked = 1
These work fine individually (my pseudocode maybe wrong or overly simplistic, but you get the idea), but as soon as I union all them together, I get the ORA-12840 error :(
EDIT By request, here is an obfuscated version of my function:
function getMatches(
search in varchar2)
return idTable pipelined
as
idRegex varchar2(20) := '(05|10|20|32)\d{3}';
searchSQL varchar2(32767);
type rc is ref cursor;
cCluster rc;
rCluster idTrinity;
BAD_CLUSTER exception;
begin
if regexp_like(search, '^L\d{3}$') then
searchSQL := 'select distinct null id1, id2_link id2, id3_link id3 from anotherSchema.linkTable#my.remote.link where id2 = ''' || search || '''';
elsif regexp_like(search, '^' || idRegex || '(,' || idRegex || || ')*$') then
searchSQL := 'select distinct null id1, id2, id3 from anotherSchema.idTable#my.remote.link where id2 in (' || regexp_replace(search, '(\d{5})', '''\1''') || ')';
else
raise BAD_CLUSTER;
end if;
open cCluster for searchSQL;
loop
fetch cCluster into rCluster;
exit when cCluster%NOTFOUND;
pipe row(rCluster);
end loop;
close cCluster;
return;
exception
when BAD_CLUSTER then
raise_application_error(-20000, 'Invalid Cluster Search');
return;
when others then
raise_application_error(-20999, 'API' || sqlcode || chr(10) || sqlerrm);
return;
end getMatches;
It's very simple, designed for an API with limited access to the database, in terms of sophistication (hence passing a comma delimited string as a possible valid argument): If you supply a grouping code, it returns linked IDs (it's a composite, 3-field key); however, if you supply a custom list of codes, it just returns those instead.
I'm on Oracle 10gR2; not sure which version exactly, but I can look it up when I'm back in the office :P

To be honest no idea where the issue came from but the simplest way to solve it - create a temporary table and populate it by values from your pipelined function and use the table inside WITH clause. Surely the temp table should be created but I'm pretty sure you get serious performance shift because dynamic sampling isn't applied to pipelined functions without tricks.
p.s. the issue could be fixed by with marked as ( select /*+ INLINE / id from table(getMatches('OK'))) but surely it isn't the stuff you're looking for so my suggestion is confirmed WITH does something like 'insert /+ APPEND*/' inside it'.

Related

getting error of character string buffer too small even after changing to varchar(32000)

getting error QRA-06502 of character string buffer too small even after changing to varchar(32000)
create or replace function product_purchase(CUSTOMER_COD VARCHAR2) return varchar is
CODE VARCHAR2(32000);
BEGIN
CODE := ' ';
FOR PRODUCTS_INFO IN (SELECT CUSTOMER_CODE,PRODUCT_NAME
FROM ORDER_DETAIL JOIN ORDERS
ON ORDER_DETAIL.ORDER_ID = ORDERS.ORDER_ID)
LOOP
CODE := CODE || PRODUCTS_INFO.CUSTOMER_CODE||PRODUCTS_INFO.PRODUCT_NAME ||',';
END LOOP;
RETURN CODE;
END product_purchase;
/
ERROR MAINLY OCCURS WHILE SELECTING DATA AND PUTTING IT IN THE FUNCTION
SELECT CUSTOMER_CODE ,product_purchase(CUSTOMER_CODE),COUNT(PRODUCT_NAME)
FROM ORDER_DETAIL JOIN ORDERS
ON ORDER_DETAIL.ORDER_ID = ORDERS.ORDER_ID
GROUP BY CUSTOMER_CODE,product_purchase(CUSTOMER_CODE)
HAVING COUNT(PRODUCT_NAME)=3;
Even after changing to varchar2(32000)? Well, that doesn't guarantee that you'll get the result (obviously).
The way I see it, it is cursor query that is wrong. If you're calling a function as
product_purchase(CUSTOMER_CODE)
it means that you're passing a parameter to it (you didn't post the whole function code so I'll presume that parameter's name is par_customer_code; note that it shouldn't be just customer_code as it is equal to column name and you'll get unexpected result once you use the parameter).
Exactly - use the parameter. Code you wrote doesn't use it. Should've been
SELECT CUSTOMER_CODE,PRODUCT_NAME
FROM ORDER_DETAIL JOIN ORDERS
ON ORDER_DETAIL.ORDER_ID = ORDERS.ORDER_ID
WHERE customer_code = par_customer_code --> this
I guess that - applying that change - you won't have problems.
However, if you do, then check whether code you wrote actually works, run the cursor query itself and see how many rows it returns. Then, restrict number of rows. How? A simple way is to use rownum:
SELECT CUSTOMER_CODE,PRODUCT_NAME
FROM ORDER_DETAIL JOIN ORDERS
ON ORDER_DETAIL.ORDER_ID = ORDERS.ORDER_ID
WHERE customer_code = par_customer_code
AND rownum <= 10; --> this
and run your code again.
If it works, and if it turns out that varchar2 isn't capable of storing that much data, use CLOB datatype instead:
DECLARE
CODE CLOB; --> this

Oracle SQL Selecting weekly music rank procedure

I'm trying to make procedure to select weekly music rank based on hits and likes.
create or replace procedure select_rank(
v_title IN music.title%TYPE,
v_release_date IN music.release_date%TYPE,
v_hit IN music.hit%TYPE
) is
v_cnt number := 0;
BEGIN
select music.title, music.release_date, count(melon_user.user_idx) as likes, music.hit
into v_rank, v_title, v_cnt, v_release_date, v_hit
from melon_user, user_like_music, music
where melon_user.user_idx = user_like_music.user_idx
and user_like_music.music_idx = music.music_idx
group by music.title, music.hit, music.release_date
order by count(melon_user.user_idx) desc, music.hit desc;
DBMS_OUTPUT.PUT_LINE('v_title: ' || v_title);
DBMS_OUTPUT.PUT_LINE('v_cnt: ' || v_cnt);
DBMS_OUTPUT.PUT_LINE('v_release_date: ' || v_release_date);
DBMS_OUTPUT.PUT_LINE('v_hit: ' || v_hit);
END select_rank;
/
But I keep getting error says
PLS-00403: expression 'V_RELEASE_DATE' cannot be used as an
INTO-target of a SELECT/FETCH statement
PLS-00403: expression 'V_RELEASE_DATE' cannot be used as an
INTO-target of a SELECT/FETCH statement
PLS-00403: expression 'V_RELEASE_DATE' cannot be used as an
INTO-target of a SELECT/FETCH statement
how do I fix this?
In the procedure's signature you have:
v_release_date IN music.release_date%TYPE,
v_release_date is an IN parameter and will be read only. If you want to assign values to it wthin the procedure and return them then make it an OUT (or IN OUT) parameter.
However, you have also:
Not declared the v_rank variable.
Your query appears likely to return multiple rows - SELECT .. INTO .. will only work for queries that return a single row. If this is the case then you either want to use BULK COLLECT INTO or return a cursor.

Ref Cursor Exceptions

I have a couple of questions arounbd ref_cursors. Below is a ref_cursor that returns a a single row to a Unix calling script based on what is passed in and although the select looks a little untidy, it works as expected.
My first question is that in the select I join to a lookup table to retrieve a single lookup value 'trigram' and on testing found that this join will occasionally fail as no value exists. I have tried to capture this with no_data_found and when others exception but this does not appear to be working.
Ideally if the join fails I would still like to return the values to the ref_cursor but add something like 'No Trigram' into the trigram field - primarily I want to capture exception.
My second question is more general about ref_cursors - While initially I have created this in its own procedure, it is likely to get called by the main processing procedure a number of times, one of the conditions requires a seperate select but the procedure would only ever return one ref_cur when called, can the procdure ref_cur out be associated with 2 queries.
CREATE OR REPLACE PROCEDURE OPC_OP.SiteZone_status
(in_site_id IN AW_ACTIVE_ALARMS.site_id%TYPE
,in_zone_id IN AW_ACTIVE_ALARMS.zone_id%TYPE
,in_mod IN AW_ACTIVE_ALARMS.module%TYPE
,p_ResultSet OUT TYPES.cursorType
)
AS
BEGIN
OPEN p_ResultSet FOR
SELECT a.site_id,'~',a.zone_id,'~',b.trigram,'~',a.module,'~',a.message_txt,'~',a.time_stamp
FROM AW_ACTIVE_ALARMS a, AW_TRIGRAM_LOCATION b
WHERE a.site_id = b.site_id
AND a.zone_id = b.zone_id
AND a.site_id = in_site_id
AND a.zone_id = in_zone_id
AND a.module LIKE substr(in_mod,1,3)||'%'
AND weight = (select max(weight) from AW_ACTIVE_ALARMS c
WHERE c.site_id = in_site_id
AND c.zone_id = in_zone_id
AND c.module LIKE substr(in_mod,1,3)||'%');
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.PUT_LINE('No Data Found');
END SiteZone_status;
I have modified my code to adopt answers provided and this now works as expected as a standalone procedure within my package, which when called via a UNIX script using:
v_process_alarm=$(sqlplus -s user/pass <
set colsep ','
set linesize 500
set pages 0 feedback off;
set serveroutput on;
VARIABLE resultSet REFCURSOR
EXEC alarm_pkg.rtn_active_alarm($site,$zone,$module, :resultSet);
PRINT :resultSet
EOF
)
However the procedure returning the ref cursor is to be called from the main processing procedure as I only want to return values if certain criteria are met. I have add an out refcurosr to my main procedure and set a variable to match, I then call my ref cursor procedure from here but this fails to compile
with the message 'Wrong number or types of argument in call'
My question is what is the correct way to call a procedure that has out refcursor from within a procedure and then return these values from there back to the calling script.
Oracle doesn't know whether a query will return rows until you fetch from the cursor. And it is not an error for a query to return 0 rows. So you will never get a no_data_found exception from opening a cursor. You'll only get that if you do something like a select into a local variable in which case a query that returns either 0 or more than 1 row is an error.
It sounds like you want to do an outer join to the AW_TRIGRAM_LOCATION table rather than a current inner join. This will return data from the other tables even if there is no matching row in aw_trigram_location. That would look something like this (I have no idea why every other column is a hard-coded tilde character, that seems exceptionally odd)
SELECT a.site_id,'~',
a.zone_id,'~',
nvl(b.trigram, 'No Trigram Found'),'~',
a.module,'~',
a.message_txt,'~',
a.time_stamp
FROM AW_ACTIVE_ALARMS a
LEFT OUTER JOIN AW_TRIGRAM_LOCATION b
ON( a.site_id = b.site_id AND
a.zone_id = b.zone_id )
WHERE a.site_id = in_site_id
AND a.zone_id = in_zone_id
AND a.module LIKE substr(in_mod,1,3)||'%'
AND weight = (select max(weight)
from AW_ACTIVE_ALARMS c
WHERE c.site_id = in_site_id
AND c.zone_id = in_zone_id
AND c.module LIKE substr(in_mod,1,3)||'%');
I'm not quite sure that I understand your last question. You can certainly put logic in your procedure to run a different query depending on an input parameter. Something like
IF( <<some condition>> )
THEN
OPEN p_ResultSet FOR <<query 1>>
ELSE
OPEN p_ResultSet FOR <<query 2>>
END IF;
Whether it makes sense to do this rather than adding additional predicates or creating separate procedures is a question you'd have to answer.
You can use a left outer join to your look-up table, which is clearer if you use ANSI join syntax rather than Oracle's old syntax. If there is no record in AW_TRIGRAM_LOCATION then b.trigram will be null, and you can then use NVL to assign a dummy value:
OPEN p_ResultSet FOR
SELECT a.site_id,'~',a.zone_id,'~',NVL(b.trigram, 'No Trigram'),'~',
a.module,'~',a.message_txt,'~',a.time_stamp
FROM AW_ACTIVE_ALARMS a
LEFT JOIN AW_TRIGRAM_LOCATION b
ON b.site_id = a.site_id
AND b.zone_id = a.zone_id
WHERE a.zone_id = in_zone_id
AND a.module LIKE substr(in_mod,1,3)||'%'
AND weight = (select max(weight) from AW_ACTIVE_ALARMS c
WHERE c.site_id = in_site_id
AND c.zone_id = in_zone_id
AND c.module LIKE substr(in_mod,1,3)||'%');
You won't get NO_DATA_FOUND opening a cursor, only when you fetch from it (depending on what is actually consuming this). It's a bad idea to catch WHEN OTHERS anyway - you would want to catch WHEN NO_DATA_FOUND, though it wouldn't help here. And using dbms_output to report an error relies on the client enabling its display, which you can't generally assume.

Oracle Cursor Scripts errors

I have to construct a block to pull information from two tables using a cursor. As a further challenge, I have to identify the first item on the pull. I tried to use an IF statement to work through this. It errors out in several areas and I have no idea what I am doing wrong. Not asking for the answer per say, just enough of a push to get me moving again. Thanks. Here is the code I've put together so far:
DECLARE
CURSOR cur_pled IS
SELECT dd_pledge.idpledge, dd_pledge.pledgeamt, dd_pledge.paymonths, dd_payment.paydate, dd_payment.payamt
FROM dd_pledge, dd_payment
WHERE dd_payment.idpledge = dd_pledge.idpledge AND
dd_pledge.idpledge = 104
ORDER BY dd_pledge.idpledge, dd_payment.paydate;
TYPE type_pled IS RECORD
(pledID dd_pledge.idpledge%TYPE,
pledAmt dd_pledge.pledgeamt%TYPE,
payMonths dd_pledge.paymonths%TYPE,
payDate dd_payment.paydate%TYPE,
payAmt dd_payment.payamt%TYPE);
rec_pled type_pled;
lv_id_num dd_pledge.idpledge%TYPE := 0;
BEGIN
OPEN cur_pled;
LOOP
FETCH cur_pled INTO rec_pled;
EXIT WHEN cur_pled%NOTFOUND;
IF rec_pled.type <> lv_id_num THEN
DBMS_OUTPUT.PUT_LINE('First Payment');
ELSE DBMS_OUTPUT.PUT_LINE('Other Payment');
END IF;
END LOOP;
CLOSE cur_pled;
DBMS_OUTPUT.PUT_LINE(pledID || ' ' || dd_pledge.pledgeamt || ' ' ||
dd_pledge,payMonths || ' ' || dd_payment.payDate || ' ' ||
dd_payment.payAmt);
END;
There are loads of errors in your code. If you had formatted it correctly, you would have spotted some of them yourself!
Things that leapt out at me:
You're referring to dd_pledge in the final dbms_output.put_line, but dd_pledge isn't a variable. I think you meant to use rec_pled instead.
You refer to pledID in your final dbms_output.put_line statement - but this is a field defined in the record type, NOT a defined variable. I think you probably meant to use rec_pled.pledid
You're selecting the results of the cursor into rec_pled.type - however, "type" is not a field declared in the type_pled's definition! Did you mean rec_pled.idpledge instead?
You have dd_pledge,payMonths in your final dbms_output.put_line statement - the comma should be a full stop: rec_pled.payMonths
You're outputting the results after you've closed the results. Because this is just a record variable, you're only going to be outputting the results from the last row in the query.
Why aren't you doing a cursor for loop? That takes care of the exiting and declaring a record for you.
Anyway, I think you can achieve your results by using an analytic function in your query, rather than needing to use PL/SQL to do the work:
SELECT plg.idpledge,
plg.pledgeamt,
plg.paymonths,
pay.paydate,
pay.payamt,
case when row_number() over (partition by plg.idpledge, pay.paydate) = 1 then 'First Payment'
else 'Other Payment'
end type_of_payment
FROM dd_pledge plg
inner join dd_payment pay on (pay.idpledge = plg.idpledge)
--WHERE plg.idpledge = 104 -- add back in if you really need to do it for a single id
ORDER BY plg.idpledge, pay.paydate;

PL/SQL issue concerning Frequent Itemset

I'm trying to build a PL/SQL application to mine frequent item sets out of a set of given data and I've run into a bit of a snag. My PL/SQL skills aren't as good as I'd like them to be, so perhaps one of you can help me understand this a bit better.
So to begin, I'm using the Oracle data mining procedure: *DBMS_FREQUENT_ITEMSET.FI_TRANSACTIONAL*
While reading the documentation, I came across the following example which I have manipulated to query over my data set:
CREATE OR REPLACE TYPE FI_VARCHAR_NT AS TABLE OF NUMBER;
/
CREATE TYPE fi_res AS OBJECT (
itemset FI_VARCHAR_NT,
support NUMBER,
length NUMBER,
total_tranx NUMBER
);
/
CREATE TYPE fi_coll AS TABLE OF fi_res;
/
create or replace
PROCEDURE freq_itemset_test is
cursor freqC is
SELECT itemset
FROM table(
CAST(DBMS_FREQUENT_ITEMSET.FI_TRANSACTIONAL(CURSOR(SELECT sale.customerid, sale.productid FROM Sale INNER JOIN Customer ON customer.customerid = sale.customerid WHERE customer.region = 'Canada' )
,0,2, 2, NULL, NULL) AS fi_coll));
coll_nt FI_VARCHAR_NT;
num_rows int;
num_itms int;
BEGIN
num_rows := 0;
num_itms := 0;
OPEN freqC;
LOOP
FETCH freqC INTO coll_nt;
EXIT WHEN freqC%NOTFOUND;
num_rows := num_rows + 1;
num_itms := num_itms + coll_nt.count;
END LOOP;
DBMS_OUTPUT.PUT_LINE('Rows: ' || num_rows || ' Columns: ' || num_itms);
CLOSE freqC;
END;
My reasoning for using the Oracle FI_TRANSACTIONAL over straight SQL is that I will need to repeat this analysis for multiple dynamic values of K, so why reinvent the wheel? Ultimately, my goal is to reference each individual item sets returned by the procedure and return the set with the highest support based on some query logic. I will be incorporating this block of PL/SQL into another that basically changes the literal in the query from 'Canada' to multiple other regions based on the content of the data.
My question is: How can I actually get a programmatic reference on the data returned by the cursor (freqC)? Obviously I do not need to count the rows and columns, but that was part of the example. I'd like to print out the item sets with DBMS print line after I've found the most occurring item set. When I view this in a debugger, I see that each fetch of the cursor actually returns an item set (in this case, k=2, so two items). But how do I actually touch them programmatically? I'd like to grab the sets themselves as well as fi_res.support.
As always, thanks to everyone for sharing their brilliance!
You are fetching your data into a nested table. So to see the data in there, you would need to loop over the nested table:
FOR i IN coll_nt.FIRST .. coll_nt.LAST
LOOP
dbms_output.put_line(i||': '||coll_nt(i));
END LOOP;
For much more information on nested tables and other types of collections, see the presentation at:
http://www.toadworld.com/platforms/oracle/w/wiki/8253.everything-you-need-to-know-about-collections-but-were-afraid-to-ask.aspx

Resources