How to replace with zero after full-stop if not have any value using regexp_substr in oracle - oracle11g

Values are like:
Num(column)
786.56
35
select num,regexp_substr(num,'[^.]*') "first",regexp_substr(num,'[^.]+$') "second" from cost
when i execute the above query output will be like
num first second
786.56 786 56
35 35 35
I want to print zero if not have any value after full-stop,by default second column repeating first value

There are two options here; using either the occurrence or subexpression parameters available in REGEXP_SUBSTR().
Subexpression - the 5th parameter
Using subexpressions you can pick out which group () in your match you want to return in any given function call
SQL> with the_data (n) as (
2 select 786.56 from dual union all
3 select 35 from dual
4 )
5 select regexp_substr(n, '^(\d+)\.?(\d+)?$', 1, 1, null, 1) as f
6 , regexp_substr(n, '^(\d+)\.?(\d+)?$', 1, 1, null, 2) as s
7 from the_data;
F S
--- ---
786 56
35
^(\d+)\.?(\d+)?$ means at the start of the string ^, pick a group () of digits \d+ followed by an optional \.?. Then, pick an optional group of digits at the end of the string $.
We then use sub-expressions to pick out which group of digits you want to return.
Occurrence - the 3th parameter
If you place the number in a group and forget about matching the start and end of the string you can pick the first group of numbers and the second group of numbers:
SQL> with the_data (n) as (
2 select 786.56 from dual union all
3 select 35 from dual
4 )
5 select regexp_substr(n, '(\d+)\.?', 1, 1, null, 1) as f
6 , regexp_substr(n, '(\d+)\.?', 1, 2, null, 1) as s
7 from the_data;
F S
--- ---
786 56
35
(\d+)\.? means pick a group () of digits \d+ followed by an optional .. For the first group the first occurrence is the data before the ., for the second group the second occurrence is the data after .. You'll note that you still have to use the 5th parameter of REGEXP_SUBSTR() - subexpression - to state that you want the only the data in the group.
Both options
You'll note that neither of these return 0 when there are no decimal places; you'll have to add that in with a COALESCE() when the return value is NULL. You also need an explicit cast to an integer as COALESCE() expects consistent data types (this is best practice anyway):
SQL> with the_data (n) as (
2 select 786.56 from dual union all
3 select 35 from dual
4 )
5 select cast(regexp_substr(n, '^(\d+)\.?(\d+)?$', 1, 1, null, 1) as integer) as f
6 , coalesce(cast(regexp_substr(n, '^(\d+)\.?(\d+)?$', 1, 1, null, 2) as integer), 0) as s
7 from the_data;
F S
---- ----
786 56
35 0

Related

regexp_substr in sql to separate numbers from text field

I have a sql that returns comments based on employee feedback.
As you can see with the comments below, the formatting can be a bit different.
Is there a way that i can extract the numbers out?
Examples :
W.C. 06.07.2022 change from 7 to 5
wk com 13/07 demand 8 change to 13
Increase demand from 7 to 12 W/C 11/07
Output Result
7 and 5,
8 and 13,
7 and 12
Here's a way given the sample data. First identify the group of 1 or more numbers followed by an optional group of the word of "change" and a space, followed by the word "to and a space, then 1 or more digits. Within that group, group the digits desired. Of course, big assumptions here on the words between the numbers.
WITH tbl(ID, emp_comment) AS (
SELECT 1, 'W.C. 06.07.2022 change from 7 to 5' FROM dual UNION ALL
SELECT 2, 'wk com 13/07 demand 8 change to 13' FROM dual UNION ALL
SELECT 3, 'Increase demand from 7 to 12 W/C 11/07' FROM dual
)
SELECT ID, REGEXP_SUBSTR(emp_comment, '.* ((\d+) (change )?to \d+).*', 1, 1, NULL, 2) nbr_1,
REGEXP_SUBSTR(emp_comment, '.* (\d+ (change )?to (\d+)).*', 1, 1, NULL, 3) nbr_2
FROM tbl;
ID NBR_1 NBR_2
---------- ----- -----
1 7 5
2 8 13
3 7 12
3 rows selected.

Creating even ranges based on values in an oracle table

