I want to create a trigger which generates an 18 digit code based on the value of several other columns in the table.
R-MU-WEST-RJS-9697 So basically my last 4 digit code is the STORE_CODE based on which I want to compare and insert that 18 digit code against it the column named as CODE_18DIGIT
Below is the select statement that generates the code
SELECT 'R-'
|| CASE
WHEN INSTR (TRIM (r.state), ' ') > 1
THEN
SUBSTR (r.state, 1, 1)
|| SUBSTR (r.state, INSTR (r.state, ' ') + 1, 1)
ELSE
UPPER (SUBSTR (r.state, 1, 2))
END
|| '-'
|| UPPER (SUBSTR (r.zone_name, 1, 4))
|| '-'
|| r.format_code
|| '-'
|| SUBSTR (r.store_code, 1, 4)
FROM tbl_rrsoc_store_info r
WHERE r.ISACTIVE = 'Y';
and the table description of tbl_rrsoc_store_info is below
Name Null Type
--------------------------- -------- --------------
RRSOC_ID NOT NULL NUMBER
STORE_CODE NOT NULL NVARCHAR2(55)
STATE NVARCHAR2(55)
CITY NVARCHAR2(55)
CODE_18DIGIT NVARCHAR2(18)
Architecturally, this is not a particularly good design. You're violating basic rules of normalization by storing redundant data. It would be much less objectionable to just create a view that computes the code or to add a computed column to the table instead. But it sounds like you have decided that you want to use the trigger-based solution.
For simplicity, I'd create a function (note that the code you posted references multiple columns that don't exist in the table definition you posted. My guess is that the columns exist and were omitted from your table definition but maybe you changed the column names in either your query or your table definition and didn't change them in the other).
create or replace function make_18digit_code(
p_state tbl_rrsoc_store_info.state%type,
p_zone_name tbl_rrsoc_store_info.zone_name%type, -- Note that your table definition doesn't include this column
p_format_code tbl_rrsoc_store_info.format_code%type, -- Or this column
p_store_code tbl_rrsoc_store_info.store_code%type
)
return tbl_rrsoc_store_info.code_18digit%type
is
begin
return 'R-' ||
CASE
WHEN INSTR (TRIM (p_state), ' ') > 1
THEN
SUBSTR (p_state, 1, 1)
|| SUBSTR (p_state, INSTR (p_state, ' ') + 1, 1)
ELSE
UPPER (SUBSTR (p_state, 1, 2))
END
|| '-'
|| UPPER (SUBSTR (p_zone_name, 1, 4))
|| '-'
|| p_format_code
|| '-'
|| SUBSTR (p_store_code, 1, 4);
end;
Then you can simply call the function in the trigger
create or replace trigger trg_generate_code
before insert or update on tbl_rrsoc_store_info
for each row
begin
:new.code_18digit := make_18digit_code( :new.state,
:new.zoe_name,
:new.format_code,
:new.store_code );
end;
Related
I have a table name as Can
Internal_id not null VARCHAR2(10)
RCR not null VARCHAR2(10)
IVC. not null VARCHAR2(10)
Currently there no values for column RCR
The values stored in column Internal_Id are like
SER00001
SER00002 upto SER00093
I need to update RCR correspondingly to internal_id
Like for SER00001 it will be SERSRV00001
SER00002 it will be SERSRV00002
and so on
I can do this like update Can where
RCR = lpad(Internal_id,3)||'SRV'||substr(Internal_id,4,6) where Internal_id = 'SER00001'
How to update in loop instead of writing so many update statements
update MyTable set RCR = substr(Internal_id, 1, 3) || "SRV" || substr(Internal_id, 4);
You can do a select first to make sure it does the right thing:
select substr(Internal_id, 1, 3) || "SRV" || substr(Internal_id, 4) from MyTable;
Ofcourse the right way to solve your query is using a SQL statement which #Kusalananda mentioned. But incase you specifically wanted to do it in loop(Oracle PL/SQL) then you can use this way as well:
begin
for i in (select * from can)
loop
update can
set RCR = substr(i.Internal_id, 1, 3) || "SRV" || substr(i.Internal_id, 4)
where Internal_id = i.Internal_id;
commit;
end loop;
end;
Today, I came across a funny piece of code that I think should not compile. It uses an SELECT ... INTO clause within a FOR r IN ... LOOP. Here's a script that compiles on Oracle 11i. The script is a shortened version of actual PL/SQL code compiled in a package, runing in production.
create table tq84_foo (
i number,
t varchar2(10)
);
insert into tq84_foo values (1, 'abc');
insert into tq84_foo values (2, 'def');
declare
rec tq84_foo%rowtype;
begin
for r in (
select i, t
into rec.i, rec.t -- Hmm???
from tq84_foo
)
loop
dbms_output.put_line('rec: i= ' || rec.i || ', t=' || rec.t);
end loop;
end;
/
drop table tq84_foo purge;
The output, when run, is:
rec: i= , t=
rec: i= , t=
I believe 1) I can safely remove the INTO part of the select statement and 2) that this construct should either be invalid or exhibits at least undefined behaviour.
Are my two assumptions right?
Your assumptions are partly right:
1) Yes, you can safely remove the INTO part of the SELECT statement. But you should change the line in the loop to this format:
dbms_output.put_line('rec: i= ' || r.i || ', t=' || r.t);
That way it will get the data out of the r variable
2) The problem with this code is that the syntax of the SELECT ... INTO should fail if the query return more than one row. If it does not fail so it might be a bug and will have unexpected behaviour.
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;
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.
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.