How do I check for a IN condition against a dynamic list in Oracle? - oracle

EDIT: changed the title to fit the code below.
I'm trying to retrieve a list of acceptable values from an Oracle table, then performing a SELECT against another while comparing some fields against said list.
I was trying to do this with cursors (like below), but this fails.
DECLARE
TYPE gcur IS REF CURSOR;
TYPE list_record IS TABLE OF my_table.my_field%TYPE;
c_GENERIC gcur;
c_LIST list_record;
BEGIN
OPEN c_GENERIC FOR
SELECT my_field FROM my_table
WHERE some_field = some_value;
FETCH c_GENERIC BULK COLLECT INTO c_LIST;
-- try to check against list
SELECT * FROM some_other_table
WHERE some_critical_field IN c_LIST;
END
Basically, what I'm trying to do is to cache the acceptable values list into a variable, because I will be checking against it repeatedly later.
How do you perform this in Oracle?

We can use collections to store values to suit your purposes, but they need to be declared as SQL types:
create type list_record is table of varchar2(128)
/
This is because we cannot use PL/SQL types in SQL statements. Alas this means we cannot use %TYPE or %ROWTYPE, because they are PL/SQL keywords.
Your procedure would then look like this:
DECLARE
c_LIST list_record;
BEGIN
SELECT my_field
BULK COLLECT INTO c_LIST
FROM my_table
WHERE some_field = some_value;
-- try to check against list
SELECT * FROM some_other_table
WHERE some_critical_field IN ( select * from table (c_LIST);
END;
"I see that you still had to perform a
SELECT statement to populate the list
for the IN clause."
If the values are in a table there is no other way to get them into a variable :)
"I'm thinking that there's a
significant performance gain using
this over a direct semi-join"
Not necessarily. If you're only using the values once then the sub-query is certainly the better approach. But as you want to use the same values in a number of discrete queries then populating a collection is the more efficient approach.
In 11g Enterprise Edition we have the option to use result set caching. This is a much better solution, but one which is not suited for all tables.

Why pull the list instead of using a semi-join?
SELECT *
FROM some_other_table
WHERE some_critical_field IN (SELECT my_field
FROM my_table
WHERE some_field = some_value);

Related

Iterative search of a Teradata clob

