Find range of values between 2 columns in Oracle DB - oracle11g

Hi I have a table with 2 columns with range, so for e.g If Range Start = ABC1/000/0/0000 and Range END = ABC1/000/0/1022 .
I have to get all the values between this range and then join this with another table. Can you let me know how can I get all the values in DUAL table. I am using Oracle 11g.
Basically I need to make a list with first value as ABC1/000/0/0000 second as ABC1/000/0/0001 till ABC1/000/0/1022.

I have no idea what you mean by "storing values temporarily in DUAL". DUAL is a singe column table with a single value!
However, something like this might be what you want. If its not, then perhaps you could elaborate on your problem a little further
select blah
from another_table
where somekey in
( select blah
from table
where col between <rangeStart> and <rangeEnd>
)

So, it seems you need a few things.
Separate the "last value" from a slash-separated string, such as
ABC1/000/0/0000. It is best to do this with standard substr() and
instr() functions, not with regular expressions (for faster
execution). In instr() we can use a negative argument for
occurrence, to indicate "counting from the end of the string".
Something like this:
select range_from, substr(range_from, instr(range_from, '/', -1) + 1
from ...
Actually, you will need to convert this to a number with to_number() for further processing, and you will also need to capture the substring up to the last slash (similar use of substr() and instr(). And you will need to do the same for range_to.
Generate all the numbers from the first value to the last value. This is easily done with a connect by level query (hierarchical query). Some care must be taken since we may need to do this for several input rows (input ranges) at once.
Then put everything back together and use the result in further processing.
I will assume that the range_from string contains at least one slash, that the substring between the last slash and the end of the string represents a non-negative integer in character format, and the range_to similarly contains at least one slash and the substring from the last slash to the end of the string represents a non-negative integer. It is your responsibility to guarantee that this integer is greater than or equal to the one from range_from. In the output I will use the same substring UP TO the last slash as I find in range_from; if the requirement is that range_to must have the same initial substring, it is your responsibility to guarantee that.
I will also assume that the width (number of characters) of the "number" part (the last token in the strings) is not known beforehand and must be calculated in the query.
with
test_data( id, range_from, range_to ) as (
select 1, 'ABC1/000/0/2033', 'ABC1/000/0/2035' from dual union all
select 2, 'xyz/33/200' , 'xyz/33/200' from dual union all
select 3, '300/LMN/000' , '300/LMN/003' from dual
)
-- end of test data; SQL query begins below this line
select id, stub || lpad(to_char(from_nbr + level - 1), len, '0') as val
from (
select id, stub, length(from_str) as len, to_number(from_str) as from_nbr,
to_number(to_str) as to_nbr
from (
select id, substr(range_from, 1, instr(range_from, '/', -1)) as stub,
substr(range_from, instr(range_from, '/', -1) + 1) as from_str,
substr(range_to , instr(range_to , '/', -1) + 1) as to_str
from test_data
)
)
connect by level <= 1 + to_nbr - from_nbr
and prior id = id
and prior sys_guid() is not null
order by id, level -- if needed
;
ID VAL
-- --------------------
1 ABC1/000/0/2033
1 ABC1/000/0/2034
1 ABC1/000/0/2035
2 xyz/33/200
3 300/LMN/000
3 300/LMN/001
3 300/LMN/002
3 300/LMN/003

Related

WRBTR field calculation inside CASE throws error for max decimal places

I have following select:
SELECT FROM bseg
LEFT JOIN aufk ON ( ltrim( aufk~aufnr, '0' ) = bseg~zuonr )
JOIN bkpf ON bkpf~belnr = bseg~belnr AND bkpf~gjahr = bseg~gjahr AND bkpf~bukrs = bseg~bukrs
FIELDS bseg~bukrs, bseg~bschl, bseg~wrbtr, bseg~h_hwaer
INTO TABLE #DATA(output).
When the select is complete I loop over the output table making a calculation when bschl = '50'.
LOOP AT output ASSIGNING FIELD-SYMBOL(<output>) WHERE bschl = '50'.
<output>-wrbtr = <output>-wrbtr * ( -1 ).
ENDLOOP.
Since ABAP 7.4 I could use CASE statements in the SQL select.
This is what I want to use to get rid of the loop.
SELECT FROM bseg
LEFT JOIN aufk ON ( ltrim( aufk~aufnr, '0' ) = bseg~zuonr )
JOIN bkpf ON bkpf~belnr = bseg~belnr AND bkpf~gjahr = bseg~gjahr AND bkpf~bukrs = bseg~bukrs
FIELDS bseg~bukrs,
CASE
WHEN bseg~bschl = '50' THEN bseg~wrbtr * ( -1 )
ELSE bseg~wrbtr
END AS bseg~wrbtr, bseg~h_hwaer
INTO TABLE #DATA(output).
This is on how I would deal with the requirements described above.
Unfortunately I get an error message:
The maximum possible number of places in the expression starting with WRBTR is 34 places with 2 decimal places.
There can be, however, no more than 31 places and 14 decimal places.`
I also tried to cast bseg~wrbtr:
WHEN bseg~bschl = '50' THEN CAST( bseg~wrbtr * ( -1 ) )
-> ")" is invalid here (due to grammar).
Or
WHEN bseg~bschl = '50' THEN CAST( bseg~wrbtr AS test ) * ( -1 )
-> "TEST" is invalid here (due to grammar).
Does someone know how to deal with this?
My answer is specific to setting a sign via * -1. It doesn't apply to multiplications with other numbers.
In ABAP 7.52 and S/4HANA 1709, BSEG-WRBTR is still a packed-7-bytes number including 2 decimals, and except END AS bseg~wrbtr which leads to the error "~" is invalid here (due to grammar) and must be replaced with END AS wrbtr, the syntax is valid in my system.
In my system, the inline declaration of the output table chooses a packed-13-bytes number including 2 decimals. It's the multiplication that makes the number of digits in the output table multiplied by 2 (from 7 bytes to 13 bytes). As a comparison, an addition would only declare a packed-8-bytes number.
I guess you have a more recent S/4HANA version with BSEG-WRBTR having many more digits (feature called "Amount Field Length Extension"), it's why the multiplication makes the inline declaration produces an invalid type with too many digits.
Workaround: if you sign without multiplying, it will keep the same number of digits (packed-7-bytes number in my case), and this syntax should also work in your case:
CASE bseg~bschl
WHEN '50' THEN - bseg~wrbtr "<=== negative sign, is not the same logic as * -1
ELSE bseg~wrbtr
END AS wrbtr
EDIT Dec 30th - I didn't find a clear reference in the ABAP documentation how the inline types are calculated for arithmetic SQL expressions, it's by searching "up" from the behavior I experienced that I could find a logical way "down":
SELECT, INTO target - #DATA(dobj):
The ABAP type to which the result type of an SQL expression is assigned is used for this expression.
sql_exp - sql_arith (it concerns +, -, * and /):
Alongside any integer operands (see above), decimal expression have at least one operand with the type DEC, CURR, or QUAN or p with decimal places. The operator / is not allowed in decimal expressions. The result has the type DEC with the length 31 and a maximum of 14 decimal places. Using the associated assignment rule, it can be assigned to all numeric ABAP types whose value range is large enough, except for decimal floating point numbers.
SELECT, Assignment Rules:
If the target field has a numeric data type, the value of the result field is converted to this data type and the value range of the target field must be large enough. Here, any surplus decimal places in result fields of the type CURR, DEC, or QUAN (numbers in the BCD format) are cut off.
The proper CASE syntax:
CASE bseg~bschl
WHEN '50' THEN bseg~wrbtr * ( -1 )
ELSE bseg~wrbtr
END AS bseg~wrbtr
Move bseg~bschl right after case and after WHEN mention only the values for equality
The results of the calculation of CAST( bseg~wrbtr AS D34N ) * CAST( -1 AS D34N ) in your CASE are put into data object of type calculation type.
According to the docu, the calculation type for the WRBTR (ABAP type P) is also P, but with important remark:
A calculation type p in assignments to an inline declaration can produce the data type p with length 8 and no decimal places and this can produce unexpected results and raise exceptions
SOLUTION: remove the inline declaration INTO TABLE #DATA(output) from your select query and declare your itab in advance with a proper accuracy.
Here is my working solution for this problem.
CASE bseg~bschl
WHEN '50' THEN CAST( bseg~wrbtr AS D34N ) * CAST( -1 AS D34N )
ELSE CAST( bseg~wrbtr AS D34N )
END AS wrbtr, bseg~h_hwaer,

What's the best way to extract 2nd number from a string

I have something like below stored in a table column. I need only 133 extracted from this.
015.133.Governmental Affairs
When I do
select regexp_substr('015.133.Governmental Affairs', '\.*+[[:digit:]]+*',1,2) from dual;
The result is .133
If I do
regexp_substr('015.133.Governmental Affairs', '\*+[[:digit:]]+*',1,2)
it returns nothing. What's correct expression here?
The trick with coming up with a good regex is to be able to explain it in plain language first.
Editing to explain better hopefully.
Here I am matching zero or more digits where followed by a literal period. The 4th argument to REGEXP_SUBSTR (2) is which occurrence of this pattern to match on. Note the pattern consists of 2 groups as defined by being surrounded by parentheses. The 6th argument to REGEXP_SUBSTR says when a match is found to return the 1st subgroup (the numbers, not the period), if you put a 2 there you'd get the period that follows the number 133.
SELECT REGEXP_SUBSTR('015.133.Governmental Affairs', '([[:digit:]]*?)(\.)', 1, 2, NULL, 1) AS nbr
FROM dual;
NBR
---
133
1 row selected.
Here's something adapted from this question: How to extract group from regular expression in Oracle?
SELECT REGEXP_REPLACE(
'015.133.Governmental Affairs',
'^[[:digit:]]+\.([[:digit:]]+)\..*',
'\1'
) FROM DUAL;
The regex looks for a string that starts with a series of digits, then ., then more digits, then another ., then the rest of the string. It then replaces the entire match (which is the entire string) with \1, which is whatever was in that second set of digits, inside the parentheses.

SQLite3 - Calculated SELECT with padding and concatenation

I have the following SQLite table (a stub of the real table which has a few other columns)
CREATE TABLE IF NOT EXISTS fingers(id INTEGER,intLL INTEGER,fracLat INTEGER,fracLng INTEGER,PRIMARY KEY(id)) WITHOUT ROWID;
A typical entry in this table would be along the lines of
INSERT INTO fingers(id,intLL,fracLat,fracLng) VALUES(1,12899,42513,4025);
From time-to-time I need to query this table to pull out rows for matching intLL values in such a way that a calculated value meets a variable condition. For example
SELECT * FROM fingers WHERE intLL = 12899 AND ('8508' = (CAST((ROUND(CAST(fracLat AS REAL)/500))
AS INTEGER) || CAST((ROUND(CAST(fraCLng AS REAL)/500)) AS INTEGER)));
Explanation
Transform the fractLat and fracLng columns by dividing them by 10,250 or 500. The CAST AS REAL is required to prevent the default integer division that would be performed by SQLite
Round the decimal result to the closest integer. After rounding you will by default get a value with a trailing .0. The CAST AS INTEGER ensures that this is removed
Concatenate the two parts. The concatenation is going wrong. In the present case the concatenated result would be 858 which is not what I want
Compare against an incoming value: 8508 in this case.
My questions
How can I pad the two parts with 0s when required prior to concatenation so as to ensure that they have the same number of digits
Is there a simpler way of achieving this?
One way to pad 0s is to concatenate 00 at the start of the number and with SUBSTR() return the last 2 chars.
Also, you can divide by 500.0 to avoid integer division:
SELECT * FROM fingers
WHERE intLL = 12899
AND '8508' = SUBSTR('00' || CAST(fracLat / 500.0 AS INTEGER), -2) ||
SUBSTR('00' || CAST(fraCLng / 500.0 AS INTEGER), -2)
Another way to do it is with the function printf() which formats a number:
SELECT * FROM fingers
WHERE intLL = 12899
AND '8508' = printf('%02d', fracLat / 500.0) ||
printf('%02d', fraCLng / 500.0)
See the demo.

How to cast a column into decimal of varying significant digits in Oracle

I have a column that is stored in ###0.0000000000 format. In a report I'm generating I need it to only show a few significant digits. Problem is the number needed changes based on the product with a default of 2. There's a column in another table that provides the required digits per each product.
I've tried a few things so far but it seems to not like it and throws a syntax error.
Cast(A.Price as Numeric(10,coalesce(B.Sig_Digits,2)))
That threw an error so I tried making the coalesce part a column and aliasing it in case the coalesce broke it, and that didn't work either. Round will take a column as an argument but I don't want it to round. Other than an ugly
case when Sig_digits = 1 then to_char(price,'###0.0') when Sig_digits = 2...
etc. what other options are there? This is a very large report, with 100+ columns and a few million rows so I'd prefer to not do the case when.
Use TO_CHAR with RPAD to add 0s to the end of the format model to the correct number of decimal places:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_name ( value, sig ) AS
SELECT 123.456789, 2 FROM DUAL UNION ALL
SELECT 123456789.123456789, 7 FROM DUAL;
Query 1:
SELECT TO_CHAR( value, RPAD( 'FM999999999990.', sig + 15, '0' ) )
FROM table_name
Results:
| TO_CHAR(VALUE,RPAD('FM999999999990.',SIG+15,'0')) |
|---------------------------------------------------|
| 123.46 |
| 123456789.1234568 |

Split column value in sqlite

Am new to sqlite in my learning I come across the subString function so in my exercise, My table name is t1 and my column value is Partha000099 I want to increment by 1 eg., Partha000100 when i try with
SELECT SUBSTR(MAX(ID),6) FROM t1
am getting output as 000099 when I increment by 1 with the below query
SELECT SUBSTR(MAX(ID),6)+1 FROM t1
am getting output as 100, Now my question is how to construct it back as I expect
I tried with the below query,
SELECT 'Partha' || SUBSTR(MAX(ID),6)+1 FROM t1
am getting output as 1. Please some one help me.
While my solution will work, I would advice you against this type of key generation. "SELECT MAX(ID)+1" to generate the next key will be fraught with problems in more concurrent databases and you risk generating duplicate keys in a busy application/system.
It would be better to split the key into two columns, one with the group or name 'Partha', and the other column with an automatically incremented number.
However, having said that, here's how to generate the next key like your example.
You need to:
Split the key into two
Increment the numeric part
Convert it back to a string
Pad it to 6 digits
Here's the SQL that will do that:
SELECT SUBSTR(ID, 1, 6) || SUBSTR('000000' || (SUBSTR(MAX(ID), 7)+1), -6) FROM t1;
To pad it to 6 digits, I prepend 6 zeroes, then grab the last 6 digits from the resulting string with this type of expression
SUBSTR(x, -6)
The reason why you got 1 was that your expression was grouped like this:
SELECT .... + 1
And the .... part, your string concatenation, was then attempted converted to a number, which resulted in 0, thus 0+1 gives 1.
To get the unpadded result you could've just added some parenthesis:
SELECT 'Partha' || (SUBSTR(MAX(ID),6)+1) FROM t1
^ ^
This, however, would also be wrong as it would return Partha1, and that is because SUBSTR(..., 6) grabs the 6th character and onwards and the 6th character is the final a in Partha, so to get Partha100 you would need this:
SELECT 'Partha' || (SUBSTR(MAX(ID),7)+1) FROM t1
^

Resources