Value not getting pass from variable in string in PLSQL - oracle

I have a For loop in plsql and a procedure.
Inside the procedure I have IF block :
IF c.payer='GCC' and c.dataset='SAME' then
EMPID:=REGEXP_SUBSTR(c.hitablename,'[^_]+',1,4);
vbquery:='
Insert /*parallel(unk_codes)*/ into unk_codes(act_nbr, hitablename, groupid, payer)
select /* parallel(a,4) */ distinct '''||EMPID||'0100'' as unknown_codes,'''||c.hitablename||''' as hitablename,
REGEXP_SUBSTR('''||c.hitablename||''',''[^_]+'',1,4) as groupid,'''||c.payer||''' as payer from HI0777777.'||c.hitablename||' a
where '''||EMPID||'0100'' in (select NVL(b.ACC_NUM,''NOT'') from group_abc b where
'''||EMPID||'0100'' = b.ACC_NUM
AND
REGEXP_SUBSTR('''||c.hitablename||''',''[^_]+'',1,4)=b.group_id)';
BEGIN
EXECUTE IMMEDIATE vbquery;
EXCEPTION WHEN OTHERS THEN
Dbms_Output.put_line('GCC SAME block'||SQLERRM||vbquery||c.ai_table);
END;
END IF;
I want to pass EMPID inside the vbquery string but I am getting ORA-00936: missing expression error.
I tried to debugged and see what is the problem and I found EMPID value not getting replaced inside vbquery.
My vbquery is being printed as:
ORA-00936: missing expression
Insert /*parallel(unk_codes)*/ into unk_codes(act_nbr, hitablename, groupid, payer)
select /* parallel(a,4) */ distinct unknown_codes,'HI_SAME_GCC_MPD' as hitablename,
REGEXP_SUBSTR('HI_SAME_GCC_MPD','[^_]+',1,4) as groupid,'GCC' as payer from HI0777777.HI_SAME_GCC_MPD a
where not exists (select 1 from group_abc b where b.acc_num= and
REGEXP_SUBSTR('HI_SAME_GCC_MPD','[^_]+',1,4)=b.group_id)
My EMPID may be 'ABCD'.
So my expected output was:
Insert /*parallel(unk_codes)*/ into unk_codes(act_nbr, hitablename, groupid, payer)
select /* parallel(a,4) */ distinct 'ABCD0100' unknown_codes,'HI_SAME_GCC_MPD' as hitablename,
REGEXP_SUBSTR('HI_SAME_GCC_MPD','[^_]+',1,4) as groupid,'GCC' as payer from HI0777777.HI_SAME_GCC_MPD a
where not exists (select 1 from group_abc b where b.acc_num='ABCD0100' and
REGEXP_SUBSTR('HI_SAME_GCC_MPD','[^_]+',1,4)=b.group_id)
Why is that EMPID value not getting passed in string?

I think you're looking in a wrong place in code. In the plsql-example I see the following part in the query you're trying to build
... where '''||EMPID||'0100'' in (select NVL(b.ACC_NUM,''NOT'') from group_abc b where...
but in the debug result of your query there is a part which is completely different and shouldn't be there at all
... where not exists (select 1 from group_abc b where b.acc_num= and ...
I'm pretty sure there is another branch in the "if" or something similar to that

You didn't say what values are involved so I tried to guess; also, I don't have your tables so I skipped a loop and used local variables instead.
It seems that either
c.hitablename doesn't look as you think it does, or
REGEXP_SUBSTR (which is used to set EMPID's value) doesn't work as you think it does as it returns an empty string, not e.g. ABCD (as you expected)
Because, if hitablename is something like in my example (X_Y_Z_ABCD), then the final insert statement looks OK:
SQL> set serveroutput on
SQL> DECLARE
2 c_hitablename VARCHAR2 (20) := 'X_Y_Z_ABCD'; --> this isn't what you think it is ...
3 c_payer VARCHAR2 (20) := 'xxxx';
4 EMPID VARCHAR2 (20);
5 vbquery VARCHAR2 (1000);
6 BEGIN
7 EMPID := --> ... or this doesn't do what you think it does
8 REGEXP_SUBSTR (c_hitablename,
9 '[^_]+',
10 1,
11 4);
12 vbquery :=
13 '
14 Insert /*parallel(unk_codes)*/ into unk_codes(act_nbr, hitablename, groupid, payer)
15 select /* parallel(a,4) */ distinct '''
16 || EMPID
17 || '0100'' as unknown_codes,'''
18 || c_hitablename
19 || ''' as hitablename,
20 REGEXP_SUBSTR('''
21 || c_hitablename
22 || ''',''[^_]+'',1,4) as groupid,'''
23 || c_payer
24 || ''' as payer from HI0777777.'
25 || c_hitablename
26 || ' a
27 where '''
28 || EMPID
29 || '0100'' in (select NVL(b.ACC_NUM,''NOT'') from group_abc b where
30 '''
31 || EMPID
32 || '0100'' = b.ACC_NUM
33 AND
34 REGEXP_SUBSTR('''
35 || c_hitablename
36 || ''',''[^_]+'',1,4)=b.group_id)';
37
38 DBMS_OUTPUT.put_line (vbquery);
39 END;
40 /
Insert /*parallel(unk_codes)*/ into unk_codes(act_nbr, hitablename, groupid,
payer)
select /* parallel(a,4) */ distinct 'ABCD0100' as
unknown_codes,'X_Y_Z_ABCD' as
hitablename,
REGEXP_SUBSTR('X_Y_Z_ABCD','[^_]+',1,4) as groupid,'xxxx' as payer
from HI0777777.X_Y_Z_ABCD a
where 'ABCD0100' in (select NVL(b.ACC_NUM,'NOT')
from group_abc b where
'ABCD0100' = b.ACC_NUM
AND
REGEXP_SUBSTR('X_Y_Z_ABCD','[^_]+',1,4)=b.group_id)
PL/SQL procedure successfully completed.
SQL>
Formatted:
INSERT /*parallel(unk_codes)*/
INTO unk_codes (act_nbr,
hitablename,
groupid,
payer)
SELECT /* parallel(a,4) */
DISTINCT 'ABCD0100' AS unknown_codes,
'X_Y_Z_ABCD' AS hitablename,
REGEXP_SUBSTR ('X_Y_Z_ABCD',
'[^_]+',
1,
4) AS groupid,
'xxxx' AS payer
FROM HI0777777.X_Y_Z_ABCD a
WHERE 'ABCD0100' IN (SELECT NVL (b.ACC_NUM, 'NOT')
FROM group_abc b
WHERE 'ABCD0100' = b.ACC_NUM
AND REGEXP_SUBSTR ('X_Y_Z_ABCD',
'[^_]+',
1,
4) = b.GROUP_ID)
Finally: what do you think those /*parallel(unk_codes)*/ thingies do? I bet you thought query will run faster if you executed it with that hint, didn't you? Bad news for you - what you put in here is just a comment, not a hint. Oracle suggests us (developers) not to use hints if we don't know what they do and how to properly use them. You, apparently, don't as you made the same mistake twice (so it wasn't a typo). Therefore, you can remove those comments out of your code (or use a hint if you really want to, but then use proper syntax).

Related

PL/SQL Oracle. Best way to implement an IS_CONTAINED operator

I am newbie so that maybe this question has been made one or two million times, but it is not findable / searchable in the knowledge database.
In Oracle PL/SQL, it is normal to query as follows:
select a,b,c
from table_foo
where c in (select k from table(array_bar));
But I need all the opposite of that. I need a kind of "IS_CONTAINED" operator, like this:
select a,b,c
from table_foo
where AT_LEAST_ONE_OF_THE_ITEMS_IN (select k from table(array_bar)) IS_CONTAINED_IN c;
I have my own ideas to implement it using a function with a loop. But maybe some genius has found a simple way to do it without a function. This is to say, maybe the operator IS_CONTAINED is already invented by Oracle and I haven't found it out.
Sorry if this question is repeated. I promise I have searched for it in the knowledge base. But it seems that nobody in the space-time of this Universe has never needed the super-obvious operator IS_CONTAINED.
SOLUTION:
Thanks to everybody for the suggestions. In the end, I had to use some functions, but I think I got a good solution. The situation is: I have a table of centers. Each center can be in one or more cities, this is to say, it's a 1 to N relationship. But this relationship is done using a single table. This table contains some fields. One of these fields, named 'cities_list', contains all related cities separated by semicolons. It's like this:
CODE DESCRIPTION CITIES_LIST
---- ----------- -----------
0001 Desc 0001 London; Berlin; NY; SF
0002 Desc 0002 Paris; Madrid; Rome
0003 Desc 0003 Berlin; Paris; London
0004 Desc 0004 Madrid;NY;Tokyo
0005 Repe 0005 Rome;Rome;Rome;LA;LA;LA;
0006 One 0006 NY
0007 Desc 0007 Sydney;Tokyo;Madrid
0008 Desc 0008 LA;SF;NY
0009 Desc 0009 Seoul;Beijing;
0010 Error0010 Beijing;;;;OZ;
0011 None 0011 (null)
0012 All 0012 London;Paris;Berlin;Madrid;Rome;NY;SF;LA;Seoul;Beijing;Tokyo;Sydney
Possible cities are: London; Paris; Berlin; Madrid; Rome; NY; SF; LA; Seoul; Beijing; Tokyo; Sydney.
In order to filter records of that table, the user can select, through a combo, one or more of those cities. Selected cities are passed to the PL/SQL query as a string (varchar) of cities separated by a hash sign (#). For instance 'London#Paris#Sydney'.
The PL/SQL has to select the records that have at least one city in common between the field 'cities_list' and the string of cities passed from the combo. First, I put here the PL/SQL code and I will explain it later on:
--1.SELECT AND EXECUTE THIS:
SET SERVEROUTPUT ON;
--2.SELECT AND EXECUTE THIS:
DROP TABLE table_centers; CREATE GLOBAL TEMPORARY TABLE table_centers (code VARCHAR2(10), description VARCHAR2(100), cities_list VARCHAR2(1000));
--3.SELECT AND EXECUTE THIS:
CREATE OR REPLACE TYPE table_TYPE IS TABLE OF VARCHAR2(250);
--4.SELECT AND EXECUTE THIS:
CREATE OR REPLACE FUNCTION VARCHAR_TO_TABLE (input_varchar VARCHAR2, separator VARCHAR2 DEFAULT ';')
RETURN table_TYPE
IS
--VARS
output_table table_TYPE := table_TYPE();
BEGIN
--For better performance, input_varchar is splitted without blanks into output_table using the regular expression [^;]+
SELECT
--The Keyword 'level' in statement 'regexp_substr' refers to a pseudocolumn in Oracle
TRIM(regexp_substr(input_varchar,'[^' || separator || ']+', 1, level))
BULK COLLECT INTO
output_table
FROM DUAL
CONNECT BY
regexp_substr(input_varchar,'[^' || separator || ']+', 1, level) IS NOT NULL;
--Now we have all chunks into the table output_table
RETURN output_table;
END VARCHAR_TO_TABLE;
--5.SELECT AND EXECUTE THIS:
CREATE OR REPLACE FUNCTION INTERSECT_TABLES(input_A VARCHAR2 , separator_A VARCHAR2 , input_B VARCHAR2 , separator_B VARCHAR2)
RETURN NUMBER
IS
--VARS
A table_TYPE;
B table_TYPE;
result BOOLEAN;
BEGIN
--Splits input_A and input_B into tables and checks if there is overlapping
A := VARCHAR_TO_TABLE(input_A, separator_A);
B := VARCHAR_TO_TABLE(input_B, separator_B);
--If intersection is not empty result is TRUE
result := A multiset intersect B is not empty;
-- Returns 1 if intersection is not empty, returns 0 otherwise (Note that functions called from a SQL query cannot take any BOOLEAN parameters)
IF result = TRUE THEN RETURN 1; ELSE RETURN 0; END IF;
END INTERSECT_TABLES;
--6.SELECT AND EXECUTE THIS:
CREATE OR REPLACE PROCEDURE GET_CENTERS (cities_input VARCHAR2 , separator_input VARCHAR2 , out_Cursor OUT sys_refcursor)
AS
BEGIN
OPEN out_Cursor FOR
SELECT tc.code, tc.description, tc.cities_list
FROM table_centers tc
--Has current record some city in common with cities_input? If yes, select current record
WHERE INTERSECT_TABLES(cities_input , separator_input , tc.cities_list , ';') = 1;
END GET_CENTERS;
--7.SELECT AND EXECUTE THIS:
BEGIN
DELETE FROM table_centers; COMMIT;
INSERT ALL
--We'll use following cities: London Paris Berlin Madrid Rome NY SF LA Seoul Beijing Tokyo Sydney
INTO table_centers (code,description,cities_list) VALUES ('0001', 'Desc 0001', 'London; Berlin; NY; SF')
INTO table_centers (code,description,cities_list) VALUES ('0002', 'Desc 0002', 'Paris; Madrid; Rome')
INTO table_centers (code,description,cities_list) VALUES ('0003', 'Desc 0003', 'Berlin; Paris; London')
INTO table_centers (code,description,cities_list) VALUES ('0004', 'Desc 0004', 'Madrid;NY;Tokyo')
INTO table_centers (code,description,cities_list) VALUES ('0005', 'Repe 0005', 'Rome;Rome;Rome;LA;LA;LA;')
INTO table_centers (code,description,cities_list) VALUES ('0006', 'One 0006', 'NY')
INTO table_centers (code,description,cities_list) VALUES ('0007', 'Desc 0007', 'Sydney;Tokyo;Madrid')
INTO table_centers (code,description,cities_list) VALUES ('0008', 'Desc 0008', 'LA;SF;NY')
INTO table_centers (code,description,cities_list) VALUES ('0009', 'Desc 0009', 'Seoul;Beijing;')
INTO table_centers (code,description,cities_list) VALUES ('0010', 'Error0010', 'Beijing;;;;OZ;')
INTO table_centers (code,description,cities_list) VALUES ('0011', 'None 0011', '')
INTO table_centers (code,description,cities_list) VALUES ('0012', 'All 0012', 'London;Paris;Berlin;Madrid;Rome;NY;SF;LA;Seoul;Beijing;Tokyo;Sydney')
SELECT 1 FROM DUAL;
END;
--8.SELECT AND EXECUTE THIS:
SELECT * FROM table_centers;
I have used 'Oracle SQL Developer'. You can select the sentences one by one and execute them with the F9 key. You can also create a Package.
If someone wants to test that code, you can also select and execute with F9 the following query:
--9.SELECT AND EXECUTE THIS:
DECLARE
--VARS
out_Cursor sys_refcursor;
cities_array table_TYPE;
citiesA varchar(1000) := 'London#Paris#Berlin#Madrid#Rome#NY#SF#LA# Seoul # Beijing # Tokyo # Sydney ';
citiesB varchar(1000) := 'London;Paris;Berlin;Madrid;Rome;NY;SF;LA; Seoul ; Beijing ; Tokyo ; Sydney ';
Rcode table_centers.code%TYPE;
Rdescription table_centers.description%TYPE;
Rcities_list table_centers.cities_list%TYPE;
CR char := CHR(13);
TAB char := CHR(9);
BEGIN
--TEST 1
dbms_output.put_line('TEST 1: ' || CR);
cities_array := table_TYPE();
cities_array := VARCHAR_TO_TABLE(citiesA, '#');
--Now we have all cities in the array cities_array
FOR elem in 1 .. cities_array.count LOOP
dbms_output.put_line(TAB || elem || ':' || cities_array(elem) || '.');
END LOOP;
--TEST 2
dbms_output.put_line('TEST 2: ' || CR);
cities_array := table_TYPE();
cities_array := VARCHAR_TO_TABLE(citiesB, ';');
--Now we have all cities in the array cities_array
FOR elem in 1 .. cities_array.count LOOP
dbms_output.put_line(TAB || elem || ':' || cities_array(elem) || '.');
END LOOP;
--TEST 3
dbms_output.put_line('TEST 3: ' || CR);
GET_CENTERS(citiesA, '#', out_Cursor);
fetch out_Cursor into Rcode,Rdescription,Rcities_list;
WHILE out_Cursor%FOUND LOOP
dbms_output.put_line(TAB || 'CITIES:' || Rcities_list || '.');
fetch out_Cursor into Rcode,Rdescription,Rcities_list;
END LOOP;
close out_Cursor;
--TEST 4
dbms_output.put_line('TEST 4: ' || CR);
GET_CENTERS('London#Paris#Sydney', '#', out_Cursor);
fetch out_Cursor into Rcode,Rdescription,Rcities_list;
WHILE out_Cursor%FOUND LOOP
dbms_output.put_line(TAB || 'CITIES:' || Rcities_list || '.');
fetch out_Cursor into Rcode,Rdescription,Rcities_list;
END LOOP;
close out_Cursor;
--TEST 5
dbms_output.put_line('TEST 5: ' || CR);
GET_CENTERS('Madrid', '#', out_Cursor);
fetch out_Cursor into Rcode,Rdescription,Rcities_list;
WHILE out_Cursor%FOUND LOOP
dbms_output.put_line(TAB || 'CITIES:' || Rcities_list || '.');
fetch out_Cursor into Rcode,Rdescription,Rcities_list;
END LOOP;
close out_Cursor;
--TEST 6
dbms_output.put_line('TEST 6: ' || CR);
GET_CENTERS('Gotham City', '#', out_Cursor);
fetch out_Cursor into Rcode,Rdescription,Rcities_list;
WHILE out_Cursor%FOUND LOOP
dbms_output.put_line(TAB || 'CITIES:' || Rcities_list || '.');
fetch out_Cursor into Rcode,Rdescription,Rcities_list;
END LOOP;
close out_Cursor;
--TEST 7
dbms_output.put_line('TEST 7: ' || CR);
GET_CENTERS('', '#', out_Cursor);
fetch out_Cursor into Rcode,Rdescription,Rcities_list;
WHILE out_Cursor%FOUND LOOP
dbms_output.put_line(TAB || 'CITIES:' || Rcities_list || '.');
fetch out_Cursor into Rcode,Rdescription,Rcities_list;
END LOOP;
close out_Cursor;
END;
You can modify TEST 7 and put your own values in the first parameter of the function 'GET_CENTERS'. I have executed this query and I have got these results:
TEST 1:
1:London.
2:Paris.
3:Berlin.
4:Madrid.
5:Rome.
6:NY.
7:SF.
8:LA.
9:Seoul.
10:Beijing.
11:Tokyo.
12:Sydney.
TEST 2:
1:London.
2:Paris.
3:Berlin.
4:Madrid.
5:Rome.
6:NY.
7:SF.
8:LA.
9:Seoul.
10:Beijing.
11:Tokyo.
12:Sydney.
TEST 3:
CITIES:London; Berlin; NY; SF.
CITIES:Paris; Madrid; Rome.
CITIES:Berlin; Paris; London.
CITIES:Madrid;NY;Tokyo.
CITIES:Rome;Rome;Rome;LA;LA;LA;.
CITIES:NY.
CITIES:Sydney;Tokyo;Madrid.
CITIES:LA;SF;NY.
CITIES:Seoul;Beijing;.
CITIES:Beijing;;;;OZ;.
CITIES:London;Paris;Berlin;Madrid;Rome;NY;SF;LA;Seoul;Beijing;Tokyo;Sydney.
TEST 4:
CITIES:London; Berlin; NY; SF.
CITIES:Paris; Madrid; Rome.
CITIES:Berlin; Paris; London.
CITIES:Sydney;Tokyo;Madrid.
CITIES:London;Paris;Berlin;Madrid;Rome;NY;SF;LA;Seoul;Beijing;Tokyo;Sydney.
TEST 5:
CITIES:Paris; Madrid; Rome.
CITIES:Madrid;NY;Tokyo.
CITIES:Sydney;Tokyo;Madrid.
CITIES:London;Paris;Berlin;Madrid;Rome;NY;SF;LA;Seoul;Beijing;Tokyo;Sydney.
TEST 6:
TEST 7:
CITIES:.
The nub of the issue is the function 'INTERSECT_TABLES'. This function uses the sentence " result := A multiset intersect B is not empty; ". A and B are variables of type 'TABLE'. The operator '... multiset intersect ... is not empty' returns TRUE if tables A and B have at least one item (row) with the same value (text or number), regardless of its order or position in each table.
EXPLANATION:
I have created a temporary table named 'table_centers' and I have filled it in with some data. In order to query this table, I have created following functions:
The function 'VARCHAR_TO_TABLE' converts a string (varchar) into a 'table' type variable. You must pass a separator character as a parameter, so that each chunk of the string separated by that character will be one item (=row) of the resulting table. This way, I can use the same function regardless whether cities are separated by a semicolon (;) or by a hash (#). This function uses 'regexp_substr' and BULK COLLECT instead of a LOOP for better performance. The Keyword 'level' in statement 'regexp_substr' refers to a pseudocolumn in Oracle. See Is there a function to split a string in PL/SQL?.
In order to execute the final query to 'table_centers', I have implemented the function 'GET_CENTERS'. It has only one SELECT that selects the records of 'table_centers' that have in their field 'cities_list' at least one city in common with the string 'cities_input', which is passed as a parameter. Both strings are compared by the function 'INTERSECT_TABLES', being these strings previously splitted into tables through the function 'VARCHAR_TO_TABLE'.
The function 'INTERSECT_TABLES' is used in the clause 'WHERE' because the filtering must be done through this function. This is because a 'table' type can not be used inside a SQL query. Otherwise, you'll get an error "collection types can not be used inside a SQL statement". Therefore, using this function in the WHERE clause is mandatory. Also, boolean types can not be used, therefore, the function 'INTERSECT_TABLES' returns the numbers 0 or 1, not FALSE or TRUE.
Perhaps you are looking for multiset conditions. For example:
create or replace type number_tt as table of number;
select 'Yes' as member
from dual
where 1 member of number_tt(1,2,3);
select 'Yes' as subset
from dual
where number_tt(2,3) submultiset of number_tt(1,2,3,4);
Taking William Robertson's answer a step further, to check if at least one member of a set is a member in another set:
create or replace type number_tt as table of number;
/
with t1(id, c) as (
select 1, number_tt(1,2,3) from dual union all
select 2, number_tt(4,5,6) from dual union all
select 3, number_tt(7,8,9) from dual
)
select id, 'Yes' Intersects
from t1
where c multiset intersect number_tt(1,2,3,8) is not empty;
Yields the following results:
ID INTESECTS
1 Yes
3 Yes
Updating based on the sample data provided. Note: converting from string data to sets is left as an exercise for the student ;)
create or replace type varchar30_tt as table of varchar2(30);
/
with t1(id, c) as (
select 1, varchar30_tt('Rome','NY','London') c from dual union all
select 2, varchar30_tt('LA','SF','Torronto') c from dual union all
select 3, varchar30_tt('Paris','London','Rome') c from dual
)
select id
, 'Yes' Intesects
from t1
where c multiset intersect varchar30_tt('SF','LA','NY') is not empty;
You need OR cond -
with array_bar as (select k from table(array_bar))
select a,b,c
from table_foo
where c in array_bar
or b in array_bar
or a in array_bar;

ORACLE use custom tableObject into a function

I want insert a cursor into my custom tableObject, but it is not always found.
My RECORD:
create or replace type "RECORDRANKING" as object
(
-- Attributes
COL1 NUMBER,
COL2 VARCHAR(50),
COL3 NUMBER
-- Member functions and procedures
-- member procedure <ProcedureName>(<Parameter> <Datatype>)
)
This is object table:
CREATE OR REPLACE TYPE "TBRANKING" AS TABLE OF RECORDRANKING;
Now I go into creating a function (into a package):
CREATE OR REPLACE PACKAGE BODY PKLBOTTONI as
FUNCTION testlb
(
p_gapup IN NUMBER,
p_gadown IN NUMBER
)
RETURN SYS_REFCURSOR IS
cursor_ranking SYS_REFCURSOR;
position NUMBER ;
gap_ranking TBRANKING;
gaprecord RECORDRANKING;
upgap NUMBER;
downgap NUMBER;
BEGIN
select * into cursor_ranking from(
select pkranking.getRanking( 100 ) from dual);
LOOP
FETCH cursor_ranking INTO gap_ranking;
EXIT WHEN cursor_ranking%NOTFOUND;
INSERT INTO gap_ranking (COL1,COL2,COL3)
VALUES
(cursor_ranking.C1,
cursor_ranking.C2,
cursor_ranking.C3);
END LOOP;
return gap_ranking;
END;
END PKLBOTTONI;
I always get:
Compilation errors for PACKAGE BODY PKLBOTTONI
Error: PL/SQL: ORA-00942: table or view does not exist
Line: 32
Text: INSERT INTO gap_ranking
In the loop you're both fetching into "gap_ranking" and then trying to "insert into" it again. Insert into a collection is not a valid syntax. Fetch is ok, either in a loop or using bulk collect to fetch multiple records at once.
From your excerpt it doesn't look like you have a physical database table, so the way to do it in PL/SQL would be something like below.
For more help using collections you can check Oracle docs below too:
http://docs.oracle.com/cd/B28359_01/appdev.111/b28370/collections.htm
SQL> set serveroutput on
SQL> declare
2 gap_ranking tbranking := tbranking(null); -- initialize
3 begin
4 gap_ranking.delete; -- clear empty record
5 for cur in
6 (select level as i from dual connect by level <= 5)
7 loop
8 -- insert empty
9 gap_ranking.extend;
10 -- attribute values
11 gap_ranking(gap_ranking.last) := recordranking(1000 + cur.i, 'TEST' || cur.i, 10 + cur.i);
12 end loop;
13 -- loop to print - just to illustrate
14 for j in gap_ranking.first .. gap_ranking.last
15 loop
16 dbms_output.put_line(gap_ranking(j).col1 || ',' ||
17 gap_ranking(j).col2 || ',' ||
18 gap_ranking(j).col3);
19
20 end loop;
21 -- same as...
22 for j in 1 .. gap_ranking.count
23 loop
24 dbms_output.put_line(gap_ranking(j).col1 || ',' ||
25 gap_ranking(j).col2 || ',' ||
26 gap_ranking(j).col3);
27
28 end loop;
29 end;
30 /
1001,TEST1,11
1002,TEST2,12
1003,TEST3,13
1004,TEST4,14
1005,TEST5,15
1001,TEST1,11
1002,TEST2,12
1003,TEST3,13
1004,TEST4,14
1005,TEST5,15
PL/SQL procedure successfully completed
SQL>

WHERE condition for "starts with any of the values in a CSV list" [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
how to convert csv to table in oracle
I have a query in PL/SQL that is built to handle input in a variable as a "starts-with" filter:
WHERE product_group LIKE strProductGroup || '%'
As a new "feature", the input variable could contain comma separated values. So, where before I would expect something like "ART", I could now see "ART,DRM".
I'd like to avoid building this query as a string and using EXECUTE IMMEDIATE, if possible. Can anyone think of a way to write a WHERE condition that is the equivalent of saying "starts with any of the values in a CSV list" in Oracle 10g?
Assuming that you don't have any restrictions on creating a couple of additional objects (a collection type and a function), you can parse the list into a collection that you can reference in your query. Tom Kyte has a good discussion on this in his variable "IN" list thread.
If you use Tom's myTableType and in_list function, for example
SQL> create or replace type myTableType as table
of varchar2 (255);
2 /
Type created.
ops$tkyte#dev8i> create or replace
function in_list( p_string in varchar2 ) return myTableType
2 as
3 l_string long default p_string || ',';
4 l_data myTableType := myTableType();
5 n number;
6 begin
7 loop
8 exit when l_string is null;
9 n := instr( l_string, ',' );
10 l_data.extend;
11 l_data(l_data.count) :=
ltrim( rtrim( substr( l_string, 1, n-1 ) ) );
12 l_string := substr( l_string, n+1 );
13 end loop;
14
15 return l_data;
16 end;
17 /
Then you can search for equality relatively easily.
WHERE product_group IN (SELECT column_value
FROM TABLE( in_list( strProductGroup )))
But you want to do a LIKE which is a bit more challenging since you can't do a LIKE on an in-list. You could, however, do something like
select *
from emp e,
(select '^' || column_value search_regexp
from table( in_list( 'KIN,BOB' ))) a
where regexp_like( e.ename, a.search_regexp )
This will search the EMP table for any employees where the ENAME begins with either KIN or BOB. In the default SCOTT.EMP table, this will return just one row, the row where the ENAME is "KING"
I found another post that gave me an idea. In my specific case, the values in the input will all be 3 characters, so I can do the following:
AND SUBSTR(product_group, 0, 3) IN
(SELECT regexp_substr(strProductGroup, '[^,]+', 1, LEVEL)
FROM dual
CONNECT BY LEVEL <= length(regexp_replace(strProductGroup, '[^,]+')) + 1)
I like this solution, because it does not require additional types or functions, but is pretty limited to my specific case.

using long string(over 4000) in oracle query

I know that in sql varchar2 can only be around 4000.
I know that in oracle PL varchcar2 can be around 32000.
I have a varchar2 variable defined that is over 4000 characters long and I want to use it in a query. I don't want to insert the value into a table. The value is a dilimited string that I am parsing and inserting into a table with this query. This query works when the variable is less than 4000 characters long. Is there a way to make it work with up to 32000 characters?
create global temporary table t(single_element varchar(500),element_no number);
declare
--declared as 32767 but this string contains less than 4000 characters.
--This will work. If you expand the string to 32000 characters it will not work.
myvar varchar2(32767) := 'tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4^~tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testmsg4';
begin
delete from t;
insert into t
SELECT SUBSTR(str, start_pos, (next_pos-start_pos)) AS single_element, element_no
FROM (
SELECT
ilv.str,
nt.column_value AS element_no,
INSTR(ilv.str, '^~', DECODE(nt.column_value, 1, 0, 1), DECODE(nt.column_value, 1, 1, nt.column_value-1)) + 2 AS start_pos,
INSTR(ilv.str, '^~', 1, DECODE(nt.column_value, 1, 1, nt.column_value)) AS next_pos
FROM (
select '~' || myvar || '^~' as str,
(Length(myvar) - length(replace(myvar,'^~','')))/2 + 2 as no_of_elements
from dual) ilv,
TABLE(
CAST(
MULTISET(
SELECT ROWNUM FROM dual CONNECT BY ROWNUM < ilv.no_of_elements
) AS number_ntt )) nt
);
end;
The error I get when expanding "myvar" to 32000 characters is
can bind a LONG value only for insert into a LONG column
Is there a way I can get around this size restraint because i'm not actually inserting this value into a table, i'm just using it in the query?
Do you have to define the variable as a VARCHAR2? Could you define it as a CLOB instead?
If I change the declaration of MYVAR from a VARCHAR2(32767) to a CLOB and define the NUMBER_NTT type, your code runs for me
SQL> ed
Wrote file afiedt.buf
SP2-0161: line 2 truncated.
1 declare
2 myvar clob := 'tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3^~tcd4~#testms
<<snip>>
~tcd3~#testmsg3^~tcd4~#testmsg4';
4 begin
5 delete from t;
6 insert into t
7 SELECT SUBSTR(str, start_pos, (next_pos-start_pos)) AS single_element, elem
ent_no
8 FROM (
9 SELECT
10 ilv.str,
11 nt.column_value AS element_no,
12 INSTR(ilv.str, '^~', DECODE(nt.column_value, 1, 0, 1), DECODE
(nt.column_value, 1, 1, nt.column_value-1)) + 2 AS start_pos,
13 INSTR(ilv.str, '^~', 1, DECODE(nt.column_value, 1, 1, nt.colu
mn_value)) AS next_pos
14 FROM (
15 select '~' || myvar || '^~' as str,
16 (Length(myvar) - length(replace(myvar,'^~','')))/2 + 2 as n
o_of_elements
17 from dual) ilv,
18 TABLE(
19 CAST(
20 MULTISET(
21 SELECT ROWNUM FROM dual CONNECT BY ROWNUM < ilv.n
o_of_elements
22 ) AS number_ntt )) nt
23 );
24* end;
25 /
PL/SQL procedure successfully completed.
SQL> select count(*) from t;
COUNT(*)
----------
172
That being said, that's not how I'd parse a delimited string, particularly in PL/SQL. But it does the job.
OK, well this skirts close to the edges of your implementation bias, although remember that forall IS a bulk-binding operation, not a real loop, but have you looked at the dbms_utility.comma_to_table function?
It is an optimized internal oracle parsing function, although with some limitations as you can read about here: http://www.techiegyan.com/2009/02/17/oracle-breaking-comma-separated-string-using-dbms_utilitycomma_to_table/
You would need to replace() to make it comma-delimited, and also double-quotes-enclose if you have parsed fields that starts with numbers, special characters, contains commas, etc
But if your data will allow - it sure makes your code look cleaner (and will likely work much faster too)
declare
myvar varchar2(32000) := 'tcd1~#testmsg1^~tcd2~#testmsg2^~tcd3~#testmsg3';
mycnt binary_integer;
myresults sys.dbms_utility.lname_array;
begin
sys.dbms_utility.comma_to_table('"'||replace(myvar,'^~','","')||'"', mycnt, myresults );
delete from t;
forall ix in myresults.first..myresults.last
insert into tvalues (myresults(ix));
commit;
end;

sqlplus - using a bind variable in "IN" clause

I am setting a bind variable in a PL/SQL block, and I'm trying to use it in another query's IN expression. Something like this:
variable x varchar2(255)
declare
x varchar2(100);
begin
for r in (select id from other_table where abc in ('&val1','&val2','&val3') ) loop
x := x||''''||r.id||''',';
end loop;
--get rid of the trailing ','
x:= substr(x,1,length(x)-1);
select x into :bind_var from dual;
end;
/
print :bind_var;
select *
from some_table
where id in (:bind_var);
And I get an error (ORA-01722: Invalid number) on the query that tries to use the bind variable in the "IN" list.
The print statement yiels '123','345' which is what I expect.
Is it possible to use the bind variable like this or should I try a different approach?
(using Oracle 10g)
Clarification:
This is for a reconcilliation sort of thing. I want to run
select *
from some_table
where id in (select id from other_table where abc in ('&val1','&val2','&val3'))
before the main part of the script (not pictured here) deletes a whole bunch of records. I want to run it again afterwards to verify that records in some_table have NOT been deleted. However, the data in other_table DOES get deleted by this process so I can't just refer to the data in other_table because there's nothing there. I need a way to preserve the other_table.id values so that I can verify the parent records afterwards.
I would store the other_table.id's in a PL/SQL table and reference that table in the query afterwards:
type t_id_table is table OF other_table.id%type index by binary_integer;
v_table t_id_table;
-- fill the table
select id
bulk collect into v_table
from other_table
where abc in ('&val1','&val2','&val3');
-- then at a later stage...
select *
from some_table st
, table(cast(v_table AS t_id_table)) idt
where st.id = idt.id;
You can't use comma-separated values in one bind variable.
You could say:
select * from some_table where id in (:bind_var1, :bind_var2)
though
You're better off using something like:
select * from some_table where id in ("select blah blah blah...");
I would use a global temporary table for this purpose
create global temporary table gtt_ids( id number ) ;
then
...
for r in (select id from other_table where ... ) loop
insert into gtt_ids(id) values (r.id) ;
end loop;
...
and at the end
select *
from some_table
where id in (select id from gtt_ids);
changed the loop to use listagg (sadly this will only work in 11gr2).
but for the variable in list, I used a regular expression to accomplish the goal (but pre 10g you can use substr to do the same) this is lifted from the asktom question linked.
variable bind_var varchar2(255)
variable dataSeperationChar varchar2(255)
declare
x varchar2(100);
begin
select listagg(id,',') within group(order by id) idList
into x
from(select level id
from dual
connect by level < 100 )
where id in (&val1,&val2,&val3) ;
select x into :bind_var from dual;
:dataSeperationChar := ',';
end;
/
print :bind_var;
/
select *
from (
select level id2
from dual
connect by level < 100
)
where id2 in(
select -- transform the comma seperated string into a result set
regexp_substr(:dataSeperationChar||:bind_var||','
, '[^'||:dataSeperationChar||']+'
,1
,level) as parsed_value
from dual
connect by level <= length(regexp_replace(:bind_var, '([^'||:dataSeperationChar||'])', '')) + 1
)
;
/*
values of 1,5, and 25
BIND_VAR
------
1,5,25
ID2
----------------------
1
5
25
*/
EDIT
Oops just noticed that you did mark 10g, the only thing to do is NOT to use the listagg that I did at the start
Ok, I have a kind of ugly solution that also uses substitution variables...
col idList NEW_VALUE v_id_list /* This is NEW! */
variable x varchar2(255)
declare
x varchar2(100);
begin
for r in (select id from other_table where abc in ('&val1','&val2','&val3') ) loop
x := x||''''||r.id||''',';
end loop;
--get rid of the trailing ','
x:= substr(x,1,length(x)-1);
select x into :bind_var from dual;
end;
/
print :bind_var;
select :x idList from dual; /* This is NEW! */
select *
from some_table
where id in (&idList); /* This is CHANGED! */
It works, but I'll accept an answer from someone else if it's more elegant.

Resources