We have an accountnumber stored in a Clob field in a table...we'll call it tbl_accountdetail. I need to pull back all records from tbl_accountdetail if the account numbers are in the results from another query...we'll call it sourcequery.
I can do this individually for each account number with:
Select * from Tbl_accountdetail where REGEXP_INSTR(CLOB,'accountnumber')>0
Naturally, my first thought was to do a cursor and loop through each account number from the sourcequery.
Declare #accountnumber varchar(30)
Declare Err_Cursor Cursor for
Select accountnumber from ErrorTable;
Open Err_Cursor;
Fetch next from Err_Cursor into #accountnumber;
While ##Fetch_status = 0
Begin
Select * from Tbl_accountdetail where REGEXP_INSTR(CLOB,#accountnumber)>0
Fetch next from Err_Cursor into #accountnumber
End;
Close Err_Cursor;
Deallocate Err_Cursor;
The more I read the more I'm confused about the best/most efficient way to get my desired results.
References to cursors all seem to require them to be included in a stored procedure and based on the simplicity, you wouldn't think this needs to be added to a sp. References to macros all seem to be macros that need to update/insert,etc. which I don't need. All I need to do is return the rows from Tbl_accountdetail that have the accountnumber somewhere in the clob.
I'm new to Teradata and Clobs. Can someone help me with the best way to search the clob? And to do so for a list of values?
Any help/suggestions greatly appreciated.
How is your CLOB data structured? Is the accountnumber field stored in a way that you can extract it using a searchable pattern -- i.e. accountnumber=<10-digit-#>?
If you want to search for multiple accountnumber values, then I think the best way is to extract the accountnumber value(s) from each CLOB and then search on them. Something like:
SELECT *
FROM Tbl_accountdetail
WHERE <extracted_accountnumber> IN (
SELECT account_number
FROM account_table
)
You are right that cursors are only used in stored procedures. The main purpose for them is to process each row of a result set individually and perform any additional logic, which you don't seem to need in your case. You could either put the SQL into a macro or just run it as-is.
Update
Assuming you only have one accountnum value in your CLOB field with a format of "accountnum": "123456789", you should be able to do something like this:
SELECT *
FROM Tbl_accountdetail
WHERE REGEXP_SUBSTR(myclob, '"accountnum":\s+"([0-9]+)"', 1, 1, NULL) IN (
SELECT account_number
FROM account_table
)
This should extract the first accountnumber match in your CLOB field and see if that value also exists in the IN sub-query.
I don't have a TD system to test on, so you may need to fiddle with the arguments a bit. Just replace myclob with the name of your CLOB field and update the sub-query in the IN(). Give that a try and let me know.
SQL Fiddle (Oracle)
Regexp Tester
Teradata - REGEXP_SUBSTR

How to find the column used in the dynamic query without executing whole query

Problem Statement
I have a dynamic SQL which i need to store in a table ,but before
storing the sql i need to validate the sql with the list of columns
stored in another table.
Without executing the query , is it possible to find name of columns in the select ?
Approach1
Only option i can think of is ,try to use explain plan of the query and read the meta data in the data dictionaries table .But unfortunately i am not able to find any table with such data.Please let me know if you know such views?
Approach2
Use DBMS_SQL.DESCRIBE_COLUMNS package to find the column name ,but i believe this will execute the whole query.
You don't need to execute the query to get the column names, you just need to parse it; e.g. as a simple example:
set serveroutput on
declare
l_statement varchar2(4000) := 'select * from employees';
l_c pls_integer;
l_col_cnt pls_integer;
l_desc_t dbms_sql.desc_tab;
begin
l_c := dbms_sql.open_cursor;
dbms_sql.parse(c=>l_c, statement=>l_statement, language_flag=>dbms_sql.native);
dbms_sql.describe_columns(c=>l_c, col_cnt=>l_col_cnt, desc_t=>l_desc_t);
for i in 1..l_col_cnt loop
dbms_output.put_line(l_desc_t(i).col_name);
end loop;
dbms_sql.close_cursor(l_c);
exception
when others then
if (dbms_sql.is_open(l_c)) then
dbms_sql.close_cursor(l_c);
end if;
raise;
end;
/
which outputs:
EMPLOYEE_ID
FIRST_NAME
LAST_NAME
EMAIL
PHONE_NUMBER
HIRE_DATE
JOB_ID
SALARY
COMMISSION_PCT
MANAGER_ID
DEPARTMENT_ID
PL/SQL procedure successfully completed.
You can do whatever validation you need on the column names inside the loop.
Bear in mind that you'll only see (and validate) the column names or aliases for column expressions, which won't necessarily reflect the data that is actually being retrieved. Someone could craft a query that pulls any data from anywhere it has permission to access, but then gives the columns/expression aliases that are considered valid.
If you're trying to restrict access to specific data then look into other mechanisms like views, virtual private database, etc.
DBMS_SQL.PARSE will not execute a SELECT statement but it will execute a DDL statement. If the string 'select * from employees' is replaced by 'drop table employees' the code will fail but the table will still get dropped.
If you're only worried about the performance of retrieving the metadata then Alex Poole's answer will work fine.
If you're worried about running the wrong statement types then you'll want to make some adjustments to Alex Poole's answer.
It is surprisingly difficult to tell if a statement is a SELECT instead of something else. A simple condition checking that the string begins with select will work 99% of the time but getting from 99% to 100% is a huge amount of work. Simple regular expressions cannot keep up with all the different keywords, comments, alternative quoting format, spaces, etc.
/*comment in front -- */ select * from dual
select * from dual
with asdf as (select * from dual) select * from asdf;
((((((select * from dual))))));
If you need 100% accuracy I recommend you use my open source PLSQL_LEXER. Once installed you can reliably test the command types like this:
select
statement_classifier.get_command_name(' /*comment*/ ((select * from dual))') test1,
statement_classifier.get_command_name('alter table asdf move compress') test2
from dual;
TEST1 TEST2
----- -----
SELECT ALTER TABLE

Insert data from pl/sql table type to another pl/sql table type

I am just wondering whether it is possible to insert data from one pl/sql table to another using bulkcollect?
I am trying it out but looks like it is not possible conceptually and the only way is to loop through the Pl/sql table .
Any insights would be really helpful.
Thanks
below is the simplified version of what i am trying. i think i am making some conceptual mistake here . hence it is not working:
DECLARE
TYPE ROWTBL IS TABLE OF BW_COLUMN.NAME%TYPE ;
PL_TBL_ROW ROWTBL;
TYPE COLNAME_TBL IS TABLE OF BW_COLUMN.NAME%TYPE ;
PL_TBL_COLNAME COLNAME_TBL;
BEGIN
SELECT NAME
BULK COLLECT INTO PL_TBL_ROW
FROM TBL_COL WHERE TBL_ID = 2000081;
SELECT NAME
BULK COLLECT INTO PL_TBL_COLNAME
FROM PL_TBL_ROW;
END;
BULK COLLECT is a mechanism for efficiently reading data into PL/SQL data structures so they can be processed by PL/SQL code. You can certainly use this approach for copying data from one table to another but I suspect it will probably take more time than the simpler approach of using an INSERT statement such as
BEGIN
INSERT INTO SOME_TABLE (COL_1, COL_2, COL_N)
SELECT COL_1, COL_2, COL_N
FROM SOME_OTHER_TABLE
WHERE SOME_FIELD = SOME_OTHER_VALUE;
END;
Best of luck.

Alternate method to global temp tables for Oracle Stored Procedure

I have read and understand that Oracle uses only global temp tables unlike MS SQL which allows #temp tables. The situation that I have would call for me to create hundreds of Global temp tables in order to complete the DB conversion I am working on from MS SQL to Oracle. I want to know if there is another method out there, within a Oracle Stored Procedure, other than creating all of these tables which will have to be maintained in the DB.
Thank You
" Most of the time the only thing the temp tables are used within a
stored proc and then truncated at the end. We do constant upgrades to
our applications and having them somewhat comparable ensures that when
a change is made in one version that it can be easily merged to the
other."
T-SQL Temp tables are essentially memory structures. They provide benefits in MSSQL which are less obvious in Oracle, because of differences in the two RDBMS architectures. So if you were looking to migrate then you would be well advised to take an approach more fitted to Oracle.
However, you have a different situation, and obviously keeping the two code bases in sync will make your life easier.
The closest thing to temporary tables as you want to use them are PL/SQL collections; specifically, nested tables.
There are a couple of ways of declaring these. The first is to use a SQL template - a cursor - and define a nested table type based on it. The second is to declare a record type and then define a nested table on that. In either case, populate the collection variable with a bulk operation.
declare
-- approach #1 - use a cursor
cursor c1 is
select *
from t23;
type nt1 is table of c1%rowtype;
recs1 nt1;
-- approach #1a - use a cursor with an explicit projection
cursor c1a is
select id, col_d, col_2
from t23;
type nt1a is table of c1a%rowtype;
recs1 nt1a;
-- approach #2 - use a PL/SQL record
type r2 is record (
my_id number
, some_date date
, a_string varchar2(30)
);
type nt2 is table of r2;
recs2 nt2;
begin
select *
bulk collect into recs1
from t23;
select id, col_d, col_2
bulk collect into recs2
from t23;
end;
/
Using a cursor offers the advantage of automatically reflecting changes in the underlying table(s). Although the RECORD provides the advantage of stability in the face of changes in the underlying table(s). It just depends what you want :)
There's a whole chapter in the PL/SQL reference manual. Read it to find out more.

