I can't able to print the following pyramid in oracle. (PL/SQL Block) - sql

I want to print the following pyramid:-
ORACLE
ORACL
ORAC
ORA
OR
O

Somewhat shorter code than previous answers:
SQL> select substr('ORACLE', 1, level) val
2 from dual
3 connect by level <= length('ORACLE')
4 order by level desc;
VAL
------------------------
ORACLE
ORACL
ORAC
ORA
OR
O
6 rows selected.
SQL>

you can achieve this using a With expression and a LEVEL connector
with str as (select 'ORACLE' as str
from dual
)
select substr(s.str, 1, length(s.str)+1 - level) as Pyramid
from str s
connect by level-(length(s.str)-1) <= (length(s.str)+1) - level
;
also if you needed as PL/SQL block, then solution is:
set serveroutput on
declare v_pyramid varchar2(100);
begin
with str as (select 'ORACLE' as str from dual)
select listagg(t.Pyramid, chr(13)) within group (order by t.lvl)
into v_pyramid
from ( select substr(s.str, 1, length(s.str)+1 - level) as Pyramid
, level as lvl
from str s
connect by level-(length(s.str)-1) <= (length(s.str)+1) - level
) t ;
dbms_output.put_line(v_pyramid);
end;
good luck :)

Here's an option if you just want plain PL/SQL.
declare
v_str varchar2(6) := 'ORACLE';
begin
for i in 0 .. length(v_str) loop
dbms_output.put_line(substr(v_str, 1, length(v_str)-i));
end loop;
end;
/

Here is Simple code:-
DECLARE
VAL VARCHAR2(200):='ORACLE';
OT VARCHAR2(200);
BEGIN
FOR I IN REVERSE 1..LENGTH(VAL) LOOP
OT:=SUBSTR(VAL,1,I);
DBMS_OUTPUT.PUT_LINE(OT);
END LOOP;
END;

Related

How to tokenize semicolon separated column value to pass to IF statement in a function in Oracle DB

I have a table called 'config' and when I query it in following manner:
SELECT value FROM config WHERE property = 'SPECIAL_STORE_ID'
its response will be: 59216;131205;76707;167206 //... (1)
I want to tokenize the above values using semicolon as the delimiter and then use them in a user-defined Function's IF statement to compare, something like this:
IF in_store_id exists in (<delimited response from (1) above>)//...(2)
THEN do some stuff
where in_store_id is the parameter passed-in to the function
Is this possible to do as one-liner in (2) above ?
I'm on Oracle 12c
One-liner? I don't think so, but - if you're satisfied with something like this, fine.
SQL> select * From config;
VALUE PROPERTY
-------------- ----------------
7369;7499;7521 SPECIAL_STORE_ID
SQL> declare
2 in_store_id varchar2(20) := 7369;
3 l_exists number;
4 begin
5 select instr(value, ';' || in_store_id || ';')
6 into l_exists
7 from config
8 where property = 'SPECIAL_STORE_ID';
9
10 if l_exists > 0 then
11 dbms_output.put_line('that STORE_ID exists in the value');
12 else
13 dbms_output.put_line('that STORE_ID does not exist in the value');
14 end if;
15 end;
16 /
that STORE_ID exists in the value
PL/SQL procedure successfully completed.
SQL>
If the delimited response is a collection then you can use member of to check if the collection contains the ID or not like
create or replace procedure test_procedure2(p_property in varchar2, p_id in varchar2) is
type test_t is table of varchar2(20);
l_ids test_t;
begin
select regexp_substr(value, '[^;]+', 1, level) bulk collect into l_ids
from (select value from config where property = p_property)
connect by level <= regexp_count(value, ';')+1;
if(p_id member of (l_ids)) then
dbms_output.put_line('Do stuff for '||p_property||' '||p_id);
end if;
end;
/
or do it without the collection with intermediate select like
create or replace procedure test_procedure1(p_property in varchar2, p_id in varchar2) is
l_flag number(3);
begin
select count(1) into l_flag from dual where p_id in (
select regexp_substr(value, '[^;]+', 1, level)
from (select value from config where property = p_property)
connect by level <= regexp_count(value, ';')+1
);
if(l_flag > 0) then
dbms_output.put_line('Do stuff for '||p_property||' '||p_id);
end if;
end;
/
See fiddle

Calculate values from column having expression

