How to query and compare strings containing fractions in Oracle 9i - oracle

I have been asked to work with a legacy Oracle 9i DB and compare fractional values which users have stored as strings. ie. some table field may contain values like "9 7/8", "3 15/16" etc.
Without modifying the DB, adding procedures, indexes or anything how could I query for all the records who's value is less than "7 4/5"?
Like:
SELECT * FROM `ANNOYING_TABLE` WHERE FRACTIONAL_STRING_TO_DECIMAL(`fractional_data`) < 7.8
Is there some way to perhaps split on the " " and the split the second value on "/" and then do split[0]+split[1]/split[2]
SELECT * FROM `ANNOYING_TABLE`, FIRST_SPLIT = SPLIT(`fractional_data`," "), SECOND_SPLIT = SPLIT( FIRST_SPLIT[1], "/" ) WHERE (FIRST_SPLIT[0]+(SECOND_SPLIT[0] / SECOND_SPLIT[1])) < 7.8
Any ideas?

I got it by some contortions of built in functions...
SELECT
"TABLE"."ID",
"TABLE"."VALUE"
FROM
"TABLE"
WHERE
(
SUBSTR(
VALUE,
0,
INSTR(VALUE, ' ')- 1
) + SUBSTR(
VALUE,
INSTR(VALUE, ' ')+ 1,
INSTR(VALUE, '/')- INSTR(VALUE, ' ')- 1
) / SUBSTR(
VALUE,
INSTR(VALUE, '/')+ 1
) > 9.5
)
It's not elegant, but it runs and thats all I need. Hopes this helps some other unlucky person stuck with legacy Oracle.

you are asking to execute a string or varchar2 as equation, the result is a value of this equation. right?
here is a solution for this, I assume the space in "7 4/5" means "7+ 4/5" or you have to modify the function bellow to meet your requirements.
create or replace function get_equation_val( p_equation varchar2) return number is
eq varchar2(500);
eq_stmt varchar2(500);
eq_result number;
begin
eq := replace(p_equation, ' ', '+');
eq_stmt := 'select ' || eq || ' from dual';
execute immediate eq_stmt
into eq_result;
-- dbms_output.put_line(eq_result);
return(eq_result);
end get_equation_val;
and here is a test for this function:
select get_equation_val('7 4/5') from dual;

Related

Extract TEXT from a CLOB field

