I would suppose this can be best done with regexp, as that's probably the shortest way, but anything else as a suggestion is also good.
Say, I have a string in PL/SQL, and want to capitalize every Nth character if its small, and lower if its capital. For example, every fifth letter.
I'd want to examine the possibilities to achieve that.
Thanks.
Since I have nothing better to do on a Saturday night (after Dark Matter of course) I decided to challenge my brain a bit. So after learning that SQLFiddle is broken, I installed Oracle 11g R2 Express Edition on my computer to work this out... yeah, I'm really that bored.
Directly from a table:
drop table test;
create table test (nbr number, txt varChar2(26));
insert into test (nbr, txt) values (1,'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
insert into test (nbr, txt) values (2,'abcdefghijklmnopqrstuvwxyz');
insert into test (nbr, txt) values (3,'ABCDEFGHIJklmnopqrstUVWXYZ');
insert into test (nbr, txt) values (4,'abcdefghijKLMNOPQRSTuvwxyz');
select
nbr,
(
select
listAgg
(
case subStr(subStr(t.txt,((level - 1) * 5) + 1,5),5,1)
when upper(subStr(subStr(t.txt,((level - 1) * 5) + 1,5),5,1))
then subStr(subStr(t.txt,((level - 1) * 5) + 1,5),1,4) ||
lower(subStr(subStr(t.txt,((level - 1) * 5) + 1,5),5,1))
when lower(subStr(subStr(t.txt,((level - 1) * 5) + 1,5),5,1))
then subStr(subStr(t.txt,((level - 1) * 5) + 1,5),1,4) ||
upper(subStr(subStr(t.txt,((level - 1) * 5) + 1,5),5,1))
else subStr(t.txt,((level - 1) * 5) + 1,5)
end
)
within group (order by level)
from
dual
connect by
subStr(t.txt,((level - 1) * 5) + 1,5) is not null
) as "newTxt"
from
test t
;
Or you can use a PL/SQL function:
declare
function inverseCase(
txt varChar2,
nbr number
)
return varChar2 as
newTxt varChar2(254);
txtSeg varChar2(254);
segA varChar2(254);
segB varChar2(254);
begin
for i in 0..floor(length(txt)/nbr) loop
txtSeg := subStr(txt, (i * nbr) + 1, nbr);
segA := subStr(txtSeg, 1, nbr - 1);
segB := subStr(txtSeg, nbr, 1);
newTxt := newTxt || case segB
when upper(segB) then segA || lower(segB)
when lower(segB) then segA || upper(segB)
else txtSeg
end;
end loop;
return newTxt;
end;
begin
dbms_output.put_line(inverseCase('ABCDEFGHIJKLMNOPQRSTUVWXYZ',5));
dbms_output.put_line(inverseCase('abcdefghijklmnopqrstuvwxyz',5));
dbms_output.put_line(inverseCase('ABCDEFGHIJklmnopqrstUVWXYZ',5));
dbms_output.put_line(inverseCase('abcdefghijKLMNOPQRSTuvwxyz',5));
end;
Both return the following output.
nbr newTxt
1 ABCDeFGHIjKLMNoPQRStUVWXyZ
2 abcdEfghiJklmnOpqrsTuvwxYz
3 ABCDeFGHIjklmnOpqrsTUVWXyZ
4 abcdEfghiJKLMNoPQRStuvwxYz
Which I believe to be the text you are looking for.
It's been a while and I forgot how much fun this website is when I'm bored!
As one of options, you can check 5-th character using CASE statement
SELECT ( case
when SUBSTR(fieldname, 5,1) = upper (SUBSTR(fieldname, 5,1)) then lower (SUBSTR(fieldname, 5,1))
when SUBSTR(fieldname, 5,1) = lower (SUBSTR(fieldname, 5,1)) then upper (SUBSTR(fieldname, 5,1))
else SUBSTR(fieldname, 5,1) ) 5char
FROM YourTable
The problem will occur afterwards as you'll have to divide string on 3, change letter case and concatenate them back. This will be bulky. Or play around with REPLACE function, but again, as far as I know it does not work with particular character position, only with substrings by pattern, thus you will end up with dividing and concatenating again.
Regexp probably least painfull
That is if we limit transformation to be in SELECT statement only, no PL/SQL procedures
Related
I have this Maths formula which i tried to convert into Oracle function.
This is how it must work, taxableIncome loops through the table,it picks the first line on the table and if it is more than CumulativeAmount,it subtracts CumulativeAmount from taxableIncome and keeps the remainder,it multiplies the CumulativeAmount and Percentage to get a value(x),takes the remainder and go next line in the table and subtract the CumulativeAmount from the remainder.when the remainder is less than the CumulativeAmount,it will apply the 17.5 on the remainder.Later I will sum the (x) values to get a total value. The values are not calculating properly.
CREATE OR REPLACE function CalculateIncomeTax(periodId NVARCHAR2,
employeeId NVARCHAR2,paygroupcode PAYROLL_MASTER.PAY_PAY_GROUP_CODE%type,
taxableIncome NUMBER)
return NUMBER
AS
IncomeTax NUMBER (10,2);
BEGIN
SELECT
coalesce(SUM(
CASE WHEN (taxableIncome > T.TAX_CUMMULATIVE_AMOUNT) THEN (taxableIncome -
T.TAX_CUMMULATIVE_AMOUNT)* (T.TAX_PERCENTAGE/ 100)
ELSE 0.00 END
) ,0)
INTO IncomeTax
FROM TAX_LAW T JOIN PAY_GROUP P ON P.PAY_FORMULA_ID =T.TAX_FORMULA_ID
WHERE P.PAY_CODE = paygroupcode;
return IncomeTax;
end;
/
For example:
taxableIncome= 1000,the function must loop on the first line on the
table CummulativeAmount=261,so 1000-261=739, and 0*739=0,0 is
value(x),
next loop 739>70,739 is the remainder,so 739-70=669, and (5/100)*70
=3.5,3.5 is value(x),
next loop 669>100,669-100=569, (10/100)*100=10,10 is value(x),
now 569 < 2810, so (17.5/100)*569=99.575,99.575 is value(x).
so we sum all x =0+3.5+10+99.575= 113.075
if taxableIncome = 300,300 is greater than 261 in the table so we will use the percentage of 0 on it,but we will get 0.now (300-261) is less than 70 so the percentage of 5 cannot be used on it so the Incometax is 0.But 17.5 will be applied on (300-261) to get 6.825,Total IncomeTax is 6.825.This is the logic behind the calculations.I want to used while statement on calculation it.
CREATE OR REPLACE function CalculateIncomeTax2(taxableIncome NUMBER)
return NUMBER
AS
IncomeTax NUMBER (10,2) := 0;
TaxableRemainder NUMBER := taxableIncome;
BEGIN
for r in (select tax_id, tax_percentage, tax_cummulative_amount
from incometax
order by tax_id)
loop
if TaxableRemainder > r.tax_cummulative_amount then
TaxableRemainder := TaxableRemainder - r.tax_cummulative_amount;
incometax := incometax + (r.tax_cummulative_amount * r.tax_percentage / 100);
else
incometax := incometax + (TaxableRemainder * r.tax_percentage / 100);
exit;
end if;
end loop;
return IncomeTax;
end;
/
i have a string 'MCDONALD_YYYYMMDD.TXT' i need to use regular expressions and append the '**' after the letter 'D' in the string given . (i.e In the string at postion 9 i need to append '*' based on a column value 'star_len'
if the star_len = 2 the o/p = ''MCDONALD??_YYYYMMDD.TXT'
if the star_len = 1 the o/p = ''MCDONALD?_YYYYMMDD.TXT'
with
inputs ( filename, position, symbol, len ) as (
select 'MCDONALD_20170812.TXT', 9, '*', 2 from dual
)
-- End of simulated inputs (for testing purposes only, not part of the solution).
-- SQL query begins BELOW THIS LINE.
select substr(filename, 1, position - 1) || rpad(symbol, len, symbol)
|| substr(filename, position) as new_str
from inputs
;
NEW_STR
-----------------------
MCDONALD**_20170812.TXT
select regexp_replace('MCDONALD_YYYYMMDD.TXT','MCDONALD','MCDONALD' ||
decode(star_len,1,'*',2,'**'))
from dual
This is how you could do it. I don't think you need it as a regular expression though if it is always going to be "MCDONALD".
EDIT: If you need to be providing the position in the string as well, I think a regular old substring should work.
select substr('MCDONALD_YYYYMMDD.TXT',1,position-1) ||
decode(star_len,1,'*',2,'**') || substr('MCDONALD_YYYYMMDD.TXT',position)
from dual
Where position and star_len are both columns in some table you provide(instead of dual).
EDIT2: Just to be more clear, here is another example using a with clause so that it runs without adding a table in.
with testing as
(select 'MCDONALD_YYYYMMDD.TXT' filename,
9 positionnum,
2 star_len
from dual)
select substr(filename,1,positionnum-1) ||
decode(star_len,1,'*',2,'**') ||
substr(filename,positionnum)
from testing
For the fun of it, here's a regex_replace solution. I went with a star since that what your variable was called even though your example used a question mark. The regex captures the filename string in 2 parts, the first being from the start up to 1 character before the position value, the second the rest of the string. The replace puts the captured parts back together with the stars in between.
with tbl(filename, position, star_len ) as (
select 'MCDONALD_20170812.TXT', 9, 2 from dual
)
select regexp_replace(filename,
'^(.{'||(position-1)||'})(.*)$', '\1'||rpad('*', star_len, '*')||'\2') as fixed
from tbl;
I am converting some code in Access over to Oracle, and one of the queries in Access uses a table that I am unable to use in Oracle. I am unable to create new tables, so I am trying to figure out a way to use the logic behind the table in the FOR section of my select.
The logic of the table is similar to:
FOR i = 1 To 100
number = number + 1
.AddNew
!tbl_number = number
NEXT i
I'm trying to convert this to oracle, and so far I have:
FOR i in 1 .. 100 LOOP
number := number + 1;
--This is where I am stuck; How do I simulate the table part
END LOOP;
I was thinking a cursor or a record would be the answer, but I can't seem to figure out how to implement that. In the end I basically want to have:
SELECT
table.number
FROM
(
--My for loop logic
) table
EDIT
The calculation is a bit more complicated; that was just an example. They aren't actually sequential, and there isn't really a pattern to rows.
EDIT
Here is a more complicated version of the for loop which is closer to what I'm actually doing:
FOR i in 1 .. 100 LOOP
number1 := number1 + 7;
number2 := (number2 + 8) / number1;
--This is where I am stuck; How do I simulate the table part
END LOOP;
You could use a recursive query (assuming you are on Oralce 11gR2 or later):
with example(idx, number1, number2) as (
-- Anchor Section
select 1
, 1 -- initial value
, 2 -- initial value
from dual
union all
-- Recursive Section
select prev.idx + 1
, prev.number1 + 7
, (prev.number2 + 8) / prev.number1
from example prev
where prev.idx < 100 -- The Guard
)
select * from example;
In the Anchor section set all the values for your first record. Then in the Recursive section setup the logic to determine the next records values as a function of the prior records values.
The Anchor section could select the initial values from some other table rather than being hard coded as in my example.
The recursive section needs to select from the named subquery (in this case example) but may also join to other tables as needed.
You need to generate a set with sequential integer numbers. Maybe you can use this (for Oracle 10g and above):
SELECT
ROWNUM NUM
FROM
DUAL D1,
DUAL D2
CONNECT BY
(D1.DUMMY = D2.DUMMY AND ROWNUM <= 100)
For frequent period of time, i'm doing same process of updating few tables with consecutive values. Hope to make this simple an example below,
UPDATE Table_1 SET vchar_val = REPLACE (vchar_val, '.360/', '.370/'),
WHERE vchar_val LIKE 'http://services%.360/%'
AND c_version IN ('ALL', 'N/A', '37.0');
For 37th version, i'm replacing the places where '36' with '37'. The same i'll do for '38' and it continues...This is making me bore and time consuming process as i've 50 plus records like this for different tables for which i'm manually editing all update queries.
So i planned to write a scheduler which i can trigger for each version by giving input as previous version and current version, in which i'll put all this update queries.
Here comes the place where i struck, if i go by giving version values as input, i'm supposed to introduce local parameter to store. HOW CAN I ASSIGN THAT LOCAL VARIABLE TO MY UPDATE SCRIPT.??????
I go with concatenate the texts like
REPLACE (vchar_val, '.'+ #PrevVersion +'/', '.'+ #CurrentVersion +'/')
PrevVer** & CurrentVer** is my local variable with values .360 & .370 resp.
I think i miss quiet piece of code in this snippet as i'm getting error when running this.
Please help me guys to rearrange this code to automate the query or ur comments to do this job in any alternative way...
Thanks
-VIno
begin
for i in 36 .. 50 loop
UPDATE Table_1
SET vchar_val = REPLACE (vchar_val, '.'|| i ||'0/', '.'|| i+1 ||'0/')
WHERE vchar_val LIKE 'http://services%.'|| i ||'0/%'
AND c_version IN ('ALL', 'N/A', i+1 ||'.0');
end loop;
end;
Of course you could do that in one single update with some fancy reg_exp, but I leave that exercice to another fellow stackoverflower :)
Local variable:
declare
my_local_valiable number;
begin
update my_table ... ;
commit;
end;
Autoincrement: sequence
update table_1 set my_field = '.' || my_sequence.nextval where ...;
UPD
Number always in the same position (for example, 2 digits, 10th and 11th symbols in the string):
update table_1 set my_field = substr(my_field, 1, 9) || to_char(to_number(substr(my_field, 10, 2)) + 1) || substr(my_field, 12);
This converts string 'abracadab29ra' to 'abracadab30ra'.
The same with replace:
update table_1 set my_field = replace(my_field, substr(my_field, 10, 2), to_char(to_number(substr(my_field, 10, 2)) + 1));
Number always follows after a string 'value = ' and has two digits:
update table_1 set my_field = replace(my_field, substr(my_field, instr(my_field, 'value = ', 1) + 8, 2), to_char(to_number(substr(my_field, instr(my_field, 'value = ', 1) + 8, 2)) + 1))
This converts string 'my value = 33' to 'my value = 34'.
I have a temp table with 13,000 rows. Most of the rows have a numeric price (100) but some are quoted in 32nds, i.e. 100-30 = 100 + 30/32 = 100.9375. (Some even have fractions of a 32th)
I am opening a cursor FOR UPDATE and iterating over the temp table. It takes so long to execute, I am not even sure it is working (my DBA says the exec plan looks 'strange')
Can anyone suggest why this is so ridiculously slow?
Better still, could anyone suggest a better alternative? I have control over how the temp table is created, but I don't fancy trying to compact the logic for 100-30 to 100.9375 into a single update statement.
I'd like to write a function to do this, but as far as I can tell, I have to install Java to enable UDFs?!
Lastly, any idea why Sybase is such an awful, primitive database even at version 12?
My stored proc:
DECLARE cur CURSOR FOR SELECT ticket_no, price_st, price, cur FROM #t FOR UPDATE OF price
DECLARE
#ticket_no INT,
#price_st VARCHAR(20),
#price FLOAT,
#int FLOAT,
#32s FLOAT,
#frac VARCHAR(6),
#num FLOAT,
#denom FLOAT
OPEN cur
FETCH cur INTO #ticket_no, #price_st
WHILE (##SQLSTATUS != 2)
BEGIN
IF isnumeric(#price_st) = 1
BEGIN
SELECT #price = convert(FLOAT, #price_st)
END
ELSE
BEGIN
-- Convert a price like '99-22 3/4' to
-- 99 + (22/32) + (3/4 * 1/32)
SELECT #int = convert(FLOAT, substring(#price_st, 1, charindex('-', #price_st)-1))
SELECT #32s = convert(FLOAT, substring(#price_st, charindex('-', #price_st)+1, 2))
SELECT #frac = substring(#price_st, charindex(' ', #price_st)+1, 10)
SELECT #num = convert(FLOAT, substring(#frac, 1, charindex('/', #frac)-1))
SELECT #denom = convert(FLOAT, substring(#frac, charindex('/', #frac)+1, 3))
SELECT #price = #int + (#32s / 32) + (#num / (#denom * 32))
END
UPDATE #t SET price = #price WHERE CURRENT OF cur
FETCH cur INTO #ticket_no, #price_st
END
CLOSE cur
DEALLOCATE cur
Ah! I had a column in the table, with the same name as the cursor:
DECLARE cur CURSOR FOR SELECT ticket_no, price_st, price, cur FROM #t FOR UPDATE OF price
^ ^
This seems to put the Sybase server into a tailspin...
Cursors in Sybase are slower than SET based operations. Despite your reluctance I doubt you will do better than putting all the logic into one Update statement as below.
UPDATE #t
SET price =
CASE
WHEN isnumeric(price_st) = 1
THEN CONVERT(FLOAT, price_st)
ELSE
CONVERT(FLOAT, substring(price_st, 1, charindex('-', price_st)-1)) +
(CONVERT(FLOAT, substring(price_st, charindex('-', price_st)+1, 2)) / 32) +
(CONVERT(FLOAT, substring(substring(price_st, charindex(' ', price_st)+1, 10), 1, charindex('/', substring(price_st, charindex(' ', price_st)+1, 10))-1)) /
(CONVERT(FLOAT, substring(substring(price_st, charindex(' ', price_st)+1, 10), charindex('/', substring(price_st, charindex(' ', price_st)+1, 10))+1, 3)) * 32))
END