Printing with loops in sql - oracle

Hi I wrote a procedure to accomplish a task and it looks like this
create or replace procedure myproc
is
begin
for rec_ in (
select
case
when highest_ = 1 then
'Most profit in ' || category || ' -> ' || carname || ': ' || aprofit
when lowest_ = 1 then
'Least profit in ' || category || ' -> ' || carname || ': ' || aprofit
end report
from (
select
category
, carname
, aprofit
, rank() over ( partition by category order by aprofit asc ) lowest_
, rank() over ( partition by category order by aprofit desc ) highest_
from (
select unique
C.category
, C.carname
, avg( R.rentalrate - C.suggesteddealerrentalprice )
over ( partition by C.category, C.carname ) as aprofit
from rentals R
join car C on R.carid = C.carid
join cardealers CD on CD.dealerid = C.dealerid
where CD.state = 'IN'
)
)
where lowest_ = 1 or highest_ = 1
order by case when lowest_ > 1 then 2 else 1 end, category, carname
)
loop
dbms_output.put_line( rec_.report ) ;
end loop ;
end ;
/
begin
myproc ;
end ;
/
This prints out the output that looks like this
Least profit in compact -> Nissan Versa: 4
Least profit in compact -> Toyota Yaris: 4
Least profit in luxury -> Porsche: 40
Least profit in van -> Chrysler: 2
Most profit in compact -> Chevy Spark: 5
Most profit in luxury -> Audi: 45
Most profit in van -> Honda Odyssey: 9
I want it to print out like this that if a car category has more than one car inside it it only prints the heading one time. But as you can see in my output the heading for "Least profit in compact" is being printed two times. I want an output that looks like this
Least Profit in compact
- Nissan Versa: 4
- Toyota Yaris: 4
Least Profit in luxury
- Porsche: 40
Least Profit in van
- Chrysler: 2
Most Profit in compact
- Chevy Spark: 5
Most Profit in luxury
- Audi: 45
Most Profit in van
- Honda Odyssey: 9
The tables and data files are
Data file
Tables file

You may select the columns Least/Most,category,carname,aprofit separately from your select query and then use DBMS_OUTPUT conditionally.
I have used your sample dataset from the output in the below code as I do not have your table/ definitions.
DECLARE
v_high_low VARCHAR2(40) := 'DUMMY';
v_category VARCHAR2(40) := 'DUMMY';
BEGIN
for rec_ IN
(
with t (high_low,category,carname,aprofit) AS
(
select 'Least profit in ','compact', 'Nissan Versa', 4 from dual union all
select 'Least profit in ','compact','Toyota Yaris', 4 from dual union all
select 'Least profit in ','luxury','Porsche', 40 from dual union all
select 'Least profit in ','van','Chrysler', 2 from dual union all
select 'Most profit in ','compact','Chevy Spark', 5 from dual union all
select 'Most profit in ','luxury','Audi', 45 from dual union all
select 'Most profit in ','van', 'Honda Odyssey', 9 from dual
)
SELECT * FROM t order by high_low,category,carname
)
LOOP
IF rec_.high_low != v_high_low OR rec_.category != v_category
THEN
DBMS_OUTPUT.PUT(rec_.high_low);
v_high_low := rec_.high_low;
END IF;
IF rec_.category != v_category
THEN
DBMS_OUTPUT.PUT_LINE(rec_.category);
v_category := rec_.category;
END IF;
DBMS_OUTPUT.PUT_LINE(' - '||rec_.carname||': '|| rec_.aprofit);
END LOOP;
END;
/
DEMO
EDIT - Adding this demo link with the procedure with actual tables provided :
DEMO2

Related

Issues in inserting comma separated strings to table