I have a CLOB field in my Oracle Database that store TEXT data in the following format:
__99__RU_LOCKED=N;;__99__RU_SUSPENDED=Y;;__17__USER_TYPE=A;;__17__USER_TYPE_610=A;;__17__GUIFLAG=0;;__17__DEFAULT_LANG_610=E;;__17__OUTPUT_DEVICE_46=LOCL;;__17__PRINT_IMMED=G;;__17__DELETE_AFTER_PRINT=D;;__17__CATT=*BLANK;;__17__CATT_46=*;;__17__DEC_FORMAT=*BLANK;;__17__DEC_FORMAT_46=X;;__17__DATE_FORMAT=2;;__17__PARAMETERS=OM_OBJM_NO_DISPLAYX;;__17__MEAS_EASLPFL=0;;__17__USER_GROUP=S1BR22;;__17__VALID_FROM=20080222;;__17__VALID_UNTIL=99991231;;__17__ACCOUNT=37004968;;
I'm using TOAD and while I am creating the query I can read the CLOB field with the following:
--- To read the CLOB field.
select DBMS_LOB.substr(ADD_INFO_MASTER) from USER
This select return me the CLOB field HUMAN READABLE.
My question is: Is there any way to extract the one single value like ACCOUNT value from the line above?
Keep in mind that this CLOB field can variate and the __17__ACCOUNT= will not be in the same place every time. I need a way to extract to locate the ;;__17__ACCOUNT= (this will be a pattern) and extract the the value 37004968.
It is possible to achieve this while performing a query in TOAD?
If you want to deal with CLOB values larger than 4000 symbols length (Oracle 11g) or 32K length (Oracle 12c) then you must use DBMS_LOB package.
This package contains instr() and substr() functions which operates on LOBs.
In your case query looks like that:
with prm as (
select '__17__ACCOUNT' as fld_start from dual
)
select
dbms_lob.substr(
text,
-- length of substring
(
-- position of delimiter found after start of desired field
dbms_lob.instr(text, ';;', dbms_lob.instr(text, prm.fld_start))
-
-- position of the field description plus it's length
( dbms_lob.instr(text, prm.fld_start) + length(fld_start) + 1 )
),
-- start position of substring
dbms_lob.instr(text,prm.fld_start) + length(fld_start) + 1
)
from
text_table,
prm
Query above uses this setup:
create table text_table(text clob);
insert into text_table(text) values (
'__99__RU_LOCKED=N;;__99__RU_SUSPENDED=Y;;__17__USER_TYPE=A;;__17__USER_TYPE_610=A;;__17__GUIFLAG=0;;__17__DEFAULT_LANG_610=E;;__17__OUTPUT_DEVICE_46=LOCL;;__17__PRINT_IMMED=G;;__17__DELETE_AFTER_PRINT=D;;__17__CATT=*BLANK;;__17__CATT_46=*;;__17__DEC_FORMAT=*BLANK;;__17__DEC_FORMAT_46=X;;__17__DATE_FORMAT=2;;__17__PARAMETERS=OM_OBJM_NO_DISPLAYX;;__17__MEAS_EASLPFL=0;;__17__USER_GROUP=S1BR22;;__17__VALID_FROM=20080222;;__17__VALID_UNTIL=99991231;;__17__ACCOUNT=37004968;;'
);
For everyday use with development tools it may be useful to define a function which returns value of field with desired name and use it instead of writing complicated expressions each time.
E.g. :
create or replace function get_field_from_text(
pi_text in clob,
pi_field_name in varchar2
) return varchar2 deterministic parallel_enable
is
v_start_pos binary_integer;
v_field_start varchar2(4000);
v_field_value varchar2(32767);
begin
if( (pi_text is null) or (pi_field_name is null) ) then
return null;
end if;
v_field_start := pi_field_name || '=';
v_start_pos := dbms_lob.instr(pi_text, v_field_start);
if(v_start_pos is null) then
return null;
end if;
v_start_pos := v_start_pos + length(v_field_start);
v_field_value := dbms_lob.substr(
pi_text,
(dbms_lob.instr(pi_text, ';;', v_start_pos) - v_start_pos),
v_start_pos
);
return v_field_value;
end;
Usage:
select get_field_from_text(text,'__17__OUTPUT_DEVICE_46') from text_table
You could use a regular expression to extract the value:
WITH your_table AS (
SELECT '__99__RU_LOCKED=N;;__99__RU_SUSPENDED=Y;;__17__USER_TYPE=A;;__17__USER_TYPE_610=A;;__17__GUIFLAG=0;;__17__DEFAULT_LANG_610=E;;__17__OUTPUT_DEVICE_46=LOCL;;__17__PRINT_IMMED=G;;__17__DELETE_AFTER_PRINT=D;;__17__CATT=*BLANK;;__17__CATT_46=*;;__17__DEC_FORMAT=*BLANK;;__17__DEC_FORMAT_46=X;;__17__DATE_FORMAT=2;;__17__PARAMETERS=OM_OBJM_NO_DISPLAYX;;__17__MEAS_EASLPFL=0;;__17__USER_GROUP=S1BR22;;__17__VALID_FROM=20080222;;__17__VALID_UNTIL=99991231;;__17__ACCOUNT=37004968;;' clob_field FROM DUAL
)
SELECT REGEXP_SUBSTR(clob_field,'__17__ACCOUNT=.*;;')
FROM your_table
Using that you would get "__17__ACCOUNT=37004968;;". You can easily extract the value with SUBSTR.
I think that in Oracle 11g REGEXP_SUBSTR has extra parameters that would let you extract a certain group within the regular expression.
You can use INSTR and SUBSTR with CLOB datatype:
WITH T1 AS (
SELECT '__99__RU_LOCKED=N;;__99__RU_SUSPENDED=Y;;__17__USER_TYPE=A;;__17__USER_TYPE_610=A;;__17__GUIFLAG=0;;__17__DEFAULT_LANG_610=E;;__17__OUTPUT_DEVICE_46=LOCL;;__17__PRINT_IMMED=G;;__17__DELETE_AFTER_PRINT=D;;__17__CATT=*BLANK;;__17__CATT_46=*;;__17__DEC_FORMAT=*BLANK;;__17__DEC_FORMAT_46=X;;__17__DATE_FORMAT=2;;__17__PARAMETERS=OM_OBJM_NO_DISPLAYX;;__17__MEAS_EASLPFL=0;;__17__USER_GROUP=S1BR22;;__17__VALID_FROM=20080222;;__17__VALID_UNTIL=99991231;;__17__ACCOUNT=37004968;;' TEXT FROM DUAL
)
SELECT SUBSTR(TEXT,
INSTR(TEXT, '__17__ACCOUNT=') + LENGTH('__17__ACCOUNT') + 1, -- find the first position of the value
INSTR (TEXT, ';;', INSTR(TEXT, '__17__ACCOUNT=')) - (INSTR(TEXT, '__17__ACCOUNT=') + LENGTH('__17__ACCOUNT') + 1) -- length to read. Difference between the end position (the first ;; after your placeholder) and the value start position (the same value as above)
)
FROM T1;
However I like the REGEXP solution proposed by pablomatico more.

