My answer is fetching multiple rows apart from the rows provided in the output - oracle

I am trying to write a PLSQL block to display details of books along with their Author and Publisher. problem statement is as follow: Use String functions to display only the first 10 letters of column 'Title' and Sort the result set based on 'Title' in ascending order. Schema details are given below:-
Table Name Columns
1.author authorid, firstname, lastname
2.book author_id, bookid, publisherid, title
3.publisher publisherid, publishername
Output format is as follows:
BOOKID.............TITLE.............publisher.............Author
639163050...........10 Years o...........Prentice Hall...........Paul Deitel
330895717...........African Fo...........Prentice Hall...........Tem Nieto
set serveroutput on;
declare
cursor c is
select * from book b
left join author on authorid=author_id
left join publisher p on b.publisherid=p.publisherid
order by title asc;
begin
dbms_output.put_line('BOOKID'||'.............'||'TITLE'||'.............'||'publisher'||'.............'||'Author');
for i in c loop
dbms_output.put_line(i.bookid||'.............'||substr(i.title,1,10)||'.............'||i.publishername||'.............'||concat(i.firstname||' ',i.lastname));
end loop;
end;
/
OUTPUT is coming as below:-
BOOKID.............TITLE.............publisher.............Author
639163050.............10 Years o.............Prentice Hall.............Paul Deitel
330895717.............African Fo.............Prentice Hall.............Tem Nieto
110125075.............African Fu.............Prentice Hall.............Tem Nieto
162261197.............An Antholo.............Prentice Hall.............Paul Deitel
730284192.............Introducti.............Prentice Hall PTG.............Harvey Deitel
260895501.............Jambula Tr.............Prentice Hall PTG.............Harvey Deitel
120284191.............Land Apart.............Prentice Hall PTG.............Tem Nieto
230856118.............Less Than .............Prentice Hall.............Harvey Deitel
199163050.............Love Child.............Prentice Hall.............Sean Santry
140829293.............Nobody Eve.............Prentice Hall PTG.............Sean Santry
220161438.............Of Suffoca.............Prentice Hall PTG.............Harvey Deitel
130895725.............Op{-truncated-}

If I understood what you're saying, you'll need RPAD (to full column length, counting dots as well), along with SUBSTR. Something like this:
SQL> set serveroutput on
SQL>
SQL> begin
2 -- Header
3 dbms_output.put_line(
4 'BOOKID.............TITLE.............publisher.............Author');
5
6 -- You'd actually join those tables to get the result; I'm fabricating it
7 -- as you didn't post proper sample data
8 for cur_r in (select 639163050 bookid, '10 years of something' title,
9 'Prentice Hall' publisher, 'Paul Dietel' author from dual union all
10 select 330895717 , 'African fog is foggy' ,
11 'Prentice Hall' , 'Tem Nieto' from dual
12 )
13 loop
14 dbms_output.put_line(rpad(cur_r.bookid, 19, '.') ||
15 rpad(substr(cur_r.title, 1, 10), 18, '.') ||
16 rpad(cur_r.publisher, 22, '.') ||
17 cur_r.author
18 );
19 end loop;
20 end;
21 /
BOOKID.............TITLE.............publisher.............Author
639163050..........10 years o........Prentice Hall.........Paul Dietel
330895717..........African fo........Prentice Hall.........Tem Nieto
PL/SQL procedure successfully completed.
SQL>

Related

Using EXECUTE IMMEDIATE based on entries of table

