While Loop and If/Else in PL/SQL - sql

I am trying to write a procedure that will produce the following output
exec WinOrLose(4)
Welcome to the Win or Lose Game. Your number is 4.
You win.
You lose.
You win.
You lose.
==> You lose!
So far I have this:
CREATE or REPLACE Procedure WinOrLose (
p_choice number ) AS
v_answer number;
DECLARE
v_answer := p_choice
BEGIN
dbms_output.put_line ('Welcome to the Win or Lose Game. Your number is ' ||
v_answer);
FOR v_answer in 1..10
IF MOD(v_answer, 2) = 0 THEN -- v_answer is even
dbms_output.put_line (You lose)
END;
/
I'm unsure of where to go from there. My thought process (psuedocode) is this:
SET v_answer := 1
While Loop (outside)
MOD(v_answer,2) = 0 then dbms.output (YOU LOSE)
ELSE
dbms.output (YOU WIN)
end if;
v_answer := p_choice

CREATE or REPLACE Procedure WinOrLose (
p_choice number ) AS
BEGIN
dbms_output.put_line ('Welcome to the Win or Lose Game. Your number is ' ||
p_choice);
FOR v_counter in 1..p_choice LOOP
IF (MOD(v_counter, 2) = 0)
THEN
dbms_output.put_line ('You win');
ELSE
dbms_output.put_line ('You lose');
END IF;
END LOOP;
IF (MOD(p_choice , 2) = 0)
THEN
dbms_output.put_line ('==> You win!');
ELSE
dbms_output.put_line ('==> You lose!');
END IF;
END;
/

Related

XML Tag breaking in the middle when Chunking XMLTYPE to CLOB

