Oracle PL/SQL - parameterizing SAMPLE clause in SELECT statement - oracle

I have a Oracle related question. I would like to select a random sample out of a view or table in such a way that the SAMPLE clause is parameterized.
Given the following table.
CREATE TABLE FOO AS
(SELECT LEVEL AS ID
FROM DUAL
CONNECT BY LEVEL < 101
);
The following construct works, using a literal parameter in the SAMPLE clause.
SELECT ID FROM FOO SAMPLE (15); -- this will get a 15% sample
However,
DECLARE
N NUMBER := 50;
BEGIN
FOR r IN
( SELECT ID FROM FOO SAMPLE (N) -- <<< this won't work
)
LOOP
DBMS_OUTPUT.PUT_LINE( r.ID );
END LOOP;
END;
This block blows up when we put a parameter in the SAMPLE clause. It compiles and works if we put it a literal.
But if it is a variable, I get the following:
ORA-06550: line 5, column 33:
PL/SQL: ORA-00933: SQL command not properly ended
Any ideas? I'm racking by brains where the syntax gets broken.

The syntax does not allow a variable there.
One workaround would be to construct the SELECT statement dynamically. For example:
declare
l_rc sys_refcursor;
n number := 5;
begin
-- replace "mtl_system_items" with your table...
open l_rc FOR 'select count(*) from mtl_system_items sample (' || n || ')';
-- replace call to RETURN_RESULT with whatever processing you want
DBMS_SQL.RETURN_RESULT(l_rc);
end;

Related

Oracle PL/SQL SELECT INTO clause thinks it needs another INTO

I have a simple test function where I'm passing in a specific ID (the primary key of the table I'm selecting from), and computing a simple function on it and the parameters.
The skeleton code and test:
create or replace function test(id varchar2, area float) return float is
theRow forest%ROWTYPE;
begin
select * into theRow from forest where Forest_No = id;
return area / theRow.Area;
end;
begin
select test('1', 16000) from dual;
end;
The output:
[2019-10-14 21:19:10] [65000][6550] ORA-06550: line 2, column 5:
[2019-10-14 21:19:10] PLS-00428: an INTO clause is expected in this SELECT statement
I am at a loss for what to do here, as far as I can tell the documentation and examples use the same order and syntax. I have tried moving the into clause to the end as in Postgresql, but that did not work.
What have I missed here?
Issue is in calling statement.
Whenever select statement is used in plsql block it must have into clause to assign return value to variable.
You should remove begin and end from your calling code:
--begin -- remove this
select test('1', 16000) from dual;
--end; -- remove this
Or if you want to use it in plsql block then add into clause:
Declare
Area_ float(precision);
begin
select test('1', 16000) into area_ from dual;
-- use area_ in your code wherever required
dbms_output.put_line('area: ' || area_);
end;
Cheers!!

DBMS_SQL.EXECUTE not giving output when the SQL have DBMS_XMLGEN.GETXML