I have a table "test_calculate" this has a column "CONN_BY" having values
column can have more than 2 number to multiply and this table may contain millions of rows , I need to get the result of the calculation from "CONN_BY" to "MVP".
I have used xmlquery for the calculation and dynamic query but these are quite slow. Is there another way which is much faster .Please suggest.
You can try the dynamic query.
Create a function which returns the calculated value and use it in your insert or select queries.
CREATE OR REPLACE FUNCTION UFN_CALCULATE (CLM_VALUE VARCHAR2)
RETURN NUMBER IS
RES_VAL NUMBER;
BEGIN
EXECUTE IMMEDIATE 'select '||CLM_VALUE||' FROM DUAL' INTO RES_VAL;
RETURN RES_VAL;
END;
You can use that function like below.
SELECT UFN_CALCULATE('.0876543 * .09876') FROM DUAL;
SELECT UFN_CALCULATE(CONN_BY) FROM YOUR_TABLE;
One option is using select ... connect by level <= regexp_count(conn_by,'[^*]+')... query for the implicit cursor within a PL/SQL code block
SQL> set serveroutput on
SQL> declare
mvp owa.nc_arr; -- numeric array to initialize each multiplication to 1 for each id value
begin
dbms_output.put_line('ID MVP');
dbms_output.put_line('--------');
for c in
(
select id,
to_number( regexp_substr(conn_by,'[^*]+',1,level) ) as nr,
level as lvl , max( level ) over ( partition by id ) as mx_lvl
from test_calculate
connect by level <= regexp_count(conn_by,'[^*]+')
and prior sys_guid() is not null
and prior conn_by = conn_by
order by id, lvl
)
loop
if c.lvl = 1 then mvp(c.id) := 1; end if;
mvp(c.id) := c.nr * mvp(c.id);
if c.lvl = c.mx_lvl then
dbms_output.put_line(c.id||' '||mvp(c.id));
end if;
end loop;
end;
/
where test_calculate is assumed to have an identity column(id)
Demo

With statement select and function into cursor

it is possible to use the with structure with a function inside a cursor, I don't know if I am declaring it inappropriately, I am getting the following error using with function inside procedure pl sql statement is not supported
CURSOR c_detail IS
WITH
FUNCTION CALC_NUMBER(FOB_ITEM NUMBER DEFAULT 0,
FOB_TOTAL NUMBER DEFAULT 0,
WEIGHT NUMBER DEFAULT 0) RETURN NUMBER
IS
PESO_BRUTO_ITEM NUMBER :=0;
BEGIN
IF( (FOB_ITEM > 0) AND (FOB_TOTAL > 0 AND WEIGHT> 0 )) THEN
PESO_BRUTO_ITEM := (FOB_ITEM * WEIGHT) / FOB_TOTAL;
END IF;
RETURN PESO_BRUTO_ITEM;
END CALC_NUMBER;
test_data AS
(
SELECT 36.25 AS FOB_I, 12536.36 AS FOB_TOTAL, 362 AS W FROM dual UNION ALL
SELECT 15.36 AS FOB_I, 3678.65 AS FOB_TOTAL, 362 AS W FROM dual UNION ALL
SELECT 878.77 AS FOB_I, 89653.13 AS FOB_TOTAL, 362 AS W FROM dual
)
SELECT TD.FOB_I,
TD.FOB_TOTAL,
CALC_NUMBER(TD.FOB_I, TD.FOB_TOTAL, TD.W) WEIGHT
FROM test_data TD
[TL;DR] You can declare a function in a sub-query factoring clause but (as Justin Cave points out) it only works when you are executing the query as dynamic SQL and support for using functions in static SQL within a cursor may be available in future database versions.
This sub-query factoring clause with a function works outside of a cursor:
WITH
FUNCTION with_function(
p_id IN NUMBER
) RETURN NUMBER
IS
BEGIN
RETURN 42 + p_id;
END;
test_data ( id ) AS (
SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= 3
)
SELECT id,
with_function( id )
FROM test_data;
Trying to put it into a cursor (in Oracle 18c):
DECLARE
p_id NUMBER;
p_fn NUMBER;
CURSOR c_detail IS
WITH
FUNCTION with_function(
p_id IN NUMBER
) RETURN NUMBER
IS
BEGIN
RETURN 42 + p_id;
END;
test_data ( id ) AS (
SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= 3
)
SELECT id,
with_function( id )
FROM test_data;
BEGIN
OPEN c_detail;
LOOP
FETCH c_detail INTO p_id, p_fn;
EXIT WHEN c_detail%NOTFOUND;
DBMS_OUTPUT.PUT_LINE( p_id || ', ' || p_fn );
END LOOP;
CLOSE c_detail;
END;
/
Outputs the error:
ORA-06550: line 7, column 14:
PL/SQL: ORA-00905: missing keyword
ORA-06550: line 6, column 3:
PL/SQL: SQL Statement ignored
ORA-06550: line 13, column 5:
PLS-00103: Encountered the symbol "END" when expecting one of the following:
begin function pragma procedure subtype type <an identifier>
<a double-quoted delimited-identifier> current cursor delete
exists prior
Removing the function then the cursor works:
DECLARE
p_id NUMBER;
CURSOR c_detail IS
WITH
test_data ( id ) AS (
SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= 3
)
SELECT id
FROM test_data;
BEGIN
OPEN c_detail;
LOOP
FETCH c_detail INTO p_id;
EXIT WHEN c_detail%NOTFOUND;
DBMS_OUTPUT.PUT_LINE( p_id );
END LOOP;
CLOSE c_detail;
END;
/
So it is not an issue with using a sub-query factoring clause in a cursor.
Executing the cursor as dynamic SQL query:
DECLARE
p_id NUMBER;
p_fn NUMBER;
c_detail SYS_REFCURSOR;
p_sql VARCHAR2(4000) := 'WITH
FUNCTION with_function(
p_id IN NUMBER
) RETURN NUMBER
IS
BEGIN
RETURN 42 + p_id;
END;
test_data ( id ) AS (
SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= 3
)
SELECT id,
with_function( id )
FROM test_data';
BEGIN
OPEN c_detail FOR p_sql;
LOOP
FETCH c_detail INTO p_id, p_fn;
EXIT WHEN c_detail%NOTFOUND;
DBMS_OUTPUT.PUT_LINE( p_id || ', ' || p_fn );
END LOOP;
CLOSE c_detail;
END;
/
Works and outputs:
1, 43
2, 44
3, 45
So it appears that, yes, you can declare a function in a sub-query factoring clause but it only works when you are executing the query as dynamic SQL.
db<>fiddle here
The error message clearly says you can't do what you are after, but then you will not have to if your cursor looks like following
CURSOR c_detail IS
WITH test_data AS(
SELECT 36.25 AS FOB_I, 12536.36 AS FOB_TOTAL, 362 AS W FROM dual UNION ALL
SELECT 15.36 AS FOB_I, 3678.65 AS FOB_TOTAL, 362 AS W FROM dual UNION ALL
SELECT 878.77 AS FOB_I, 89653.13 AS FOB_TOTAL, 362 AS W FROM dual
)
SELECT TD.FOB_I,
TD.FOB_TOTAL,
CASE WHEN TD.FOB_I > 0 AND TD.FOB_TOTAL > 0 AND TD.W > 0
THEN (TD.FOB_I * TD.W) / TD.FOB_TOTAL
ELSE 0
END WEIGHT
FROM test_data TD
;