Populating a cursor with a rowtype variable

No amount of googling seems to find the answer to this one...
I'm trying to modify Oracle sproc that that currently pulling values out of some tables and moving them to other tables.
It has a ROWTYPE variable that is defined like this:
myRow my_tbl%ROWTYPE;
Right now, the sproc does some logic that populates the rowtype variable and then uses it to populate a table:
INSERT INTO MY_TBL
( col1,
col2,
col3,
-snip-
)
VALUES (
myRow.aValue,
myRow.aValue2,
myRow.aValu3,
-snip-
)
Instead of populating a table, I want to use the ROWTYPE to populate a cursor that is returned to a Web app. However, I can't find a way to do this ROWTYPE -> REF CURSOR conversion. Is this possible? If not, is there a way to manually populate a cursor with data drawn from various tables and using some complex logic? I'm using Oracle 10g.
Thanks!
The tricky part about this is that you are using a REF CURSOR, which is intended for a set of rows to return data for just a single row. I imagine you are just doing this because your web app understands ref cursors and how to get them from Oracle, but not object types. I am also guessing that for some reason you can't just write a single select statment to retrieve and manipulate the data as needed (this is the easiest way, so with more info we can possibly help you out to achieve it).
There are a few ways I can think of to do this, none of them very pretty, so hopefully someone else will chime in with a better idea.
1) Create the cursor by selecting your calculated variables from dual
DECLARE
refcur SYS_REFCURSOR;
myRow TBL%ROWTYPE;
BEGIN
myRow.aValue := 1;
myRow.aValue2 := 3;
myRow.aValue3 := 5;
OPEN refcur
FOR
select
myRow.aValue,
myRow.aValue2,
myRow.aValue3
from
dual;
CLOSE refcur;
END;
2) Create a pipelined function that returns a table of your rowtype, and create your cursor from a select from that function.
The select from dual would be something like
select myRow.aValue,
myRow.aValue2,
myRow.aValu3
from dual;
You should be able to declare a cursor for that.
There is a good writeup of REF CURSOR at http://psoug.org/reference/ref_cursors.html

Resources