For each string, execute a function/procedure - sql

I'd like to loop through a list of strings and execute a function/procedure with each string as the argument.
What's the best alternative to the following generic code (since it's not legal):
set serveroutput on;
begin
FOR r IN ('The', 'Quick', 'brown', 'fox')
LOOP
dbms_output.put_line( r );
END LOOP;
end;
I assume there might be pattern for this.

Just for completeness, a pure PL/SQL solution.
SQL> set serveroutput on
SQL>
SQL> declare
2 my_array sys.dbms_debug_vc2coll
3 := sys.dbms_debug_vc2coll('The', 'Quick', 'brown', 'fox');
4 begin
5 for r in my_array.first..my_array.last
6 loop
7 dbms_output.put_line( my_array(r) );
8 end loop;
9 end;
10 /
The
Quick
brown
fox
PL/SQL procedure successfully completed.
SQL>
This uses the preclared sys.dbms_debug_vc2coll datatype, which has quite a generous definition ...
SQL> desc sys.dbms_debug_vc2coll
sys.dbms_debug_vc2coll TABLE OF VARCHAR2(1000)
SQL>
... so, like Gary says, you may wish to declare your own. Especially if your strings are short and you have lots of them.

DECLARE
-- 1. declare a list type
TYPE STR_LIST_TYPE IS TABLE OF VARCHAR2(15);
-- 2. declare the variable of the list
V_STR_VALUES STR_LIST_TYPE;
-- 3. optional variable to store single values
V_STR_VALUE VARCHAR2(15);
BEGIN
-- 4. initialize the list of values to be iterated in a for-loop
V_STR_VALUES := STR_LIST_TYPE('String 1','String 2');
-- 5. iterating over the values
FOR INDX IN V_STR_VALUES.FIRST..V_STR_VALUES.LAST
LOOP
-- 6. accessing the value itself
V_STR_VALUE := V_STR_VALUES(INDX);
END LOOP;
END;

I generally use my own collection type, but you can use the built-in sys.dbms_debug_vc2coll
select column_value from table(sys.dbms_debug_vc2coll('The', 'Quick', 'brown', 'fox'));
[I incorrectly had column_name not column_value. Thanks for the correction]

The answer here depends on where the strings come from. In a non 'database language' you would probably get the strings into an array somehow, and then loop over the array, as you have illustrated above. The question is, is that list of strings hardcoded, or are you selecting them from a database table?
OMG Ponies solution will work, but it involves a possibly needless select. You may be better using PLSQL table or varrays - as I said, it depends on how you get the strings into your program that you need to process. Here is an example using plsql tables:
declare
type myarray is table of varchar2(255) index by binary_integer;
v_array myarray;
begin
v_array(v_array.count + 1) := 'The';
v_array(v_array.count + 1) := 'quick';
v_array(v_array.count + 1) := 'brown';
v_array(v_array.count + 1) := 'fox';
for i in 1..v_array.count loop
dbms_output.put_line(v_array(i));
end loop;
end;
/

Use:
SELECT package.your_function(x.col)
FROM (SELECT 'The' AS col
FROM DUAL
UNION ALL
SELECT 'Quick'
FROM DUAL
UNION ALL
SELECT 'brown'
FROM DUAL
UNION ALL
SELECT 'fox'
FROM DUAL) x
Oracle 9i+, Using Subquery Factoring (AKA CTE)
WITH list AS (
SELECT 'The' AS col
FROM DUAL
UNION ALL
SELECT 'Quick'
FROM DUAL
UNION ALL
SELECT 'brown'
FROM DUAL
UNION ALL
SELECT 'fox'
FROM DUAL)
SELECT package.your_function(x.col)
FROM list x

set serveroutput on;
begin
dbms_output.put_line('The');
dbms_output.put_line('Quick');
dbms_output.put_line('brown');
dbms_output.put_line('fox');
end;

Related

Select statement in PL/SQL

