How many times will the "SELECT" inside "IN" execute? - sql

I have less exposure to the performance aspect of Oracle. I want to know how exactly Oracle will handle this situation:
My procedure receives a comma-separated string, which I have to split based on comma and filter.
I know this works:
SELECT *
FROM some_table
WHERE some_attribute IN (SELECT
REGEXP_SUBSTR(comma_separated_string,'[^,]+', 1, LEVEL)
FROM DUAL
CONNECT BY REGEXP_SUBSTR(comma_separated_string,
'[^,]+', 1, LEVEL) IS NOT NULL);
Since, "REG_EX" is CPU intensive, I thought of replacing the above by:
SELECT *
FROM some_table
WHERE INSTR(','|| comma_separated_string||',', ','|| some_attribute||',') >
0;`
OR
SELECT *
FROM some_table
WHERE ','||comma_separated_string||',' like '%,'|| some_attribute ||',%';`
So, picking either one depends on how the select executes or, to be precise, how many times the clause after where executes.

Related

Find out what pattern was matched when using a LIKE query?

If I was to perform a normal query with a bunch of LIKE statements in it. Would it be possible to return which search term actually resulted in the row being returned?
So if I ran :
select cand_id
FROM cand_kw
WHERE client_id='client'
AND ( ( UPPER(kw) LIKE '%ANDREW%' AND UPPER(kw) LIKE '%POSTINGS%' )
OR ( UPPER(kw) LIKE '%BRET%' )
OR ( UPPER(kw) LIKE '%TIM%' )) ) )
And it returned some rows of results is there a way to tag on which term was actually matched in the row? So if '%ANDREW%' was what caused this row to be returned I could then show that information.
The data base engine is oracle 9i and I realize that this is normally a function something like full text searches that this database is not setup to handle so I am just trying to fake it in way.
It is a bit tricky, because more than one keyword may match. You could use a CASE expression in the SELECT clause, but then you would get the first matching keyword only.
Another approach would be to put each keyword on a separate row, use a join to filter the original table, and then aggregate the list of matching keyword.
So:
SELECT c.cand_id, LISTAGG(k.kw, ', ') WITHIN GROUP (ORDER BY k.kw) matches
FROM cand_kw c
INNER JOIN (
SELECT 'ANDREW' kw FROM DUAL
UNION ALL SELECT 'POSTINGS' FROM DUAL
UNION ALL SELECT 'BRET' FROM DUAL
UNION ALL SELECT 'TIM' FROM DUAL
) k ON c.kw LIKE '%' || k.kw || '%'
GROUP BY c.cand_id

Finding max value of multiple columns from multiple tables to update Sequence