I have following 3 parameters from stored procedure
P_Param1 = 12
P_Paramj2= 'val:15,val:16'
P_param3 = 'Name:check values,Name:bv,Name:cv'
I have a table and need to insert above details and final table looks like below
proID CatID CatName
12 15 check values
12 15 bv
12 15 cv
12 16 check values
12 16 bv
12 16 cv
I have written a query to split P_param3 as below and getting splitted values but stuck in generating loops to make a table like above.
SELECT
regexp_substr('Name:check values,Name:bv,Name:cv', '(Name:)?(.*?)(,Name:|$)', 1, level, NULL,
2) AS "CatName"
FROM
dual
CONNECT BY
level <= regexp_count('Name:check values,Name:bv,Name:cv', 'Name:');
Split the catIds into rows and split the catNames into rows and then CROSS JOIN them and insert.
You can do it with simple (fast) string functions using:
CREATE PROCEDURE insertCats(
p_proid IN table_name.proid%TYPE,
p_catids IN VARCHAR2,
p_catnames IN VARCHAR2
)
IS
c_catid_prefix CONSTANT VARCHAR2(10) := 'val:';
c_catid_length CONSTANT PLS_INTEGER := LENGTH(c_catid_prefix);
c_catname_prefix CONSTANT VARCHAR2(10) := 'Name:';
c_catname_length CONSTANT PLS_INTEGER := LENGTH(c_catname_prefix);
BEGIN
INSERT INTO table_name (proid, catid, catname)
WITH catid_bounds (catids, spos, epos) AS (
SELECT p_catids,
1 + c_catid_length,
INSTR(p_catids, ',', 1 + c_catid_length)
FROM DUAL
UNION ALL
SELECT catids,
epos + 1 + c_catid_length,
INSTR(catids, ',', epos + 1 + c_catid_length)
FROM catid_bounds
WHERE epos > 0
),
catids (catid) AS (
SELECT CASE epos
WHEN 0
THEN SUBSTR(catids, spos)
ELSE SUBSTR(catids, spos, epos - spos)
END
FROM catid_bounds
),
catname_bounds (catnames, spos, epos) AS (
SELECT p_catnames,
1 + c_catname_length,
INSTR(p_catnames, ',', 1 + c_catname_length)
FROM DUAL
UNION ALL
SELECT catnames,
epos + 1 + c_catname_length,
INSTR(catnames, ',', epos + 1 + c_catname_length)
FROM catname_bounds
WHERE epos > 0
),
catnames (catname) AS (
SELECT CASE epos
WHEN 0
THEN SUBSTR(catnames, spos)
ELSE SUBSTR(catnames, spos, epos - spos)
END
FROM catname_bounds
)
SELECT p_proid,
i.catid,
n.catname
FROM catids i CROSS JOIN catnames n;
END;
/
db<>fiddle here

Ora-00932 - expected NUMBER got -

I have been running the below query without issue:
with Nums (NN) as
(
select 0 as NN
from dual
union all
select NN+1 -- (1)
from Nums
where NN < 30
)
select null as errormsg, trunc(sysdate)-NN as the_date, count(id) as the_count
from Nums
left join
(
SELECT c1.id, trunc(c1.c_date) as c_date
FROM table1 c1
where c1.c_date > trunc(sysdate) - 30
UNION
SELECT c2.id, trunc(c2.c_date)
FROM table2 c2
where c2.c_date > trunc(sysdate) -30
) x1
on x1.c_date = trunc(sysdate)-Nums.NN
group by trunc(sysdate)-Nums.NN
However, when I try to pop this in a proc for SSRS use:
procedure pr_do_the_thing (RefCur out sys_refcursor)
is
oops varchar2(100);
begin
open RefCur for
-- see above query --
;
end pr_do_the_thing;
I get
Error(): PL/SQL: ORA-00932: inconsistent datatypes: expected NUMBER got -
Any thoughts? Like I said above, as a query, there is no issue. As a proc, the error appears at note (1) int eh query.
This seems to be bug 18139621 (see MOS Doc ID 2003626.1). There is a patch available, but if this is the only place you encounter this, it might be simpler to switch to a hierarchical query:
with Nums (NN) as
(
select level - 1
from dual
connect by level <= 31
)
...
You could also calculate the dates inside the CTE (which also fails with a recursive CTE):
with Dates (DD) as
(
select trunc(sysdate) - level + 1
from dual
connect by level <= 31
)
select null as errormsg, DD as the_date, count(id) as the_count
from Dates
left join
(
SELECT c1.id, trunc(c1.c_date) as c_date
FROM table1 c1
where c1.c_date > trunc(sysdate) - 30
UNION
SELECT c2.id, trunc(c2.c_date)
FROM table2 c2
where c2.c_date > trunc(sysdate) -30
) x1
on x1.c_date = DD
group by DD;
I'd probably organise it slightly differently, so the subquery doesn't limit the date range directly:
with dates (dd) as
(
select trunc(sysdate) - level + 1
from dual
connect by level <= 31
)
select errormsg, the_date, count(id) as the_count
from (
select null as errormsg, d.dd as the_date, c1.id
from dates d
left join table1 c1 on c1.c_date >= d.dd and c1.c_date < d.dd + 1
union all
select null as errormsg, d.dd as the_date, c2.id
from dates d
left join table2 c2 on c2.c_date >= d.dd and c2.c_date < d.dd + 1
)
group by errormsg, the_date;
but as always with these things, check the performance of each approach...
Also notice that I've switched from union to union all. If an ID could appear more than once on the same day, in the same table or across both tables, then the counts will be different - you need to decide whether you want to count them once or as many times as they appear. That applies to your original query too.