Using string in Oracle stored procedure

When I simply write a query in which it has code like
Select * from ..
where ...
AND gfcid in ( select regexp_substr('1005771621,1001035181'||',','\d+',1,level)
from dual
connect by level <= (select max(length('1005771621,1001035181')-length(replace('1005771621,1001035181',',')))+1
from dual) )
It works.
But I want to make it dynamic query in oracle stored procedure. I did like this:
GDFCID_STRING := ' select regexp_substr('
|| '1005771621,1001035181'
|| ','
|| ','
|| '\d+'
|| ',1,level) from dual connect by level <= (select max(length('
|| '1005771621,1001035181'
|| ')-length(replace('
|| '1005771621,1001035181'
|| ','
|| ','
|| ')))+1 from dual)';
Select * from ..
where ...
AND gfcid in (GDFCID_STRING)
But it does now work.
As far as I understand your problem, you need a method to accept a comma-delimited string as an input, break it into a collection of integers and then compare a number (read: integer) with the values in this collection.
Oracle offers mainly three types of collections- varrays, nested tables and associative arrays. I would explain how to convert a comma-delimited string into a nested table and use it to query or compare.
First, you need to define an object type in the schema. You can write queries using this type only if you define it at schema level.
CREATE OR REPLACE TYPE entity_id AS OBJECT (id_val NUMBER(28));
/
CREATE OR REPLACE TYPE entity_id_set IS TABLE OF entity_id;
/
Next, define a function like this:
FUNCTION comma_to_nt_integer (p_comma_delimited_str IN VARCHAR)
RETURN entity_id_set IS
v_table entity_id_set;
BEGIN
WITH temp AS (SELECT TRIM(BOTH ',' FROM p_comma_delimited_str) AS str FROM DUAL)
SELECT ENTITY_ID(TRIM (REGEXP_SUBSTR (t.str,
'[^,]+',
1,
LEVEL)))
str
BULK COLLECT INTO v_table
FROM temp t
CONNECT BY INSTR (str,
',',
1,
LEVEL - 1) > 0;
RETURN v_table;
END comma_to_nt_integer;
You are done with the DDL required for this task. Now, you can simply write your query as:
SELECT *
FROM ..
WHERE ...
AND gfcid in (table(comma_to_nt_integer(GDFCID_STRING)));
In general you can use
execute immediate v_your_sql_code;
to execute dynamic SQL within PL/SQL, but from your question I'm not really aware what you want to do.
Edit:
y_your_sql_code := 'Select yourColumn from .. where ...AND gfcid in ('||GDFCID_STRING||')';
execute immediate v_your_sql_code into v_result;
You'll have to define v_result in the right datatype, you could use more then one result variable if you need more result columns, you'll need i.e. a complex type if you can retrieve more than one row.

oracle dynamic validation