I had a problem where the DBAs needed to recreate my sequence (had to create with "NO CACHE". Unfortunately, he dropped the sequence before grabbing the current value! The problem is, from what I can tell, there are almost 25 tables that use this sequence. My plan was to try to find the max value of each of the Primary Key "ID" fields, then run a sequence loop to get the sequence back up.
What I'm hoping to do now, is clean up my "ugly" process for a more streamlined process that I can put in my documentation (in the event this occurs again!).
My original solution was do something like the following:
SELECT 'TABLE_1','TABLE_1_ID', MAX(TABLE_1_ID) from TABLE_1
UNION ALL
SELECT 'TABLE_2','TABLE_2_ID', MAX(TABLE_2_ID) from TABLE_2
UNION ALL
SELECT 'TABLE_3','TABLE_3_ID', MAX(TABLE_3_ID) from TABLE_3
UNION ALL
...... (continue select statements for other 20+ tables)
SELECT 'TABLE_25','TABLE_25_ID', MAX(TABLE_25_ID) from TABLE_25
ORDER BY 2 DESC;
This shows works, but putting the table with the highest "MAX" at the top; but to clean it up I'd like to:
1. Simplify the query (an eliminate the UNION ALL) if possible
2. I'd really like to just run the query that returns a single row..
This would be 'gravy', but I have a loop that will run through the next val of the sequence; that loop starts off with:
declare
COL_MaxVal pls_integer;
SEQ_Currval pls_integer default -1;
BEGIN
SELECT MAX(TABLE_X_ID) INTO COL_MaxVal
FROM TABLE_X
while SEQ_Currval < COL_MaxVal
loop
select My_Sequence_SEQ.nexval into SEQ_Currval
from dual;
end loop;
end
If possible, I'd really like to just run the loop script which would discover which table/column has the highest max value, then use that table in the loop to increment the sequence to that max value.
Appreciate any help on this.
Here is solution returning one row:
WITH all_data as
(
SELECT 'TABLE_1','TABLE_1_ID', MAX(TABLE_1_ID) as id from TABLE_1
UNION ALL
SELECT 'TABLE_2','TABLE_2_ID', MAX(TABLE_2_ID) from TABLE_2
UNION ALL
SELECT 'TABLE_3','TABLE_3_ID', MAX(TABLE_3_ID) from TABLE_3
UNION ALL
...... (continue select statements for other 20+ tables)
SELECT 'TABLE_25','TABLE_25_ID', MAX(TABLE_25_ID) from TABLE_25
),
max_id as
(
SELECT max(id) as id FROM all_data
)
SELECT
ad.*
FROM
all_data ad
JOIN max_id mi ON (ad.id = mi.id)
I can not see any simpler solution for this...
If it's not too late then dba might try flashback query against dictionary. E.g.
SELECT * FROM dba_sequences AS OF TIMESTAMP systimestamp - 1/24;
Your safe value should be last_number+cache size. See details in:
LAST_NUMBER on oracle sequence

Oracle- create a temporary resultset for use in a query

How do I create a temporary result set for use in an SQL without creating a table and inserting the data?
Example: I have a list of, say 10 codes for example. I want to put this into a query, and then query the database to see which codes in this temporary list do not exist in a table.
If it was already in a table, I could do something like:
SELECT
ITEM_CODE
FROM
TEMP_ITEMS
MINUS
SELECT
ITEM_CODE
FROM
M_ITEMS
Is their a way without using PL/SQL, and pure SQL to create a temporary rowset before querying?
Please don't answer with something like:
SELECT 1 FROM DUAL
UNION ALL
SELECT 2 FROM DUAL
I am sort of thinking of something where I can provide my codes in an IN statement, and it turns that into rows for use in a later query.
Edit: so everyone knows my objective here, basically I sometimes get a list of product codes that I need to find which ones in the list are not setup in our system. I want a quick way to throw this into an SQL statement so I can see which ones are not in the system (rather than importing data etc). I usually put these into excel, then do a formula such as :
="'"&A1&"',"
So that I can create my comma separated list.
If you are using oracle 11g you can do this
with t as
(
select (column_value).getnumberval() Codes from xmltable('1,2,3,4,5')
)
SELECT * FROM t
WHERE NOT EXISTS (SELECT 1 FROM M_ITEMS M WHERE codes = M.ITEM_CODE);
or
with t as
(
select (column_value).getstringval() Codes from xmltable('"A","B","C"')
)
SELECT * FROM t
WHERE NOT EXISTS (SELECT 1 FROM M_ITEMS M WHERE codes = M.ITEM_CODE);
I would go with:
with t as (
select 1 as val from dual union all
select 2 as val from dual
)
select . . .
And then use "t" or whatever you call it, in the subsequent query block.
I'm not sure what the objection is to using the select method . . . just pop the values you want in a column in Excel and produce the code for each value by copying down the formula. Then paste the results back into your query interface.
If you want to use a temporary table, you can use the values clause. Alternatively, you can use string functions if you only want IN functionality. Put the values in a comma separated list and check to see if it matches a particular value:
where ','||<list>||',' like '%,'||col||',%'
This one is interesting because it's not a union and fit in a single select. You have to enter the string with delimiters ('a/b/c/def') two times though:
SELECT regexp_substr('a/b/c/def', '[^/]+', 1, ROWNUM) var,
regexp_substr('2/432/sd/fsd', '[^/]+', 1, ROWNUM) var2
FROM dual
CONNECT BY LEVEL <= length(regexp_replace('a/b/c/def', '[^/]', '')) + 1;
var var2
=== ====
a 2
b 432
c sd
def fsd
Note: Credits go to : https://stackoverflow.com/a/1381495/463056
So using the with clause it would give someting like :
with tempo as (
SELECT regexp_substr('a/b/c/def', '[^/]+', 1, ROWNUM) var,
regexp_substr('2/432/sd/fsd', '[^/]+', 1, ROWNUM) var2
FROM dual
CONNECT BY LEVEL <= length(regexp_replace('a/b/c/def', '[^/]', '')) + 1
)
select ...
or you can use it in a from clause :
select ...
from (
SELECT regexp_substr('a/b/c/def', '[^/]+', 1, ROWNUM) var,
regexp_substr('2/432/sd/fsd', '[^/]+', 1, ROWNUM) var2
FROM dual
CONNECT BY LEVEL <= length(regexp_replace('a/b/c/def', '[^/]', '')) + 1
) tempo
There are two approaches I would lean towards:
1. Global Temporary Table
Although you say you don't want to create a table, it depends on why you don't want a table. If you choose to create a Global Temporary table, the rows are only visible to the session that inserted them, so it's like having a private in-memory table but gives you all the benefits of a real table - i.e. being able to query and join to it.
2. Pipelined function
You can create a function that returns the results in a form that can be queried using the TABLE() operator. More info here: http://www.oracle-base.com/articles/misc/pipelined-table-functions.php
It's a bit hokey-looking. But you can parse a string into separate rows using regular expressions assuming you are using 10g or later. For example
SQL> ed
Wrote file afiedt.buf
1 SELECT REGEXP_SUBSTR('a,b,c,def,g', '[^ |,]+', 1, LEVEL) parsed_str
2 FROM dual
3* CONNECT BY LEVEL <= REGEXP_COUNT('a,b,c,def,g', '[^ |,]+')
SQL> /
PARSED_STR
--------------------------------------------
a
b
c
def
g
Personally, I would find a pipelined table function or a PL/SQL block that generates a collection easier to understand, but if you have to do it in SQL you can.
Based on your edit, if you are getting a list of product codes that is already in some sort of file, it would seem to make more sense to use an external table to expose the file as a table or to use SQL*Loader to load the data into a table (temporary or permanent) that you can query. Barring either of those options, if you really want to manipulate the list in Excel first, it would make more sense to generate an IN list in Excel and just copy and past that into your query. Generating a comma-separated list of codes in Excel only to parse that list into it's constituent elements in SQL seems like way too many steps.

dynamic number of where condition in oracle sql

I need to write a sql for a prompt in a reporting tool. I get the list of multiple values in a variable separated by '-' and the number of such values can vary.(eg1."abc-def" eg2."abc-def-xyz").
Now I need to write sql in oracle of this form(logically)
select something
from somewhere
where someColumn in 'abc' -- use substr to extract abc
or someColumn in 'def' -- use substr to extract def
or ...till we don't find '-'.
I can't use plsql for the query. Either I may not be knowing to use the variable in which I select into in plsql or may be the reporting tool doesn't support that.
Thanks in advance.
Try
select something
from somewhere
where someColumn in (select regexp_substr('abc-def-xyz','[^-]+', 1, level) from dual
connect by regexp_substr('abc-def-xyz', '[^-]+', 1, level) is not null);
To generalize (considering your fields are separated by "-")
select something
from somewhere
where someColumn in (select regexp_substr(variable,'[^-]+', 1, level) from dual
connect by regexp_substr(variable, '[^-]+', 1, level) is not null);
Basically the output of the subquery is shown below -
SQL> select regexp_substr('abc-def-xyz','[^-]+', 1, level) value from dual
connect by regexp_substr('abc-def-xyz', '[^-]+', 1, level) is not null;
VALUE
--------------------------------
abc
def
xyz
First split the string into its parts using Oracle's regexp_substr() function. See regexp_substr function (If you can access the original that generates the string, just use that.)
Second, put this in a temp table and then just have:
select something
from somewhere
where someColumn in (select parts from tmpTable)
select something
from somewhere
where INSTR('-'||'abc-def-ghi'||'-', '-'||someColumn||'-') > 0;
If you just want a list of results that contain a '-', you could just do something like
SELECT SOMETHING FROM SOMEWHERE WHERE SOMECOLUMN LIKE ('%-%');

Using LIKE in an Oracle IN clause

I know I can write a query that will return all rows that contain any number of values in a given column, like so:
Select * from tbl where my_col in (val1, val2, val3,... valn)
but if val1, for example, can appear anywhere in my_col, which has datatype varchar(300), I might instead write:
select * from tbl where my_col LIKE '%val1%'
Is there a way of combing these two techniques. I need to search for some 30 possible values that may appear anywhere in the free-form text of the column.
Combining these two statements in the following ways does not seem to work:
select * from tbl where my_col LIKE ('%val1%', '%val2%', 'val3%',....)
select * from tbl where my_col in ('%val1%', '%val2%', 'val3%',....)
What would be useful here would be a LIKE ANY predicate as is available in PostgreSQL
SELECT *
FROM tbl
WHERE my_col LIKE ANY (ARRAY['%val1%', '%val2%', '%val3%', ...])
Unfortunately, that syntax is not available in Oracle. You can expand the quantified comparison predicate using OR, however:
SELECT *
FROM tbl
WHERE my_col LIKE '%val1%' OR my_col LIKE '%val2%' OR my_col LIKE '%val3%', ...
Or alternatively, create a semi join using an EXISTS predicate and an auxiliary array data structure (see this question for details):
SELECT *
FROM tbl t
WHERE EXISTS (
SELECT 1
-- Alternatively, store those values in a temp table:
FROM TABLE (sys.ora_mining_varchar2_nt('%val1%', '%val2%', '%val3%'/*, ...*/))
WHERE t.my_col LIKE column_value
)
For true full-text search, you might want to look at Oracle Text: http://www.oracle.com/technetwork/database/enterprise-edition/index-098492.html
A REGEXP_LIKE will do a case-insensitive regexp search.
select * from Users where Regexp_Like (User_Name, 'karl|anders|leif','i')
This will be executed as a full table scan - just as the LIKE or solution, so the performance will be really bad if the table is not small. If it's not used often at all, it might be ok.
If you need some kind of performance, you will need Oracle Text (or some external indexer).
To get substring indexing with Oracle Text you will need a CONTEXT index. It's a bit involved as it's made for indexing large documents and text using a lot of smarts. If you have particular needs, such as substring searches in numbers and all words (including "the" "an" "a", spaces, etc) , you need to create custom lexers to remove some of the smart stuff...
If you insert a lot of data, Oracle Text will not make things faster, especially if you need the index to be updated within the transactions and not periodically.
No, you cannot do this. The values in the IN clause must be exact matches. You could modify the select thusly:
SELECT *
FROM tbl
WHERE my_col LIKE %val1%
OR my_col LIKE %val2%
OR my_col LIKE %val3%
...
If the val1, val2, val3... are similar enough, you might be able to use regular expressions in the REGEXP_LIKE operator.
Yes, you can use this query (Instead of 'Specialist' and 'Developer', type any strings you want separated by comma and change employees table with your table)
SELECT * FROM employees em
WHERE EXISTS (select 1 from table(sys.dbms_debug_vc2coll('Specialist', 'Developer')) mt
where em.job like ('%' || mt.column_value || '%'));
Why my query is better than the accepted answer: You don't need a CREATE TABLE permission to run it. This can be executed with just SELECT permissions.
In Oracle you can use regexp_like as follows:
select *
from table_name
where regexp_like (name, '^(value-1|value-2|value-3....)');
The caret (^) operator to indicate a beginning-of-line character &
The pipe (|) operator to indicate OR operation.
This one is pretty fast :
select * from listofvalue l
inner join tbl on tbl.mycol like '%' || l.value || '%'
Just to add on #Lukas Eder answer.
An improvement to avoid creating tables and inserting values
(we could use select from dual and unpivot to achieve the same result "on the fly"):
with all_likes as
(select * from
(select '%val1%' like_1, '%val2%' like_2, '%val3%' like_3, '%val4%' as like_4, '%val5%' as like_5 from dual)
unpivot (
united_columns for subquery_column in ("LIKE_1", "LIKE_2", "LIKE_3", "LIKE_4", "LIKE_5"))
)
select * from tbl
where exists (select 1 from all_likes where tbl.my_col like all_likes.united_columns)
I prefer this
WHERE CASE WHEN my_col LIKE '%val1%' THEN 1
WHEN my_col LIKE '%val2%' THEN 1
WHEN my_col LIKE '%val3%' THEN 1
ELSE 0
END = 1
I'm not saying it's optimal but it works and it's easily understood. Most of my queries are adhoc used once so performance is generally not an issue for me.
select * from tbl
where exists (select 1 from all_likes where all_likes.value = substr(tbl.my_col,0, length(tbl.my_col)))
You can put your values in ODCIVARCHAR2LIST and then join it as a regular table.
select tabl1.* FROM tabl1 LEFT JOIN
(select column_value txt from table(sys.ODCIVARCHAR2LIST
('%val1%','%val2%','%val3%')
)) Vals ON tabl1.column LIKE Vals.txt WHERE Vals.txt IS NOT NULL
You don't need a collection type as mentioned in https://stackoverflow.com/a/6074261/802058. Just use an subquery:
SELECT *
FROM tbl t
WHERE EXISTS (
SELECT 1
FROM (
SELECT 'val1%' AS val FROM dual
UNION ALL
SELECT 'val2%' AS val FROM dual
-- ...
-- or simply use an subquery here
)
WHERE t.my_col LIKE val
)