I (using Oracle 12c, PL/SQL) need to update an existing table TABLE1 based on information stored in a table MAP. In a simplified version, MAP looks like this:
COLUMN_NAME
MODIFY
COLUMN1
N
COLUMN2
Y
COLUMN3
N
...
...
COLUMNn
Y
COLUMN1 to COLUMNn are column names in TABLE1 (but there are more columns, not just these). Now I need to update a column in TABLE1 if MODIFY in table MAP contains a 'Y' for that columns' name. There are other row conditions, so what I would need would be UPDATE statements of the form
UPDATE TABLE1
SET COLUMNi = value_i
WHERE OTHER_COLUMN = 'xyz_i';
where COLUMNi runs through all the columns of TABLE1 which are marked with MODIFY = 'Y' in MAP. value_i and xyz_i also depend on information stored in MAP (not displayed in the example).
The table MAP is not static but changes, so I do not know in advance which columns to update. What I did so far is to generate the UPDATE-statements I need in a query from MAP, i.e.
SELECT <Text of UPDATE-STATEMENT using row information from MAP> AS SQL_STMT
FROM MAP
WHERE MODIFY = 'Y';
Now I would like to execute these statements (possibly hundreds of rows). Of course I could just copy the contents of the query into code and execute, but is there a way to do this automatically, e.g. using EXECUTE IMMEDIATE? It could be something like
BEGIN
EXECUTE IMMEDIATE SQL_STMT USING 'xyz_i';
END;
only that SQL_STMT should run through all the rows of the previous query (and 'xyz_i' varies with the row as well). Any hints how to achieve this or how one should approach the task in general?
EDIT: As response to the comments, a bit more background how this problem emerges. I receive an empty n x m Matrix (empty except row and column names, think of them as first row and first column) quarterly and need to populate the empty fields from another process.
The structure of the initial matrix changes, i.e. there may be new/deleted columns/rows and existing columns/rows may change their position in the matrix. What I need to do is to take the old version of the matrix, where I already have filled the empty spaces, and translate this into the new version. Then, the populating process merely looks if entries have changed and if so, alters them.
The situation from the question arises after I have translated the old version into the new one, before doing the delta. The new matrix, populated with the old information, is TABLE1. The delta process, over which I have no control, gives me column names and information to be entered into the cells of the matrix (this is table MAP). So I need to find the column in the matrix labeled by the delta process and then to change values in rows (which ones is specified via other information provided by the delta process)
Dynamic SQL it is; here's an example, see if it helps.
This is a table whose contents should be modified:
SQL> select * from test order by id;
ID NAME SALARY
---------- ---------- ----------
1 Little 100
2 200
3 Foot 0
4 0
This is the map table:
SQL> select * from map;
COLUMN CB_MODIFY VALUE WHERE_CLAUSE
------ ---------- ----- -------------
NAME Y Scott where id <= 3
SALARY N 1000 where 1 = 1
Procedure loops through all columns that are set to be modified, composes the dynamic update statement and executes it:
SQL> declare
2 l_str varchar2(1000);
3 begin
4 for cur_r in (select m.column_name, m.value, m.where_clause
5 from map m
6 where m.cb_modify = 'Y'
7 )
8 loop
9 l_str := 'update test set ' ||
10 cur_r.column_name || ' = ' || chr(39) || cur_r.value || chr(39) || ' ' ||
11 cur_r.where_clause;
12 execute immediate l_str;
13 end loop;
14 end;
15 /
PL/SQL procedure successfully completed.
Result:
SQL> select * from test order by id;
ID NAME SALARY
---------- ---------- ----------
1 Scott 100
2 Scott 200
3 Scott 0
4 0
SQL>

How to return rows based on the database user and the table's contents?