How to compare items in an array to those in a database column using regular expressions?

I'm trying to take a list of elements in an array like this:
['GRADE', 'GRATE', 'GRAPE', /*About 1000 other entries here ...*/ ]
and match them to their occurrences in a column in an Oracle database full of entries like this:
1|'ANTERIOR'
2|'ANTEROGRADE'
3|'INGRATE'
4|'RETROGRADE'
5|'REIGN'
...|...
/*About 1,000,000 other entries here*/
For each entry in that array of G words, I'd like to loop through the word column of the Oracle database and try to find the right-sided matches for each entry in the array. In this example, entries 2, 3, and 4 in the database would all match.
In any other programming language, it would look something like this:
for entry in array:
for each in column:
if entry.right_match(each):
print entry
How do I do this in PL/SQL?
In PL/SQL it can be done in this way:
declare
SUBTYPE my_varchar2_t IS varchar2( 100 );
TYPE Roster IS TABLE OF my_varchar2_t;
names Roster := Roster( 'GRADE', 'GRATE', 'GRAPE');
begin
FOR c IN ( SELECT id, name FROM my_table )
LOOP
FOR i IN names.FIRST .. names.LAST LOOP
IF regexp_like( c.name, names( i ) ) THEN
DBMS_OUTPUT.PUT_LINE( c.id || ' ' || c.name );
END IF;
END LOOP;
END LOOP;
end;
/
but this is row by row processing, for large table it would be very slow.
I think it might be better to do it in a way shown below:
create table test123 as
select 1 id ,'ANTERIOR' name from dual union all
select 2,'ANTEROGRADE' from dual union all
select 3,'INGRATE' from dual union all
select 4,'RETROGRADE' from dual union all
select 5,'REIGN' from dual ;
create type my_table_typ is table of varchar2( 100 );
/
select *
from table( my_table_typ( 'GRADE', 'GRATE', 'GRAPE' )) x
join test123 y on regexp_like( y.name, x.column_value )
;
COLUMN_VALUE ID NAME
------------- ---------- -----------
GRADE 2 ANTEROGRADE
GRATE 3 INGRATE
GRADE 4 RETROGRADE

how to omit first and last comma when using sys_connect_by_path function in oracle?