Consider a table with 3 columns
Value1   operator     value2
abc          =               xyz
1            !=               2
5            >                8
9            <=              11
xyz          is not Null
{null val}   is null
I want to write a genric function which returns the validated result either true or false
by validating value1 with value2 using the operator
or check value1 in case of 'is null' and 'is not null' operators (Last two cases)
You could do something like this with dynamic SQL (note that in reality you'd need to add a bunch of logic to prevent SQL injection attacks)
SQL> ed
Wrote file afiedt.buf
1 create or replace function evaluate( p_val1 in varchar2,
2 p_op in varchar2,
3 p_val2 in varchar2 )
4 return varchar2
5 is
6 l_result varchar2(1);
7 l_sql_stmt varchar2(1000);
8 begin
9 if( p_val2 is not null )
10 then
11 l_sql_stmt := 'select (case when :1 ' || p_op || ' :2 then ''Y'' else ''N'' end) from dual';
12 execute immediate l_sql_stmt
13 into l_result
14 using p_val1, p_val2;
15 else
16 l_sql_stmt := 'select (case when :1 ' || p_op || ' then ''Y'' else ''N'' end) from dual';
17 execute immediate l_sql_stmt
18 into l_result
19 using p_val1;
20 end if;
21 return l_result;
22* end;
SQL> /
Function created.
SQL> select evaluate( 'xyz', 'is not null', null )
2 from dual;
EVALUATE('XYZ','ISNOTNULL',NULL)
--------------------------------------------------------------------------------
Y
SQL> select evaluate( 'abc', '=', 'xyz' )
2 from dual;
EVALUATE('ABC','=','XYZ')
--------------------------------------------------------------------------------
N
Since you are storing the data in a table, that implies that each column is a VARCHAR2. My guess, though, is that you don't always want to use string comparison semantics. For example, the string '9' is greater than the string '11' while the number 9 is less than the number 11. If you want to use something other than string comparison semantics, you'd need to add code that inspected the parameters and applied whatever logic you would like to determine what comparison semantics you want to apply and then generated the appropriate dynamic SQL statement.
I would strongly question the wisdom of the requirement, however. First off, it makes little sense from a data model standpoint to store numeric data in a VARCHAR2 column-- when you find yourself trying to mix string and numeric data in the same column, you've almost always made a data model mistake. I would also be hard-pressed to imagine what business problem you are trying to solve that would involve this sort of dynamic function-- it seems likely that there is a better way to solve whatever problem you are attempting to solve.
It seems to me you can escape dynamic SQL with unwinding logic in a list of rules.
If you need to check one record use something like
with CheckedTable as (select * from t where primary_key = ID)
select count(*) from
(
select * from CheckedTable where operator = '=' and value1 = value2
union all
select * from CheckedTable where operator = '!=' and value1 <> value2
union all
select * from CheckedTable where operator = '>' and value1 > value2
union all
...
select * from CheckedTable where operator = 'is null' and value1 is null
)
So 1 = TRUE and 0 = FALSE.
P.S. If you want to support IN clause you can use this workaround (remove brackets from value2!)
select * from CheckedTable where operator = 'in' and ',' || value2 || ',' like '%,' || value1 || ',%'
proof on SQLFiddle
looking through above question and the results, i came up with some points that we should take care of using string and comparisons:
----->
its possible to compare numeric values with any comparison function ,ie;
=, !=, <, >, <=, >=, IS NULL, LIKE, BETWEEN, IN
but beware of using quotes;
SELECT (CASE WHEN '9' > '12' THEN 'Y' ELSE 'N' END) FROM dual;-- results Y
because, here the implicit positional comparison is done by checking 9>1 (1 in the 12), so the condition 9>1 is true, so results as Y.
The correct way of comparison with numeric is without quotes:
SELECT (CASE WHEN 9 > 12 THEN 'Y' ELSE 'N' END) FROM dual;--results N
-----> its meaningless to compare a string with another with '>' and '<'
-----> we've UTL_MATCH package especially for string matching.
SELECT utl_match.edit_distance( 'abc','abcde' ) FROM dual;--results 2, shows no:of edits required to make string1 to string2.
SELECT UTL_MATCH.EDIT_DISTANCE_similarity('abc', 'abcde') FROM DUAL;-- results 60, shows the similarity percentage of two strings.

Get list of arguments with default value

I use ALL_ARGUMENTS to get list of arguments in oracle 10g, but I can't find out is there default value for argument. Haw can I do it?
you might need to resort to plsql programming in 10g, in the vein of the code sample below. this solution is certainly brute-force in some sense, as you basically write part of a function/procedure declaration parser using very low-level primitives. however, regular expression functions aren't available in 10g either ...
the gist of the code is:
iterate over the source code lines of procedure/function declarations
note the name of the most recently declared routine
analyze all lines containing the keyword 'DEFAULT', getting the argument name and the default value spec (this is not outlined in detail in the code sample).
beware of pitfalls:
multiline declarations
C-style comments containing C-style comment opening strings
( a la /* blah blah / blah blah **/ )
string literals containing keywords
hope that helps anyway, best regards.
the code:
DECLARE
l_arg_and_more VARCHAR2(400);
l_current_unit VARCHAR2(400);
l_default_spec VARCHAR2(400);
l_offset_default BINARY_INTEGER;
l_offset_eoname BINARY_INTEGER;
BEGIN
FOR i IN (
select text
from all_source
where owner = '<name of owner>'
and name = '<object name>'
and type in ( 'PACKAGE', 'PROCEDURE', 'FUNCTION')
and text not like '--%'
order by line
) LOOP
IF i.text LIKE '%FUNCTION%' OR i.text LIKE '%PROCEDURE%' THEN
IF i.text LIKE '%FUNCTION%' THEN
l_current_unit := LTRIM(SUBSTR(i.text, INSTR(i.text, 'FUNCTION') + LENGTH('FUNCTION')), ' ');
l_offset_eoname := INSTR(l_current_unit, ' ');
IF l_offset_eoname = 0 OR l_offset_eoname > INSTR(l_current_unit, '(') THEN
l_offset_eoname := INSTR(l_current_unit, '(');
END IF;
IF l_offset_eoname <> 0 THEN
l_current_unit := SUBSTR(l_current_unit, 1, l_offset_eoname-1);
ELSE
l_current_unit := 'unidentified';
END IF;
END IF;
END IF;
--
IF i.text LIKE '%DEFAULT%' THEN
l_offset_default := INSTR (i.text, 'DEFAULT');
l_arg_and_more := SUBSTR(i.text, 1, l_offset_default - 1);
l_default_spec := SUBSTR(i.text, l_offset_default + LENGTH('DEFAULT') + 1);
--
-- process l_arg_and_more to get the arg name, l_default_spec for the default value
--
END IF;
END LOOP;
END;
I know it is a bit too late but this may help. I'm facing similar problem at this time. Needs more testing...
SELECT SUBSTR(final_str, start_pos+1, (end_pos-start_pos-1) ) dflt_values, start_pos, end_pos
FROM
(
SELECT start_pos, final_end_pos end_pos, MOD(ROWNUM, 2) rno, final_str FROM
(
SELECT distinct start_pos, end_pos, final_str, (CASE WHEN end_pos < start_pos THEN (start_pos*2) ELSE end_pos END) final_end_pos
FROM
(
SELECT Instr(final_str, '=', LEVEL) start_pos, Instr(final_str, ',', LEVEL) end_pos, final_str --<<-- distinct
FROM
(
SELECT RTRIM(SUBSTR(str, 1, e_start), ',')||RTRIM(SUBSTR(str, e_start, e_end-e_start), ')') final_str
, e_start
, e_end
, e_end-e_start
FROM
(
SELECT str, Instr(str, ',', 1, 5) e_start, Instr(str, ')') e_end
FROM
(
SELECT REPLACE(REPLACE(REPLACE(SUBSTR(str, INSTR(str, '(')+1), chr(32), ''), chr(10), chr(32) ), 'DEFAULT', ':=') str
FROM
(
SELECT 'PROCEDURE Some_Proc(p_arg1 VARCHAR2:= ''Arg1''
, p_arg2 VARCHAR2:= ''Arg2''
, p_arg3 DATE DEFAULT ''SYSDATE-90''
, p_arg4 VARCHAR2 DEFAULT ''NULL''
, p_arg5 NUMBER := ''10'')' str
FROM dual
))))
CONNECT BY LEVEL <= LENGTH(final_str)
)
WHERE start_pos > 0
ORDER BY start_pos, end_pos
))
WHERE rno > 0
/
In the view SYS.ALL_ARGUMENTS there is a column named DEFAULT_VALUE, but even in 11g it's type is LONG and is a headache to work around and convert to CLOB.
The "easiest" (not the best) way is to create a table from this view, using TO_LOB to convert this field to a CLOB and work with that.

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.

Resources