I have a following table:
id name score
1 SYS 4
2 RHWTT 5
3 LEO 4
4 MOD3_ADMIN 5
5 VPD674 4
6 SCOTT 5
7 HR 4
8 OE 5
9 PM 4
10 IX 5
11 SH 4
12 BI 5
13 IXSNEAKY 4
14 DVF 5
I want to create a policy function in Oracle SQL that makes sure of the following things:
If a user(Leo) is executing a select statement on this table, it only gets 3 LEO 4.
sys_dba gets all the results no matter what.
I have given select permissions to Leo on this table created by Scott.
I am getting stuck at writing this complex PL/SQL function. I tried the following and it states compilation errors. Also, I think it does not do what I intend to do:
CREATE FUNCTION no_show_all (
p_schema IN NUMBER(5),
p_object IN VARCHAR2
)
RETURN
AS
BEGIN
RETURN 'select avg(score) from scott.rating';
END;
/
Based on your previous question and info you posted, here's how I understood the question: if you granted select on the whole table to any user, then it is able to fetch all rows from it. You have to further restrict values.
One option - as we're talking about the function - is to use case in where clause.
Here's an example.
Sample data:
SQL> create table rating as
2 select 1 id, 'sys' name, 4 score from dual union all
3 select 3, 'leo' , 3 from dual union all
4 select 6, 'scott' , 5 from dual union all
5 select 7, 'hr' , 2 from dual;
Table created.
Function:
it accepts username as a parameter (mind letter case! In my example, everything is lowercase. In your, perhaps you'll have to use upper function or something like that)
case says: if par_user is equal to sys, let it fetch all rows. Otherwise, fetch only rows whose name column's value is equal to par_user
return the result
So:
SQL> create or replace function f_rating (par_user in varchar2)
2 return number
3 is
4 retval number;
5 begin
6 select avg(score)
7 into retval
8 from rating
9 where name = case when par_user = 'sys' then name
10 else par_user
11 end;
12 return retval;
13 end;
14 /
Function created.
Let's try it:
SQL> select f_rating('sys') rating_sys,
2 f_rating('hr') rating_hr
3 from dual;
RATING_SYS RATING_HR
---------- ----------
3,5 2
SQL>
I suggest creating a view for each user, like so
create view THE_VIEW as select * from TABLE where NAME = user
Then grant access to the view only.
Now it doesn't matter what kind of query a user tries to perform on your table, she will only get one row back.
Of-course the DBA user can access all the table data.

ORACLE apex - looping through checkbox items using PL/SQL

I have a checkbox on my page P3_Checkbox1 that is populated from the database. How can I loop through all the checked values of my checkbox in PL/SQL code?
I assume that APEX_APPLICATION.G_F01 is used only by sql generated checkboxes and I cannot use it for regular checbox as it would not populate G_Fxx array.
I think I understand now what you're saying.
For example, suppose that the P3_CHECKBOX1 checkbox item allows 3 values (display/return):
Absent: -1
Unknown: 0
Here: 1
I created a button (which will just SUBMIT the page) and two text items which will display checked values: P3_CHECKED_VALUES and P3_CHECKED_VALUES_2.
Then I created a process which fires when the button is pressed. The process looks like this (read the comments as well, please):
begin
-- This is trivial; checked (selected) values are separated by a colon sign,
-- just like in a Shuttle item
:P3_CHECKED_VALUES := :P3_CHECKBOX1;
-- This is what you might be looking for; it uses the APEX_STRING.SPLIT function
-- which splits selected values (the result is ROWS, not a column), and these
-- values can then be used in a join or anywhere else, as if it was result of a
-- subquery. The TABLE function is used as well.
-- LISTAGG is used just to return a single value so that I wouldn't have to worry
-- about TOO-MANY-ROWS error.
with
description (code, descr) as
(select -1, 'Absent' from dual union all
select 0 , 'Unknown' from dual union all
select 1 , 'Here' from dual),
desc_join as
(select d.descr
from description d join (select * from table(apex_string.split(:P3_CHECKED_VALUES, ':'))) s
on d.code = s.column_value
)
select listagg(j.descr, ' / ') within group (order by null)
into :P3_CHECKED_VALUES_2
from desc_join j;
end;
Suppose that the Absent and Unknown values have been checked. The result of that PL/SQL process is:
P3_CHECKED_VALUES = -1:0
P3_CHECKED_VALUES_2 = Absent / Unknown
You can rewrite it as you want; this is just one example.
Keywords:
APEX_STRING.SPLIT
TABLE function
I hope it'll help.
[EDIT: loop through selected values]
Looping through those values isn't difficult; you'd do it as follows (see the cursor FOR loop):
declare
l_dummy number;
begin
for cur_r in (select * From table(apex_string.split(:P3_CHECKED_VALUES, ':')))
loop
select max(1)
into l_dummy
from some_table where some_column = cur_r.column_value;
if l_dummy is null then
-- checked value does not exist
insert into some_table (some_column, ...) valued (cur_r.column_value, ...);
else
-- checked value exists
delete from some_table where ...
end if;
end loop;
end;
However, I'm not sure what you meant by saying that a "particular combination is in the database". Does it mean that you are storing colon-separated values into a column in that table? If so, the above code won't work either because you'd compare, for example
-1 to 0:-1 and then
0 to 0:-1
which won't be true (except in the simplest cases, when checked and stored values have only one value).
Although Apex' "multiple choices" look nice, they can become a nightmare when you have to actually do something with them (like in your case).
Maybe you should first sort checkbox values, sort database values, and then compare those two strings.
It means that LISTAGG might once again become handy, such as
listagg(j.descr, ' / ') within group (order by j.descr)
Database values could be sorted this way:
SQL> with test (col) as
2 (select '1:0' from dual union all
3 select '1:-1:0' from dual
4 ),
5 inter as
6 (select col,
7 regexp_substr(col, '[^:]+', 1, column_value) token
8 from test,
9 table(cast(multiset(select level from dual
10 connect by level <= regexp_count(col, ':') + 1
11 ) as sys.odcinumberlist))
12 )
13 select
14 col source_value,
15 listagg(token, ':') within group (order by token) sorted_value
16 from inter
17 group by col;
SOURCE SORTED_VALUE
------ --------------------
1:-1:0 -1:0:1
1:0 0:1
SQL>
Once you have them both sorted, you can compare them and either INSERT or DELETE a row.

Why is the output only the last value? Oracle loop cursor

I'm trying to output a list of the courses a professor teaches, by receiving the prof's id by parameter to my function, and showing all courses, each separated by a comma. For example, if a Professor teaches Humanities, Science and Math, I want the output to be: 'Humanities, Science, Math'. However, I'm getting just 'Math,'. It only shows the last field that it found that matched with the prof's id.
CREATE OR REPLACE FUNCTION listar_cursos(prof NUMBER) RETURN VARCHAR
IS
CURSOR C1 IS
SELECT subject.name AS name FROM subject
INNER JOIN course_semester
ON subject.id = course_semester.id_subject
WHERE course_semester.id_profesor = prof
ORDER BY subject.name;
test VARCHAR(500);
BEGIN
FOR item IN C1
LOOP
test:= item.name ||',';
END LOOP;
RETURN test;
END;
/
I am aware that listagg exists, however I do not wish to use it.
In your loop, you re-assign to the test variable, instead of appending to it. This is why, at the end of the loop, it will just hold the last value of item.name.
The assignment should instead be something like
test := test || ',' || item.name
Note also that this will leave a comma at the beginning of the string. Instead of returning test, you may want to return ltrim(test, ',').
Note that you don't need to declare a cursor explicitly. The code is easier to read (in my opinion) with an implicit cursor, as shown below. I create sample tables and data to test the function, then I show the function code and how it's used.
create table subject as
select 1 id, 'Humanities' name from dual union all
select 2 , 'Science' from dual union all
select 3 , 'Math' from dual
;
create table course_semester as
select 1 id_subject, 201801 semester, 1002 as id_profesor from dual union all
select 2 , 201702 , 1002 as id_profesor from dual union all
select 3 , 201801 , 1002 as id_profesor from dual
;
CREATE OR REPLACE FUNCTION listar_cursos(prof NUMBER) RETURN VARCHAR IS
test VARCHAR(500);
BEGIN
FOR item IN
(
SELECT subject.name AS name FROM subject
INNER JOIN course_semester
ON subject.id = course_semester.id_subject
WHERE course_semester.id_profesor = prof
ORDER BY subject.name
)
LOOP
test:= test || ',' || item.name;
END LOOP;
RETURN ltrim(test, ',');
END;
/
select listar_cursos(1002) from dual;
LISTAR_CURSOS(1002)
-----------------------
Humanities,Math,Science

Loop through a table in Oracle PL/SQL

I have done SQL queries but have not done any procedure writing that uses loops so I am at a lost here. I'm using Oracle SQL Developer. Can be done in SQL or PL/SQL
I have a table that resemble this:
Person_ID Score Name Game_ID
1 10 jack 1
1 20 jack 2
2 15 carl 1
2 3 carl 3
4 17 steve 1
How can I loop through this table so that I can grab a players total score for all games played. Result would be like this:
Person_ID Score Name
1 30 jack
2 18 carl
4 17 steve
Also extra credit what If i wanted to just grab say games 1 2?
EDIT: Sorry for not being clear but I do need to do this with a loop even though it can be done without it.
Solution after post edition
This procedure list scores for given game_id. If you omit parameter all games will be summed:
create or replace procedure player_scores(i_game_id number default null) as
begin
for o in (select person_id, name, sum(score) score
from games where game_id = nvl(i_game_id, game_id)
group by person_id, name)
loop
dbms_output.put_line(o.person_id||' '||o.name||' '||o.score);
end loop;
end player_scores;
Previous solution:
You don't need procedure for that, just simple query:
select person_id, name, sum(score)
from your_table
where game_id in (1, 2)
group by person_id, name

Resources