Does anyone know how to use their tables and columns from pre-existing databases in a pl/sql command. I am using something from Oracles webpage to use as an example.
SQL> declare
2 v_line varchar2(40):= '&v_string';
3 begin
4 v_line := 'Hello '||v_line;
5 dbms_output.put_line (v_line);
6 end;
7 /
old 2: v_line varchar2(40):= '&v_string';
New 2: v_line varchar2(40):= 'Thomas';
Hello Thomas
PL/SQL procedure successfully completed.
I am wanting to be able to manipulate the data from above. Lets say I have a table called name_s and I have columns called n_first and n_last and I want to use those columns to output 'Hello Thomas' but using those tables.
I am new to pl.sql and trying to learn the syntax.
I wasn't sure if you could,
DECLARE
n_first
n_last
BEGIN
SELECT * FROM name_s
n_fisrt := 'Thomas'||n_first;
dbms_output.put_line(n_first)
END;
/
Desired Output: 'Thomas'
I understand that this is not syntactically correct but I am just trying to pick up on how you can use data from your existing tables to get this sort of desired output
EDIT: The above table is hypothetical and only contains two columns for this example. Now using real tables and data.
SET SERVEROUTPUT ON;
begin
for cur_r in (select loc_t from loc_t_matrix where store_s in(1,2,3,4,5); loop
dbms_output.put_line('Hello store' || cur_r.loc_traits );
end loop;
end;
/
The above does not compile
Do you need something like this
declare
v_authName author.author_last_name%type;
begin
select
author_last_name into v_authName
from
author
where
author_key = 'A103';
dbms_output.put_line('Name: '||v_authName);
end;
/
Name: weaton
http://www.dba-oracle.com/t_pl_sql_plsql_select_into_clause.htm
As tables usually contain more than one row, I presume that NAME_S also contains several rows. In that case, the simplest option to display those names is a cursor FOR loop (Google for it). For example:
begin
for cur_r in (select n_first, n_last from name_s) loop
dbms_output.put_line('Hello ' || cur_r.n_first ||' '||cur_r.n_last);
end loop;
end;
/
As you can see, you don't need local variables in that case.
If you used your SELECT ... INTO (as suggested by mCeviker), that would work unless it raises TOO-MANY-ROWS (as you can't put all rows' names into a single variable).

compiling procedure in oracle, md5 encryption [duplicate]

I would like to declare and display a variable in Oracle.
In T-SQL I would do something like this
DECLARE #A VARCHAR(10) --Declares #A
SELECT #A = '12' --Assigns #A
SELECT #A --Displays #A
How can I do this in Oracle.
If you're talking about PL/SQL, you should put it in an anonymous block.
DECLARE
v_text VARCHAR2(10); -- declare
BEGIN
v_text := 'Hello'; --assign
dbms_output.Put_line(v_text); --display
END;
If using sqlplus you can define a variable thus:
define <varname>=<varvalue>
And you can display the value by:
define <varname>
And then use it in a query as, for example:
select *
from tab1
where col1 = '&varname';
If you are using pl/sql then the following code should work :
set server output on -- to retrieve and display a buffer
DECLARE
v_text VARCHAR2(10); -- declare
BEGIN
v_text := 'Hello'; --assign
dbms_output.Put_line(v_text); --display
END;
/
-- this must be use to execute pl/sql script
Make sure that, server output is on otherwise output will not be display;
sql> set serveroutput on;
declare
n number(10):=1;
begin
while n<=10
loop
dbms_output.put_line(n);
n:=n+1;
end loop;
end;
/
Outout:
1
2
3
4
5
6
7
8
9
10
Did you recently switch from MySQL and are now longing for the logical equivalents of its more simple commands in Oracle? Because that is the case for me and I had the very same question. This code will give you a quick and dirty print which I think is what you're looking for:
Variable n number
begin
:n := 1;
end;
print n
The middle section is a PL/SQL bit that binds the variable. The output from print n is in column form, and will not just give the value of n, I'm afraid. When I ran it in Toad 11 it returned like this
n
---------
1
I hope that helps

Oracle arrays "contains" function

I want to store a list of primary keys to other records in the same table. I then want to be able to perform a select that resembles something like:
SELECT * FROM mytable WHERE myarraycol CONTAINS '123'
I've seen that Oracle has an array data type. However it looks like the EXISTS function only verifies if the element exists at the specified index. Is there a way to verify that a given variable is in the array data type column in a single SQL query?
As an alternative to using the array data type I tried storing the PKs as a comma-delimited string like "123,324,543,23432." My query then looked like:
SELECT * FROM mytable WHERE mystringcol LIKE '%123%'
if I wanted all records with the PK '123' in it. The problem with this (among many others) is that if another record has a value "432,9912399,432" this record will show up because of the "123" in the "9912399."
One way I could solve this problem using "LIKE" and string could be to have my where clause be:
WHERE mystringcol LIKE '%,123,% OR mystringcol LIKE '%123, OR mystringcol LIKE '%,123
to test for "123" being in the middle, start or end of the entire string, but that starts to get ugly and I'd rather not do it this way.
Has anyone done something like this before and can point me in the right direction?
In theory Oracle has MEMBER OF function for collection but regarding 3rd normal form you idea looks strange.
SQL> CREATE OR REPLACE PROCEDURE member_of_example AS
2 TYPE nestedTableType IS TABLE OF VARCHAR2(10);
3 myTable1 nestedTableType;
4 result BOOLEAN;
5 BEGIN
6 myTable1 := nestedTableType('F', 'G', 'S');
7 result := 'George' MEMBER OF myTable1;
8 IF result THEN
9 DBMS_OUTPUT.PUT_LINE('''George'' is a member');
10 END IF;
11 END member_of_example;
12 /
An example for function to get the plsql table from comma-separated stuff:
CREATE OR REPLACE function Str2NmbTbl(p_str varchar2) return number_table is
l_col number_table := number_table();
l_pos number;
l_cnt number := 1;
l_num number;
begin
l_pos := instr(p_str, '[', l_cnt);
while l_pos > 0 loop
l_num := to_number(substr(p_str, l_pos + 1, instr(p_str, ']', 1, l_cnt) - l_pos - 1));
l_col.extend;
l_col(l_cnt) := l_num;
l_cnt := l_cnt + 1;
l_pos := instr(p_str, '[', l_pos + 1);
end loop;
return l_col;
end;
/
with s as
(select 1 id, '[11412][21][3131][3333]' str from dual union all
select 2 id, '[64376][553]' str from dual union all
select 3 id, '[5943][74621][19][3333][0]' str from dual union all
select 4 id, '[21593][22321][43][094]' str from dual --union all
)
select id, Str2NmbTbl(str) collctn
from s;

How do you specify IN clause in a dynamic query using a variable?

In PL/SQL, you can specify the values for the IN operator using concatenation:
v_sql := 'select field1
from table1
where field2 in (' || v_list || ')';
Is it possible to do the same using a variable?
v_sql := 'select field1
from table1
where field2 in (:v_list)';
If so, how?
EDIT: With reference to Marcin's answer, how do I select from the resultant table?
declare
cursor c_get_csv_as_tables is
select in_list(food_list) food_list
from emp_food
where emp_type = 'PERM';
cursor c_get_food_list (v_food_table varchar2Table)is
select *
from v_food_table;
begin
for i in c_get_csv_as_tables loop
for j in c_get_food_list(i.food_list) loop
dbms_output.put_line(j.element);
end loop;
end loop;
end;
I get the following error:
ORA-06550: line 10, column 6:
PL/SQL: ORA-00942: table or view does not exist
ORA-06550: line 9, column 1:
PL/SQL: SQL Statement ignored
ORA-06550: line 15, column 34:
PLS-00364: loop index variable 'J' use is invalid
ORA-06550: line 15, column 13:
PL/SQL: Statement ignored
Like in #Sathya link, you can bind the varray (I took #Codo example):
CREATE OR REPLACE TYPE str_tab_type IS VARRAY(10) OF VARCHAR2(200);
/
DECLARE
l_str_tab str_tab_type;
l_count NUMBER;
v_sql varchar2(3000);
BEGIN
l_str_tab := str_tab_type();
l_str_tab.extend(2);
l_str_tab(1) := 'TABLE';
l_str_tab(2) := 'INDEX';
v_sql := 'SELECT COUNT(*) FROM all_objects WHERE object_type IN (SELECT COLUMN_VALUE FROM TABLE(:v_list))';
execute immediate v_sql into l_count using l_str_tab;
dbms_output.put_line(l_count);
END;
/
UPDATE: the first command can be replaced with:
CREATE OR REPLACE TYPE str_tab_type IS TABLE OF VARCHAR2(200);
/
then call:
l_str_tab.extend(1);
when ever you add a value
Unfortunately you cannot bind a list like this, however you can use a table function. Read this
Here's an example of usage based on your code:
declare
cursor c_get_csv_as_tables is
select in_list(food_list) food_list
from emp_food
where emp_type = 'PERM';
cursor c_get_food_list (v_food_table varchar2Table)is
select column_value food
from TABLE(v_food_table);
begin
for i in c_get_csv_as_tables loop
for j in c_get_food_list(i.food_list) loop
dbms_output.put_line(j.food);
end loop;
end loop;
end;
I used here a column_value pseudocolumn
Bind variable can be used in Oracle SQL query with "in" clause.
Works in 10g; I don't know about other versions.
Bind variable is varchar up to 4000 characters.
Example: Bind variable containing comma-separated list of values, e.g.
:bindvar = 1,2,3,4,5
select * from mytable
where myfield in
(
SELECT regexp_substr(:bindvar,'[^,]+', 1, level) items
FROM dual
CONNECT BY regexp_substr(:bindvar, '[^,]+', 1, level) is not null
);
As per #Marcin's answer you can't do this, however, there's a fair bit to add to that, as your query should actually work, i.e. run.
Simply put, you cannot use a bind variable for a table or column. Not only that, bind variables they are assumed to be a character, so if you want a number you have to use to_number(:b1) etc.
This is where your query falls down. As you're passing in a string Oracle assumes that your entire list is a single string. Thus you are effectively running:
select field1
from table1
where field2 = v_list
There is no reason why you can't do this a different way though. I'm going to assume you're dynamically creating v_list, which means that all you need to do is create this list differently. A series of or conditions is, purportedly :-), no different to using an in.
By purportedly, I mean never rely on something that's untested. Although Tom does say in the link that there may be performance constraints there's no guarantee that it wasn't quicker than using in to begin with. The best thing to do is to run the trace on your query and his and see what difference there is, if any.
SQL> set serveroutput on
SQL>
SQL> declare
2
3 l_string varchar2(32767);
4 l_count number;
5
6 begin
7
8 for xx in ( select rownum as rnum, a.*
9 from user_tables a
10 where rownum < 20 ) loop
11
12 if xx.rnum = 1 then
13 l_string := 'table_name = ''' || xx.table_name || '''';
14 else
15 l_string := l_string || ' or table_name = ''' || xx.table_name || '
''';
16 end if;
17
18 end loop;
19
20 execute immediate 'select count(*)
21 from user_tables
22 where ' || l_string
23 into l_count
24 ;
25
26 dbms_output.put_line('count is ' || l_count);
27
28 end;
29 /
count is 19
PL/SQL procedure successfully completed.

PLSQL - How to retrieve values into a collection given an array of values?

I have a procedure that accepts an array of folder IDs and needs to return a list of document IDs. Folders are associated to documents in a one-to-many relationship--there are many documents for each folder. Specifically, there is a documents table which has a parent_folderid fk to the folders table.
This is what I have:
PROCEDURE get_folder_documents_ (
paa_folderids_i IN gtyp_folderids_table
) IS
lnt_temp_docids &&MATTER_SCHEMA..docid_tab := &&MATTER_SCHEMA..docid_tab();
lv_current_table_size NUMBER := gnt_documentids.COUNT;
BEGIN
FOR i IN paa_folderids_i.FIRST .. paa_folderids_i.LAST
LOOP
SELECT documentid
BULK COLLECT INTO lnt_temp_docids
FROM t$documents
WHERE parent_folderid = paa_folderids_i(i);
FOR j IN 1 .. lnt_temp_docids.COUNT
LOOP
lv_current_table_size := lv_current_table_size + 1;
gnt_documentids.EXTEND(1);
gnt_documentids(lv_current_table_size) := lnt_temp_docids(j);
END LOOP;
END LOOP;
END get_folder_documents_;
Is there a better way?
If gtyp_folderids_table is declared as a SQL type (as opposed to a PL/SQL type) you could use it in a SQL statement via a table() function like this:
SELECT documentid
BULK COLLECT INTO gnt_documentids
FROM t$documents
WHERE parent_folderid in ( select * from table( paa_folderids_i));
edit
If you want a PL/SQL answer, in 10g there is a more effective way - or at least an approach which requires less typing ;).
Oracle introduced some neat-o set operators which we can use with collections. The following example uses MULTISET UNION to munge several collections into one...
SQL> set serveroutput on size unlimited
SQL>
SQL> declare
2 v1 sys.dbms_debug_vc2coll
3 := sys.dbms_debug_vc2coll('SAM I AM', 'FOX IN SOCKS');
4 v2 sys.dbms_debug_vc2coll
5 := sys.dbms_debug_vc2coll('MR KNOX', 'GRINCH');
6 v3 sys.dbms_debug_vc2coll
7 := sys.dbms_debug_vc2coll('LORAX', 'MAISIE');
8 v_all sys.dbms_debug_vc2coll := sys.dbms_debug_vc2coll();
9 begin
10 dbms_output.put_line('V_ALL has '|| v_all.count() ||' elements');
11 v_all := v1 multiset union v2;
12 dbms_output.put_line('V_ALL has '|| v_all.count() ||' elements');
13 v_all := v_all multiset union v3;
14 dbms_output.put_line('V_ALL has '|| v_all.count() ||' elements');
15 end;
16 /
V_ALL has 0 elements
V_ALL has 4 elements
V_ALL has 6 elements
PL/SQL procedure successfully completed.
SQL>
Find out more about collections in 10g.