so this is my table--
create table student
(
stu_id int,
s_name nvarchar(max),
s_subject nvarchar(max),
marks varchar(20)
)
and the values are
insert into student values(123,'pammy','English','88');
insert into student values(123,'pammy','Maths','56');
insert into student values(124,'watts','Biology','98');
insert into student values(125,'Tom','Physics','90');
insert into student values(125,'Tom','Computer','95');
insert into student values(125,'Tom','ED','75');
so what i have done is extracted data which occurred thrice. and then concatenated the string values using sys_connect_by_path.
My code is--
select stu_id,s_name,
max(sys_connect_by_path(s_subject, ', ' )) s_subject,
max(sys_connect_by_path(marks, ', ' )) marks
from (select stu_id,s_name,s_subject,marks,
row_number() over
(partition by stu_id order by s_subject) rn
from student
)
start with rn = 1
connect by prior rn = rn-1 and prior stu_id = stu_id
group by stu_id,s_name
having stu_id in ( select stu_id
from student
group by stu_id
having count(stu_id) >3 )
order by stu_id,s_name
output of my code is --
stu_id s_name s_subject marks
125 Tom ,Physics,Computer,ED, ,90,95,75,
the code is working perfectly, but I am using comma as seprator, and i just want to get rid of comma at the start and at the end. in s_subject column.
what I wants is
stu_id s_name s_subject marks
125 Tom Physics,Computer,ED 90,95,75
I tried trim function, but i could not get success.
I can substr the sys connect by path if my data is fixed, but here data is not fixed.
So pls help..
SQL> select stu_id
2 , s_name
3 , ltrim(max(sys_connect_by_path(s_subject, ', ' )),', ') s_subject
4 , ltrim(max(sys_connect_by_path(marks, ', ' )),', ') marks
5 from ( select stu_id
6 , s_name
7 , s_subject
8 , marks
9 , row_number() over (partition by stu_id order by s_subject) rn
10 from student
11 )
12 where level >= 3
13 start with rn = 1
14 connect by prior rn = rn - 1
15 and prior stu_id = stu_id
16 group by stu_id
17 , s_name
18 /
STU_ID S_NAME S_SUBJECT MARKS
---------- ---------- ------------------------------ --------------------
125 Tom Computer, ED, Physics 95, 75, 90
1 row selected.
Regards,
Rob.
PS: Thanks and +1 for providing the create table statement and insert statements.
Here's how I would do it in Oracle 11.2:
SQL> select stu_id, s_name,
2 listagg(s_subject, ', ' ) within group (order by s_subject) s_subject,
3 listagg(marks, ', ' ) within group (order by s_subject) marks
4 from student
5 group by stu_id, s_name;
STU_ID S_NAME S_SUBJECT MARKS
------ --------------- ------------------------- ---------------
123 pammy English, Maths 88, 56
124 watts Biology 98
125 Tom Computer, ED, Physics 95, 75, 90
Notice that I ordered both lists by subject, so the order corresponds in each list column.
substr( max(sys_connect_by_path(s_subject, ', ' )),
2,
length(max(sys_connect_by_path(s_subject, ', ' ))-1 )

How can I return multiple identical rows based on a quantity field in the row itself?

I'm using oracle to output line items in from a shopping app. Each item has a quantity field that may be greater than 1 and if it is, I'd like to return that row N times.
Here's what I'm talking about for a table
product_id, quanity
1, 3,
2, 5
And I'm looking a query that would return
1,3
1,3
1,3
2,5
2,5
2,5
2,5
2,5
Is this possible? I saw this answer for SQL Server 2005 and I'm looking for almost the exact thing in oracle. Building a dedicated numbers table is unfortunately not an option.
I've used 15 as a maximum for the example, but you should set it to 9999 or whatever the maximum quantity you will support.
create table t (product_id number, quantity number);
insert into t values (1,3);
insert into t values (2,5);
select t.*
from t
join (select rownum rn from dual connect by level < 15) a
on a.rn <= t.quantity
order by 1;
First create sample data:
create table my_table (product_id number , quantity number);
insert into my_table(product_id, quantity) values(1,3);
insert into my_table(product_id, quantity) values(2,5);
And now run this SQL:
SELECT product_id, quantity
FROM my_table tproducts
,( SELECT LEVEL AS lvl
FROM dual
CONNECT BY LEVEL <= (SELECT MAX(quantity) FROM my_table)) tbl_sub
WHERE tbl_sub.lvl BETWEEN 1 AND tproducts.quantity
ORDER BY product_id, lvl;
PRODUCT_ID QUANTITY
---------- ----------
1 3
1 3
1 3
2 5
2 5
2 5
2 5
2 5
This question is propably same as this: how to calc ranges in oracle
Update solution, for Oracle 9i:
You can use pipelined_function() like this:
CREATE TYPE SampleType AS OBJECT
(
product_id number,
quantity varchar2(2000)
)
/
CREATE TYPE SampleTypeSet AS TABLE OF SampleType
/
CREATE OR REPLACE FUNCTION GET_DATA RETURN SampleTypeSet
PIPELINED
IS
l_one_row SampleType := SampleType(NULL, NULL);
BEGIN
FOR cur_data IN (SELECT product_id, quantity FROM my_table ORDER BY product_id) LOOP
FOR i IN 1..cur_data.quantity LOOP
l_one_row.product_id := cur_data.product_id;
l_one_row.quantity := cur_data.quantity;
PIPE ROW(l_one_row);
END LOOP;
END LOOP;
RETURN;
END GET_DATA;
/
Now you can do this:
SELECT * FROM TABLE(GET_DATA());
Or this:
CREATE OR REPLACE VIEW VIEW_ALL_DATA AS SELECT * FROM TABLE(GET_DATA());
SELECT * FROM VIEW_ALL_DATA;
Both with same results.
(Based on my article pipelined function)

Resources