I am generating a dynamic SQL which generates XML data, using SELECT DBMS_XMLGEN.GETXML, as the output have special characters,
which are not supported in XML. I tried and tested SQL in separately, It is working as expected.
I am not able to fetch the data, when I call it using Dynamic SQL.
I have used DBMS_SQL.DESC_TAB2 as it is treating whole SQL starting DBMS_XMLGEN.GETXML as one column.
--Snippet
Declaration
SelectCursorId NUMBER; --For Dynamic SQL binding
RowProcessed INTEGER; --For Dynamic SQL binding
MstrSeqNbr NUMBER := 1;
ColumnCount NUMBER; --For Dynamic SQL binding
RecordSqrNbr NUMBER := 0; --For Dynamic SQL binding
ColumnDescTbl DBMS_SQL.DESC_TAB2; --dbms_sql.desc_tab2
ColumnValue VARCHAR2(4000); --For Dynamic SQL binding
DymanicSQLCols VARCHAR2(4000); -- For debugging purpose, columns returned
SelectSQL VARCHAR2(6000);
BEGIN
--Snippet
SelectSQL := 'SELECT DBMS_XMLGEN.GETXML('SELECT MRQ.BatchNBR AS Batch_NUMBER,
MRQ.BatchRUNSEQNBR AS Batch_RUN_INSTANCE,
MRQ.BatchRUNDATE AS RUN_DATE,
MRQ.SPDATE AS POST_DATE,
MRQ.BatchNAME AS Batch_NAME,
MRQ.RPTNAME AS DATABASE_NAME,
MRQ.EFFDATE AS RUN_TIME,
MRQ.BatchSTARTDATE AS ELAPSED_TIME ,
CURSOR ( SELECT MRI.MSTREPORTRECSEQNBR AS RECORD_SEQUENCE_NUMBER,
MRI.RTTEXT1VC100 AS COUNTRY_NAME,
MRI.RTTEXT2VC100 AS CURRENCY_USED,
MRI.RTTEXT1VC50 AS COUNTRY_SHORT_CODE,
MRI.RTNUM1P0 AS ISO_CURRENCY_CODE
FROM MasterReporting MRI
WHERE BatchNbr = MR.BatchNbr
AND BatchRUNSEQNBR = MR.BatchRUNINSTANCE
ORDER BY MSTREPORTRECSEQNBR )Record
FROM BatchRUNHIST MR , MastRptSeqDtl MRQ
WHERE MR.BatchNbr = 100
AND MR.BatchRUNINSTANCE IN( 67)
AND MRQ.BatchRUNSEQNBR = MR.BatchRUNINSTANCE
AND MR.BatchRunStatCD =''COMPL''
')
FROM DUAL ';
SelectCursorId := DBMS_SQL.OPEN_CURSOR; --Pass
DBMS_SQL.PARSE ( SelectCursorId, SelectSQL, DBMS_SQL.NATIVE); --Pass
DBMS_SQL.DESCRIBE_COLUMNS2( SelectCursorId, ColumnCount, ColumnDescTbl); --Pass
** However ColumnDescTbl(1).col_name is only giving following, not sure if this is the issue
DBMS_XMLGEN.GETXML('SELECT MRQ.BatchNBRASBatch_NUMBER, MRQ.BatchRUNSEQNBRASBatch_RUN_INSTANCE, MRQ.BatchRUNDATEASRUN_DATE, MRQ.SPDATEASPOST_DATE, MRQ.BatchNAMEASBatch_NAME, MRQ.RPTNAMEASDATABAS
Next step also passes
For k in 1..ColumnCount LOOP
DBMS_SQL.DEFINE_COLUMN(SelectCursorId, k, ColumnValue, 4000); --Pass
END LOOP;
RowProcessed := DBMS_SQL.EXECUTE(SelectCursorId);
--Passes but gives 0 as output,
--Whereas, running the SQL separately gives you one row of XML data.
Minimum One row of XML Data should be returned.
From the documentation for dbms_sql.execute:
The return value is only valid for INSERT, UPDATE, and DELETE statements; for other types of statements, including DDL, the return value is undefined and must be ignored.
As your statement is a select the RowProcessed return value has no meaning; it isn't significant that you're seeing zero there.
You need to do a fetch_rows step after that, or change it to execute_and_fetch. Either way you then need to process the fetched data, of course.
(The col_name looks like a non-issue, it's just an automatically generated name/alias. If you change your dynamic statement to do ...) AS my_col_alias FROM DUAL then the col_name will be reported as MY_COL_ALIAS.)

ORA-22905 - While using DBMS_SQL.Number_Table for Table Operator

All,
Here is a simplified version of what I am attempting to accomplish. Instead of declaring my own type, I used the Number_Table type from the dbms_sql package.
First I created a simple test table:
CREATE TABLE collect_test(id NUMBER(38), other_info VARCHAR2(5));
Then, populated the table with a small amount of data:
INSERT INTO collect_test
SELECT rownum, chr(rownum+60)
FROM dual
CONNECT BY rownum <= 10;
Finally, I attempt to use PL/SQL to select some rows into a collection then use that collection to delete rows from the table:
DECLARE
l_tIDList DBMS_SQL.Number_Table;
BEGIN
SELECT ct.id
BULK COLLECT INTO l_tIDList
FROM collect_test ct
WHERE mod(ct.id, 2) = 0;
DELETE FROM (SELECT ct.id
FROM collect_test ct
INNER JOIN table(l_tIDList) ids ON ct.id = ids.column_value);
ROLLBACK;
END;
/
However, when I run this PL/SQL block I receive these errors:
ORA-06550: line 11, column 33:
PLS-00382: expression is of wrong type
ORA-06550: line 11, column 27:
PL/SQL: ORA-22905: cannot access rows from a non-nested table item
ORA-06550: line 9, column 3: PL/SQL: SQL Statement ignored
In all the other questions/articles I have found it seems like the coder was either trying to use a local type or forgetting to BULK COLLECT. I appreciate any suggestions you may have.
N.B.: I realize that I can do this specific functionality using a single DELETE statement. My actual scenario is more complicated and can't be done with a single statement.
Although it is not clear what you are trying to accomplish, there are two things which are wrong with your pl/sql block.
You can use the TABLE() function on the variables of collection
types defined in the schema and not on those defined locally
The way you are running your DML ( delete ) statement is wrong, it throws the error `
ORA-22841: DML on PL/SQL collections not supported.
So, create a collection TYPE in your schema and declare a collection of that type in your PL/SQL block and for your delete use an IN condition
CREATE OR REPLACE TYPE idlisttable as TABLE OF NUMBER;
DECLARE l_tidlist idlisttable;
BEGIN
SELECT ct.id BULK collect
INTO l_tidlist
FROM collect_test ct
WHERE MOD(ct.id, 2) = 0;
DELETE
FROM collect_test ct
WHERE ct.id IN (
SELECT ids.column_value
FROM TABLE (l_tidlist) ids
);
ROLLBACK;
END;
/
`
I have tried this using a cursor and it worked for me. Please try as below.
DECLARE
l_tIDList DBMS_SQL.Number_Table;
CURSOR c
IS
SELECT ct.id FROM collect_test ct WHERE mod(ct.id, 2) = 0;
BEGIN
OPEN c;
LOOP
FETCH c bulk collect INTO l_tIDList;
forall i IN 1 .. l_tIDList.count
DELETE FROM collect_test t WHERE t.id = l_tIDList(i);
EXIT
WHEN c%notfound;
END LOOP;
COMMIT;
CLOSE c;
END ;
/

Retrieve a select count(*) in Oracle inside a cursor

I need to retrieve the number of rows in a SELECT COUNT(*) statement that is inside a cursor (in Oracle).
The following code should explain it clearly:
PROCEDURE Save(CF_CURSOR OUT "VPA"."CF_#Runtime".CF_CURSOR_TYPE) AS
V_CF_CURSOR "VPA"."CF_#Runtime".CF_CURSOR_TYPE;
CF_ROWCOUNT NUMBER;
BEGIN
OPEN V_CF_CURSOR FOR
SELECT COUNT(*) INTO CF_ROWCOUNT FROM (
SELECT * FROM "VPA"."Employee" -- returns 1 row
) WHERE ROWNUM <= 1;
IF(CF_ROWCOUNT = 0) THEN
-- DO SOMETHING BUT NEVER GOES HERE
END IF;
COMMIT;
CF_CURSOR := V_CF_CURSOR;
END;
Here, the value of CF_ROWCOUNT is never set. If I remove the cursor, everything works as expected. I have tried to use SQL%ROWCOUNT, but it does not work either.
And, I cannot remove the cursor...
Thanks in advance!
Have you tried opening the cursor - which does a COUNT(*), then fetching that into the CF_ROWCOUNT variable instead of doing it as an INTO within the ref-cursor statement.
For example:
OPEN V_CF_CURSOR FOR SELECT COUNT(*) FROM "VPA"."Employee"; -- returns 1 row
FETCH V_CF_CURSOR INTO CF_ROWCOUNT;

Output results of Oracle stored proc from SQL Developer

I'm trying to call an Oracle stored proc using SQL Developer. The proc outputs results using a sys_refcursor. I right click in the proc window which brings up the Run PL/SQL window. When I choose the proc I want it creates all the input params etc for me. Below is the code I'm using to try and loop through the sys_refcursor and output the results, but I'm getting an error on the 'v_rec v_Return%rowtype;' line :
ORA-06550: line 6 column 9:
PLS-00320: the declaration of the type of this expression is incomplete or malformed.
ORA-06550: line 6 column 9:
PL/SQL: Item ignored
vendor code 6550
I found the looping code on a couple of other websites and it seems to be the way to do it but it's not working for me no matter what I try. Another question - on the DBMS_OUTPUT.PUT_LINE('name = ' || v_rec.ADM) am I referencing the v_rec correctly i.e. is v_rec."column_name" the correct way??
I'm not that used to Oracle and have never used SQL plus. Any suggestions appreciated.
DECLARE
P_CAE_SEC_ID_N NUMBER;
P_PAGE_INDEX NUMBER;
P_PAGE_SIZE NUMBER;
v_Return sys_refcursor;
v_rec v_Return%rowtype;
BEGIN
P_CAE_SEC_ID_N := NULL;
P_PAGE_INDEX := 0;
P_PAGE_SIZE := 25;
CAE_FOF_SECURITY_PKG.GET_LIST_FOF_SECURITY(
P_CAE_SEC_ID_N => P_CAE_SEC_ID_N,
P_PAGE_INDEX => P_PAGE_INDEX,
P_PAGE_SIZE => P_PAGE_SIZE,
P_FOF_SEC_REFCUR => v_Return
);
-- Modify the code to output the variable
-- DBMS_OUTPUT.PUT_LINE('P_FOF_SEC_REFCUR = ');
loop
fetch v_Return into v_rec;
exit when v_Return%notfound;
DBMS_OUTPUT.PUT_LINE('name = ' || v_rec.ADM);
end loop;
END;
Your problem is here:
v_Return sys_refcursor;
v_rec v_Return%rowtype;
v_Return is a cursor variable and has no specific structure (list of columns), so v_Return%rowtype is not a valid record structure to declare v_rec. It is even possible for different calls to the procedure to return cursors with different structures.
You know what you are expecting the structure of the returned cursor to be (but Oracle doesn't) so you need to explicitly define the appropriate record structure e.g.
type t_row is record (empno number, ename varchar2(30));
v_rec t_row;
You need a strongly typed ref cursor to be able to define it as a %ROWTYPE.
Example here
#Tony Andrews thanks for this it gave me a better idea where I was going wrong. Still having problems though - here's a shortened version of my proc. It's a bit complex in that it's selecting all fields from a subquery and 2 other values:
open p_fof_sec_refcur for
SELECT *
FROM(
SELECT securities.*, rownum rnum, v_total_count
FROM
(
SELECT
CFS.CAE_SEC_ID,
CFS.FM_SEC_CODE,
...
FROM
CAEDBO.CAE_FOF_SECURITY CFS
INNER JOIN caedbo.CAE_DATA_SET_ELEMENT CDSE_STAT
ON (CDSE_STAT.DATA_SET_ELEMENT_ID = CFS.APPR_STATUS)
...
WHERE APPR_STATUS = NVL(p_appr_status, APPR_STATUS)
...
)securities
)
WHERE rnum between v_pgStart and v_pgEnd;
I explicitly defined the output structure as below to match the return fields from the proc but I'm still getting an error:
v_Return sys_refcursor;
type t_row is record (CAE_SEC_ID NUMBER,FM_SEC_CODE VARCHAR2(7),...rnum number, v_total_count number);
v_rec t_row;
The error I get is
ORA-06504: PL/SQL: Return types of Result Set variables or query do not match
ORA-06512: at line 45
I'm just wondering is the "rownum rnum, v_total_count" part tripping me up. I'm pretty sure I have all the other fields in the output structure correct as I copied them directly from the proc.

Resources