I have a big table which is 100k rows in size and the PRIMARY KEY is of the datatype NUMBER. The way data is populated in this column is using a random number generator.
So my question is, can there be a possibility to have a SQL query that can help me with getting partition the table evenly with the range of values. Eg: If my column value is like this:
1
2
3
4
5
6
7
8
9
10
And I would like this to be broken into three partitions, then I would expect an output like this:
Range 1 1-3
Range 2 4-7
Range 3 8-10
It sounds like you want the WIDTH_BUCKET() function. Find out more.
This query will give you the start and end range for a table of 1250 rows split into 20 buckets based on id:
with bkt as (
select id
, width_bucket(id, 1, 1251, 20) as id_bucket
from t23
)
select id_bucket
, min(id) as bkt_start
, max(id) as bkt_end
, count(*)
from bkt
group by id_bucket
order by 1
;
The two middle parameters specify min and max values; the last parameter specifies the number of buckets. The output is the rows between the minimum and maximum bows split as evenly as possible into the specified number of buckets. Be careful with the min and max parameters; I've found poorly chosen bounds can have an odd effect on the split.
This solution works without width_bucket function. While it is more verbose and certainly less efficient it will split the data as evenly as possible, even if some ID values are missing.
CREATE TABLE t AS
SELECT rownum AS id
FROM dual
CONNECT BY level <= 10;
WITH
data AS (
SELECT id, rownum as row_num
FROM t
),
total AS (
SELECT count(*) AS total_rows
FROM data
),
parts AS (
SELECT rownum as part_no, total.total_rows, total.total_rows / 3 as part_rows
FROM dual, total
CONNECT BY level <= 3
),
bounds AS (
SELECT parts.part_no,
parts.total_rows,
parts.part_rows,
COALESCE(LAG(data.row_num) OVER (ORDER BY parts.part_no) + 1, 1) AS start_row_num,
data.row_num AS end_row_num
FROM data
JOIN parts
ON data.row_num = ROUND(parts.part_no * parts.part_rows, 0)
)
SELECT bounds.part_no, d1.ID AS start_id, d2.ID AS end_id
FROM bounds
JOIN data d1
ON d1.row_num = bounds.start_row_num
JOIN data d2
ON d2.row_num = bounds.end_row_num
ORDER BY bounds.part_no;
PART_NO START_ID END_ID
---------- ---------- ----------
1 1 3
2 4 7
3 8 10

query sqlite: add figures any numbers and another

I've a table "mytable" like this:
MyTable
I would like to add the figures of each number with the condition that:
if the add is > 9 you subtract 9.
Ex. 13 = 1 + 3= 4
7 = 0 + 7 = 7 55= 5 + 5=10 - 9 = 1 27 = 9
Code:
select number%9 from mytable
But, in this case, for the value 27 return 0, not 9 (value correct).
Thanks in advance
You're using mod, so your result will be in the set [0...(x-1)], where x is your divisor. You want to shift your result set to be [1...x], but not shifting everything up one. Instead, you want the result to be the "normal" mod result unless that result is zero, in which case you want to return your divisor. To do this, we'll need a case statement...
SELECT
CASE WHEN (number % 9) = 0 THEN
9
ELSE
(number % 9)
END AS [field]
FROM
mytable

REGEXP_SUBSTR to return first and last segment