How to get Numbers in number range by PL-Sql .?

here is my serial table.it has more than 1000 records.its with start number and end number.but between numbers not exist.
i need to add all number [start/between & end numbers] records in another temp table number by number
like below
EXIST TABLE
select concat(CARD_BULK_CODE,start_serial) startserial,concat(CARD_BULK_CODE,end_serial) endserial
from TSR_BULK_CARD_SERIALS
---------------------------
STARTSERIAL ENDSERIAL |
---------------------------
18126944 18126946 |
18141101 18141122 |
15150722 15150729 |
19069303 19069317 |
---------------------------
REQUIRED TABLE
-----------
SERIAL_NO |
-----------
18126944
18126945
18141101
18141102
....
-----------
seem its need pl-sql to implement this.
please help me to sort out this issue
I tried with below query with the help of dual.
but its very slow and not yet got results :-) running more than 1 Hour
select distinct concat(t.CARD_BULK_CODE,t.START_SERIAL)+level-1 SERIAL
from TSR_BULK_CARD_SERIALS t, dual
connect by level-1<=(concat(t.CARD_BULK_CODE,t.END_SERIAL ))-concat(t.CARD_BULK_CODE,t.START_SERIAL)
order by 1
EDIT :
Dear Alen & Dba.i tried with your ones and below error occured.
DECLARE
l_st NUMBER;
l_en NUMBER;
BEGIN
FOR rec IN (select concat(card_bulk_code, start_serial) startserial,concat(card_bulk_code, end_serial) endserial from tsr_bulk_card_serials)
LOOP
l_st := rec.startserial;
l_en := rec.endserial;
FOR rec1 IN l_st..l_en
LOOP
INSERT INTO temp(serial_no) values(rec1);
END LOOP;
END LOOP;
COMMIT;
END;
Error at line 1
ORA-01426: numeric overflow
ORA-06512: at line 9
Script Terminated on line 1.
One way to do it without resorting to plsql
WITH ranges AS
(
SELECT CONCAT(CARD_BULK_CODE, start_serial) startserial,
CONCAT(CARD_BULK_CODE, end_serial) endserial
FROM TSR_BULK_CARD_SERIALS
),
numbers(n) AS (
SELECT 0 n
FROM dual
UNION ALL
SELECT n + 1
FROM numbers
WHERE n <=
(
SELECT MAX(endserial - startserial)
FROM ranges
)
)
SELECT t.startserial + n.n SERIAL_NO
FROM ranges t JOIN numbers n
ON n.n <= t.endserial - t.startserial
ORDER BY SERIAL_NO
Here is SQLFiddle demo
Just write some PL/SQL - iterate through your table and insert rows in the temp table.
declare
l_start number;
l_end number;
begin
for r_rec in (select to_number(concat(card_bulk_code, start_serial)) startserial
, to_number(concat(card_bulk_code, end_serial)) endserial
from tsr_bulk_card_serials )
loop
l_start := r_rec.startserial;
l_end := r_rec.endserial;
for l_i in l_start..l_end loop
insert into your_temp_table;
end loop;
end loop;
end;
Try like this,
WITH t(ST, EN) AS
(
SELECT 18126944, 18126946 FROM dual
UNION
SELECT 18141101, 18141122 FROM dual
UNION
SELECT 15150722, 15150729 FROM dual
UNION
SELECT 19069303 , 19069317 FROM dual
)
SELECT DISTINCT st + LEVEL -1
FROM t
CONNECT BY LEVEL <= (SELECT en - st + 1 FROM DUAL)
ORDER BY 1;
/
Try something like this for PL/SQL,
DECLARE
l_st NUMBER;
l_en NUMBER;
BEGIN
FOR rec IN (SELECT * FROM t)
LOOP
l_st := rec.st;
l_en := rec.en;
FOR rec1 IN l_st..l_en
LOOP
INSERT INTO <your_tab>;
END LOOP;
END LOOP;
COMMIT;
END;
DECLARE
l_st NUMBER (20);
l_en NUMBER (20);
testnum NUMBER (4);
BEGIN
FOR rec IN (SELECT CONCAT (card_bulk_code, start_serial) startserial,CONCAT (card_bulk_code, end_serial) endserial FROM tsr_bulk_card_serials)
LOOP
l_st := TO_NUMBER (rec.startserial);
l_en := TO_NUMBER (rec.endserial);
testnum := l_en - l_st;
DBMS_OUTPUT.put_line (l_st);
DBMS_OUTPUT.put_line (l_en);
IF l_st < l_en
THEN
FOR rec1 IN 0 .. testnum
LOOP
l_st := l_st + 1;
INSERT INTO temp(serial_no) VALUES (l_st);
END LOOP;
END IF;
END LOOP;
COMMIT;
END;
above code helped me to sorted my issue
thanks all :-)