We have a procedure that writes a XML File using SQL and we're asked to improve it's performance. Currently, it prints the XML line-by-line like the code below:
begin
dbms_output.put_line('<tns:jpk>');
-- header section
for i in (select xmlelement("tns:header", header_num)
,xmlelement("tns:customer", customer_name)
,xmlelement("tns:po_number", po_number) li_xml
from header_tbl) loop
dbms_output.put_line( i.li_xml.getclobval() );
dbms_output.put_line('<tns:lines>');
-- Lines section
for x in (select xmlelement("tns:line_num", line_num)
,xmlelement("tns:order", order_dtl)
,xmlelement("tns:qty", qty)
from Lines_tbl) lx loop
dbms_output.put_line( x.lx_xml.getclobval() );
dbms_output.put_line('<tns:Summary>');
-- Summary section
for y in (select xmlelement("tns:sum_num", sum_num)
,xmlelement("tns:total_amt, total_amt)
,xmlelement("tns:total_qty, total_qty)
from Summary_Tbl) lc_xml loop
dbms_output.put_line( y.lc_xml.getclobval() );
end loop;
dbms_output.put_line('<tns:Summary>');
dbms_output.put_line('</tns:lines>');
end loop;
end loop;
dbms_output.put_line('</tns:jpk>');
end;
the above sample code looks like this:
<tns:jpk>
<tns:header>1</tns:header>
<tns:customer>1000</tns:customer>
<tns:po_number>909090</tns:po_number>
<tns:lines>
<tns:line_num>1</tns:line_num>
<tns:order>Other FA Open Asset Cost Pozostale Srodki Trwale -Wartosc poczatkowa</tns:order>
<tns:qty>1</tns:qty>
<tns:Summary>
<tns:sum_num>1</tns:sum_num>
<tns:total_amt>1000</tns:total_amt>
<tns:total_qty>1</tns:total_qty>
</tns:Summary>
</tns:lines>
</tns:jpk>
I've managed to make it a bit faster by using a different approach, like the code below:
declare
xml_c xmltype;
procedure print_clob( p_clob in clob ) is
v_offset number := 1;
v_chunk_size number := 10000;
--v_chunk_size number := 32767;
begin
loop
exit when v_offset > dbms_lob.getlength(p_clob);
dbms_output.put_line( dbms_lob.substr( p_clob, v_chunk_size, v_offset ) );
v_offset := v_offset + v_chunk_size;
end loop;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line('print_clob. others ' || SQLERRM);
end print_clob;
begin
select xmlagg(xmlconcat("<tns:jpk>",
-- header section
(select xmlagg(xmlconcat(
xmlelement("tns:header", header_num)
,xmlelement("tns:customer", customer_name)
,xmlelement("tns:po_number", po_number)
,xmlelement("tns:lines",
-- lines section
,(select
xmlagg(xmlconcat(
xmlelement("tns:line_num", line_num)
, xmlelement("tns:order", order_dtl)
, xmlelement("tns:qty", qty)
, (select
xmlagg(xmlconcat(
xmlelement("tns:sum_num", sum_num)
, xmlelement("tns:total_amt", total_amt)
, xmlelement("tns:total_qty", total_qty)))
from Summary_Tbl)
))
from Lines_tbl)
)
))
from header_tbl)
))
into xml_c
from dual;
print_clob( xml_c.getclobval );
end;
The above code makes a really long XML, and cuts it up to pieces and prints it.
It works fine and faster based on the execution times.
However, whenever there's a really long piece of string, the output sometimes gets skwered like below:
<tns:jpk>
<tns:header>1</tns:header>
<tns:customer>1000</tns:customer>
<tns:po_number>909090</tns:po_number>
<tns:lines>
<tns:line_num>1</tns:line_num>
<tns:order>Other FA Open Asset Cost Pozostale
Srodki Trwale -Wartosc poczatkowa</tns:order>
<tns:qty>1</tns:qty>
<tns:Summary>
<tns:sum_num>1</tns:sum_num>
<tns:total_amt>1000</tns:total_amt>
<tns:total_qty>1</tns:total_qty>
</tns:Summary>
</tns:lines>
</tns:jpk>
Is there a way for me to find out if the tag and it's contents will exceed the limit before I print it?

PLS-00306: when calling a function from a PL/SQL block

I am getting the PLS-00306 error when I attempt to run a PL/SQL block that calls a variety of things including a function. The job of the function is to count how many cars belong to a certain model type. The function works if I call it in a SQL statement or it's own block, it just doesn't seem to work here.
This is the function:
CREATE OR REPLACE Function findtotalcarmodels(
model_name_in IN varchar2)
RETURN NUMBER
IS
counter NUMBER := 0;
CURSOR car_count_cur IS
SELECT model_name
FROM i_car
WHERE model_name = model_name_in;
Rec_car_details car_count_cur%ROWTYPE;
BEGIN
OPEN car_count_cur;
LOOP
FETCH car_count_cur INTO Rec_car_details;
EXIT WHEN car_count_cur%NOTFOUND;
counter := counter + 1;
END LOOP;
CLOSE car_count_cur;
RETURN counter;
END;
This is the Block:
SET SERVEROUTPUT ON FORMAT WRAP SIZE 12000
Declare
v_model VARCHAR2(40);
v_carcategory VARCHAR2(40);
v_totalcars NUMBER;
v_maxdate DATE:=TO_DATE(1, 'J');
Cursor carcur IS
SELECT *
FROM i_car;
CURSOR c1(v_car_registration VARCHAR2) IS
SELECT *
from i_booking a
WHERE a.registration=v_car_registration;
Begin
For car_rec in carcur
LOOP
v_maxdate:=TO_DATE(1, 'J');
for rec in c1(car_rec.registration)
loop
IF rec.date_reserved > v_maxdate
then
v_maxdate:=rec.date_reserved ;
If car_rec.Cost <=50000
THEN
v_carcategory := 'Budget Car';
End IF;
If car_rec.Cost BETWEEN 50000 AND 100000
THEN
v_carcategory := 'Standard Car';
End IF;
If car_rec.Cost >100000
THEN
v_carcategory := 'Premium Car';
End If;
end IF;
v_totalcars := findtotalcarmodels;
end loop;
DBMS_OUTPUT.PUT_LINE('Registration:'|| ' '|| car_rec.registration);
DBMS_OUTPUT.PUT_LINE('Cost:'|| ' $' || car_rec.Cost);
DBMS_OUTPUT.PUT_LINE('Model Name:'|| ' '|| car_rec.model_name);
DBMS_OUTPUT.PUT_LINE('Car Category:'|| ' '||v_carcategory);
DBMS_OUTPUT.PUT_LINE('Total number of Cars:'|| ' '||v_totalcars);
DBMS_OUTPUT.PUT_LINE('Most Recent Rental Date: '|| ' '||v_maxdate);
DBMS_OUTPUT.NEW_LINE;
END LOOP;
END;
/
Before I get slammed for the style of the PL/SQL block, just keep in mind that it is written to requirement and everything works well with the exception of the function.
If someone could point me in the right direction to call this function without error I would be very grateful.
It looks like you have missed to pass the IN parameter to the function.
Try like this,
v_totalcars := findtotalcarmodels('<model_name_in>');
Well, when i see things right, you don't fillup the parameter model_name. When you don't have an overriden function wihtout parameter you need to fill it up.
AS you can also see the PLS-00306 is telling you something about wrong number of arguments.

how can we get the value of of VARRAY from IN Parameter in procedure

I am new to PL/SQL... In Varray how can i get multiple value from IN parameter..... else Is there another ways to get the values...
I want to interate the values through VArray... if any other options then its fine..
coding:
CREATE OR REPLACE
PROCEDURE dynamic_query_build(
vr_plan_sku_id IN VARCHAR2 )
IS
type plan_sku_id_array IS VARRAY(999) OF VARCHAR2(5000);
plan_sku_id plan_sku_id_array;
total INTEGER;
vrx_plan_sku_id VARCHAR2(3000);
BEGIN
vrx_plan_sku_id:= REPLACE(vr_plan_sku_id,',',chr(39)||','||chr(39));
plan_sku_id := plan_sku_id_array(chr(39)||vrx_plan_sku_id||chr(39));
total := plan_sku_id.count;
FOR i IN 1 .. total
LOOP
dbms_output.put_line(plan_sku_id(i));
END LOOP;
EXCEPTION
WHEN OTHERS THEN
raise_application_error(-20001,'An error was encountered - '||SQLCODE||' -ERROR- '||SQLERRM);
END dynamic_query_build;
Execution:
set serveroutput on;
declare
vr_plan_sku_id varchar2(200) := '5863314,5863315';
BEGIN
dynamic_query_build(vr_plan_sku_id);
END;
/
My Output:
anonymous block completed
'5863314','5863315'
Expected output:
5863314
5863315
now it is considering as single value....
I created anonymous block with procedure dynamic_query_build. Added there code, that will split VARCHAR2 variable into varray.
I think, the key to your question is this line - plan_sku_id.EXTEND();
You can extend varray dynamically, but only till it reaches defined maximum (in your case - 999).
DECLARE
vr_plan_sku_id varchar2(200) := '5863314,5863315';
PROCEDURE dynamic_query_build(
vr_plan_sku_id IN VARCHAR2 )
IS
type plan_sku_id_array IS VARRAY(999) OF VARCHAR2(5000);
plan_sku_id plan_sku_id_array;
total INTEGER;
position PLS_INTEGER := 0;
last_position PLS_INTEGER := 1;
tmp VARCHAR2(5000);
counter PLS_INTEGER := 1;
BEGIN
plan_sku_id := plan_sku_id_array();
LOOP
position := INSTR(vr_plan_sku_id, ',', last_position);
IF position > 0 THEN
tmp := SUBSTR(vr_plan_sku_id, last_position, position - last_position);
last_position := position + 1;
ELSE
tmp := SUBSTR(vr_plan_sku_id, last_position);
END IF;
plan_sku_id.EXTEND();
plan_sku_id(counter) := tmp;
counter := counter + 1;
EXIT WHEN position = 0 OR counter > 10;
END LOOP;
total := plan_sku_id.count;
FOR i IN 1 .. total
LOOP
dbms_output.put_line(plan_sku_id(i));
END LOOP;
EXCEPTION
WHEN OTHERS THEN
raise_application_error(-20001,'An error was encountered - '||SQLCODE||' -ERROR- '||SQLERRM);
END dynamic_query_build;
BEGIN
dynamic_query_build(vr_plan_sku_id);
END;
/
Put a replace in the dbms_output statement this will eliminate the quotes from the string
....
dbms_output.put_line replace (replace (plan_sku_id(i), '''' ))',',chr(10);
.....

Oracle : how to fetch data from dynamic query?

I have a program to generate dynamic query string based on input. This query may select from any tables or joined tables in my DB, and the column names and number of columns are unknown.
Now with this query string as the only input, I want to fetch all data from the result and output them line by line, is there any way to do this ?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Thank Thinkjet for the reference. I have solved the problem, to help the others, here is the piece of code I used:
DECLARE
v_curid NUMBER;
v_desctab DBMS_SQL.DESC_TAB;
v_colcnt NUMBER;
v_name_var VARCHAR2(10000);
v_num_var NUMBER;
v_date_var DATE;
v_row_num NUMBER;
p_sql_stmt VARCHAR2(1000);
BEGIN
v_curid := DBMS_SQL.OPEN_CURSOR;
p_sql_stmt :='SELECT * FROM emp';
DBMS_SQL.PARSE(v_curid, p_sql_stmt, DBMS_SQL.NATIVE);
DBMS_SQL.DESCRIBE_COLUMNS(v_curid, v_colcnt, v_desctab);
-- Define columns:
FOR i IN 1 .. v_colcnt LOOP
IF v_desctab(i).col_type = 2 THEN
DBMS_SQL.DEFINE_COLUMN(v_curid, i, v_num_var);
ELSIF v_desctab(i).col_type = 12 THEN
DBMS_SQL.DEFINE_COLUMN(v_curid, i, v_date_var);
ELSE
DBMS_SQL.DEFINE_COLUMN(v_curid, i, v_name_var, 50);
END IF;
END LOOP;
v_row_num := dbms_sql.execute(v_curid);
-- Fetch rows with DBMS_SQL package:
WHILE DBMS_SQL.FETCH_ROWS(v_curid) > 0 LOOP
FOR i IN 1 .. v_colcnt LOOP
IF (v_desctab(i).col_type = 1) THEN
DBMS_SQL.COLUMN_VALUE(v_curid, i, v_name_var);
ELSIF (v_desctab(i).col_type = 2) THEN
DBMS_SQL.COLUMN_VALUE(v_curid, i, v_num_var);
ELSIF (v_desctab(i).col_type = 12) THEN
DBMS_SQL.COLUMN_VALUE(v_curid, i, v_date_var);
END IF;
END LOOP;
END LOOP;
DBMS_SQL.CLOSE_CURSOR(v_curid);
END;
/
You can do that with DBMS_SQL package.
Update
To get more detailed reference about DBMS_SQL go here.
If you are building your string within PL/SQL, you can run it with EXECUTE IMMEDIATE. <- link. Use the BULK COLLECT INTO and output the collection.
<PRE>
DECLARE
RUN_S CLOB;
IGNORE NUMBER;
SOURCE_CURSOR NUMBER;
PWFIELD_COUNT NUMBER DEFAULT 0;
L_DESCTBL DBMS_SQL.DESC_TAB2;
Z_NUMBER NUMBER;
BEGIN
RUN_S := ' SELECT 1 AS VAL1,
2 AS VAL2,
CURSOR (SELECT 11 AS VAL11,
12 AS VAL12
FROM DUAL) AS CUR1,
CURSOR (SELECT 11 AS VAL11,
12 AS VAL12
FROM DUAL) AS CUR2
FROM DUAL';
SOURCE_CURSOR := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(SOURCE_CURSOR, RUN_S, DBMS_SQL.NATIVE);
DBMS_SQL.DESCRIBE_COLUMNS2(SOURCE_CURSOR, PWFIELD_COUNT, L_DESCTBL); -- get record structure
FOR I IN 1 .. PWFIELD_COUNT LOOP
DBMS_OUTPUT.PUT_LINE('Col ' || I || ' Type:' || L_DESCTBL(I).COL_TYPE);
IF L_DESCTBL(I).COL_TYPE = 2 THEN
DBMS_SQL.DEFINE_COLUMN(SOURCE_CURSOR, I, Z_NUMBER);
END IF;
NULL;
END LOOP;
IGNORE := DBMS_SQL.EXECUTE(SOURCE_CURSOR);
LOOP
IF DBMS_SQL.FETCH_ROWS(SOURCE_CURSOR) > 0 THEN
FOR I IN 1 .. PWFIELD_COUNT LOOP
IF L_DESCTBL(I).COL_TYPE IN (2) THEN
DBMS_SQL.COLUMN_VALUE(SOURCE_CURSOR, I, Z_NUMBER);
DBMS_OUTPUT.PUT_LINE('Col ' || I || ' Value:' || Z_NUMBER);
END IF;
END LOOP;
ELSE
EXIT;
END IF;
END LOOP;
END;
</PRE>

Oracle SQL: Find {tags} in a sql string

I have SQL strings that my users write. They look like:
SELECT Name, Age from Users WHERE Name LIKE '%a%' AND {UsersWhere}
On the oracle server side when such an SQL is to be executed I want to replace the {tags} first. The replacements for the {tags} will be valid SQL sub strings I am holding in a table. Pre-manufactered sub sqls. So the treated string will be valid SQL.
Is there some fancy build-in Oracle function to make this happen?
Thanks for a hint!
I have written a small function for anyone interested:
CREATE OR REPLACE FUNCTION SA.REPLACE_VARIABLES (p_sql IN VARCHAR2)
RETURN VARCHAR2
IS
vs_return VARCHAR2 (4000);
-- Deklarationen
vs_sql VARCHAR2(4000);
vs_substring VARCHAR2(4000);
vs_variable VARCHAR2(200);
vs_variable_content VARCHAR2(4000);
BEGIN
vs_sql := p_sql;
IF INSTR(p_sql, '{') > 0 THEN
vs_substring := vs_sql;
WHILE LENGTH(vs_substring) > 0 LOOP
IF INSTR(vs_substring, '{') <> 0
THEN
vs_variable := SUBSTR(vs_substring, INSTR(vs_substring, '{'), INSTR(vs_substring, '}') - INSTR(vs_substring, '{') + 1);
-- Do whatever you want with the variable
--vs_sql := REPLACE(vs_sql, vs_variable, vs_variable_content);
-- Substring verkürzen
vs_substring := SUBSTR(vs_substring, INSTR(vs_substring, vs_variable) + LENGTH(vs_variable) + 1);
ELSE
vs_substring := '';
END IF;
END LOOP;
END IF;
RETURN vs_sql;
EXCEPTION
WHEN OTHERS
THEN
-- Err -handle
END REPLACE_VARIABLES;
/
I'd just keep it simple:
v_sql := REPLACE(v_sql, '{UsersWhere}', '...whatever you need...');