I have a dataset which may store an account number in several different variations. It may contain hyphens or spaces as segment separators, or it may be fully concatenated. My desired output is the first three and last 5 alphanumeric characters. I'm having problems with joining the two segments "FIRST_THREE_AND_LAST_FIVE:
with testdata as (select '1-23-456-78-90-ABCDE' txt from dual union all
select '1 23 456 78 90 ABCDE' txt from dual union all
select '1234567890ABCDE' txt from dual union all
select '123ABCDE' txt from dual union all
select '12DE' txt from dual)
select TXT
,regexp_replace(txt, '[^[[:alnum:]]]*',null) NO_HYPHENS_OR_SPACES
,regexp_substr(regexp_replace(txt, '[^[[:alnum:]]]*',null), '([[:alnum:]]){3}',1,1) FIRST_THREE
,regexp_substr(txt, '([[:alnum:]]){5}$',1,1) LAST_FIVE
,regexp_substr(regexp_replace(txt, '[^[[:alnum:]]]*',null), '([[:alnum:]]){3}',1,1) FIRST_THREE_AND_LAST_FIVE
from testdata;
My desired output would be:
FIRST_THREE_AND_LAST_FIVE
-------------------------
123ABCDE
123ABCDE
123ABCDE
123ABCDE
(null)
Here's my try. Note that when regexp_replace() does not find a match, the original string is returned, that's why you can't get a null directly. My thought was to see if the result string matched the original string but of course that would not work for line 4 where the result is correct and happens to match the original string. Others have mentioned methods for counting length, etc with a CASE but I would get more strict and check for the first 3 being numeric and the last 5 being alpha as well since just checking for 8 characters being returned doesn't guarantee they are the right 8 characters! I'll leave that up to the reader.
Anyway this looks for a digit followed by an optional dash or space (per the specs) and remembers the digit (3 times) then also remembers the last 5 alpha characters. It then returns the remembered groups in that order.
I highly recommend you make this a function where you pass your string in and get a cleaned string in return as it will be much easier to maintain, encapsulate this code for re-usability and allow for better error checking using PL/SQL code.
SQL> with testdata(txt) as (
2 select '1-23-456-78-90-ABCDE' from dual
3 union
4 select '1 23 456 78 90 ABCDE' from dual
5 union
6 select '1234567890ABCDE' from dual
7 union
8 select '123ABCDE' from dual
9 union
10 select '12DE' from dual
11 )
12 select
13 case when length(regexp_replace(upper(txt), '^(\d)[- ]?(\d)[- ]?(\d)[- ]?.*([A-Z]{5})$', '\1\2\3\4')) < 8
14 -- Needs more robust error checking here
15 THEN 'NULL' -- for readability
16 else regexp_replace(upper(txt), '^(\d)[- ]?(\d)[- ]?(\d)[- ]?.*([A-Z]{5})$', '\1\2\3\4')
17 end result
18 from testdata;
RESULT
--------------------------------------------------------------------------------
123ABCDE
123ABCDE
123ABCDE
123ABCDE
NULL
SQL>
You can use the fact that the position parameter of REGEXP_REPLACE() can take back-references to get a lot closer. Wrapped in a CASE statement you get what you're after:
select case when length(regexp_replace(txt, '[^[:alnum:]]')) >= 8 then
regexp_replace( regexp_replace(txt, '[^[:alnum:]]')
, '^([[:alnum:]]{3}).*([[:alnum:]]{5})$'
, '\1\2')
end
from test_data
This is, where the length of the string with all non-alpha-numeric characters replaced is greater or equal to 8 return the 1st and 2nd groups, which are respectively the first 3 and last 8 alpha-numeric characters.
This feels... overly complex. Once you've replaced all non-alpha-numeric characters you can just use an ordinary SUBSTR():
with test_data as (
select '1-23-456-78-90-ABCDE' txt from dual union all
select '1 23 456 78 90 ABCDE' txt from dual union all
select '1234567890ABCDE' txt from dual union all
select '123ABCDE' txt from dual union all
select '12DE' txt from dual
)
, standardised as (
select regexp_replace(txt, '[^[:alnum:]]') as txt
from test_data
)
select case when length(txt) >= 8 then substr(txt, 1, 3) || substr(txt, -5) end
from standardised
I feel like I'm missing something, but can't you just concatenate your two working columns? I.e., since you have successful regex for first 3 and last 5, just replace FIRST_THREE_AND_LAST_FIVE with:
regexp_substr(regexp_substr(regexp_replace(txt, '[^[[:alnum:]]]*',null), '([[:alnum:]]){3}',1,1)||regexp_substr(txt, '([[:alnum:]]){5}$',1,1),'([[:alnum:]]){5}',1,1)
EDIT: Added regexp_substr wrapper to return null when required

SQLite get MAX value BETWEEN range with some null fields

I have a table, two columns some rows:
c1 | c2
--------
10 | 90
11 | 89.5
12 | 89
13 | 87
14 | null
15 | 86
I want to get the MAX value of column c2 but only between c1's values 12 and 15
I try with:
SELECT MAX(IFNULL(c2, 0)) AS max_value FROM mytable WHERE data BETWEEN 12 AND 15
but not working. Is there a way to ignore the null value ?
MAX aggregated function will automatically skip null values, so the following query should be fine:
SELECT MAX(c2) FROM tablename WHERE c1 BETWEEN 12 AND 15;
Please see fiddle here. I see your where condition is
WHERE data BETWEEN 12 AND 15
are you sure you are applying the WHERE clause to the correct column? I think you need to change it to:
WHERE c1 BETWEEN 12 AND 15
"The max() aggregate function returns the maximum value of all values in the group. The maximum value is the value that would be returned last in an ORDER BY on the same column. Aggregate max() returns NULL if and only if there are no non-NULL values in the group." - http://sqlite.org/lang_aggfunc.html
Assumed, you have no negatives and you don't want NULL to be 0, You can simply use
SELECT MAX(c2) AS max_value FROM mytable ...
If all rows for c2 are null, max(c2) will return null

Resources