Parse varchar2 to table (Oracle)

Is there built-in function in Oracle DB 11g r2 that could parse varchar2 variable to table? Opposite of listagg or wm_concat. I found only Tom Kyte's method dated 2006:
with data as
(
select trim(substr (txt, instr(txt, ',', 1, level) + 1
, instr(txt, ',', 1, level + 1) - instr(txt, ',', 1, level) - 1)) as token
from (select ',' || :txt || ',' txt from dual)
connect by level <= length(:txt) - length(replace(:txt, ',', '')) + 1
)
select * from data;
I think Oracle must have simpler way.
No.
I would simplify Tom's method slightly, but not by much; you can now use regular expressions as well:
select regexp_substr(:txt, '[^,]+', 1, level)
from dual
connect by regexp_substr(:txt, '[^,]+', 1, level) is not null
SQL Fiddle
Ben's regexp_substr solution is generally the preferred solution. If your string happens to consist of strings that are valid Oracle identifiers-- they are less than or equal to 30 characters and begin with an alphabetic character, you can also use the dbms_utility.comma_to_table function. Given those limitations, however, it's generally better to use the general-purpose solution.
SQL> ed
Wrote file afiedt.buf
1 declare
2 l_string varchar2(1000) := 'foo,bar,b123,FuzzyBunny,abcdefghij12345678901234567890';
3 l_num pls_integer;
4 l_arr dbms_utility.uncl_array;
5 begin
6 dbms_utility.comma_to_table( l_string, l_num, l_arr );
7 for i in 1..l_arr.count
8 loop
9 dbms_output.put_line( l_arr(i) );
10 end loop;
11* end;
SQL> /
foo
bar
b123
FuzzyBunny
abcdefghij12345678901234567890
PL/SQL procedure successfully completed.