(Oracle)Splitting strings then averaging at once - sql

My colum COL1 have sometimes have data such as, '10|20'.
My goal is to split the data if the data have "|". And then averaging them to get 15.
How to modify my code below to add COL2 like this?
(Expected results)
COL1 COL2
------- -------
10 10
10|20 15
10|20|30 20
(My code)
WITH A AS (
SELECT '10' COL1 FROM DUAL
UNION ALL
SELECT '10|20' FROM DUAL
UNION ALL
SELECT '10|20|30' FROM DUAL
) SELECT COL1 FROM A DUAL

You can use a correlated XMLTABLE to split the values:
WITH A AS (
SELECT '10' COL1 FROM DUAL UNION ALL
SELECT '10|20' FROM DUAL UNION ALL
SELECT '10|20|30' FROM DUAL
)
SELECT col1,
(
SELECT AVG( TO_NUMBER( column_value ) )
FROM xmltable(('"' || REPLACE(a.col1, '|', '","') || '"'))
) AS col2
FROM A
Which outputs:
COL1 | COL2
:------- | ---:
10 | 10
10|20 | 15
10|20|30 | 20
db<>fiddle here

Here you go:
SQL> with a as
2 (select '10' col1 from dual union all
3 select '10|20' from dual union all
4 select '10|20|30' from dual
5 )
6 select
7 col1,
8 avg(to_number(regexp_substr(col1, '[^\|]+', 1, column_value))) col2
9 from a cross join
10 table(cast(multiset(select level from dual
11 connect by level <= regexp_count(col1, '\|') + 1
12 ) as sys.odcinumberlist))
13 group by col1
14 order by col1;
COL1 COL2
-------- ----------
10 10
10|20 15
10|20|30 20
SQL>
What does it do?
Line #8 (with a little help of lines #10 - 12):
REGEXP_SUBSTR part is used to split column to rows
TO_NUMBER converts substring to number
AVG calculates average value

WITH t AS (
SELECT '10' text FROM DUAL
UNION ALL
SELECT '10|20' FROM DUAL
UNION ALL
SELECT '10|20|30' FROM DUAL
)
SELECT text,
avg(to_number(regexp_substr(t.text, '[^\|]+', 1, column_value))) average
FROM t,
TABLE (CAST (MULTISET
(SELECT LEVEL FROM dual
CONNECT BY instr(t.text, '|', 1, LEVEL - 1) > 0
) AS sys.odciNumberList ) ) lines
GROUP BY t.text ORDER BY t.text;
TEXT AVERAGE
-------- ----------
10 10
10|20 15
10|20|30 20

Related

Compare before column in before row with next column in next row

My code is :
with x as
(
select 1 col from dual union all
select 2 col from dual union all
select 8 col from dual union all
select 4 col from dual union all
select 3 col from dual union all
select 2 col from dual
)
select col col1, col col2, col col3, rownum
from x
where col2.ROWNUM > col1.ROWNUM -1
and col2.ROWNUM > col3ROWNUM +1 ;
I want to compare col2.ROWNUM > col1.ROWNUM -1 and col2.ROWNUM > col3ROWNUM + 1 but that doesn't work and I got an error
ORA-01747: invalid user.table.column, table.column, or column specification
01747. 00000 - "invalid user.table.column, table.column, or column specification"
*Cause:
*Action:
Error at Line: 10 Column: 13
Please help me
It looks you got something wrong.
Result of that CTE is a single-column table whose only column is named col. There are no other columns.
SQL> with x as (
2 select 1 col from dual union all --> in UNION, all columns are
3 select 2 col from dual union all named by column name(s) from the
4 select 8 col from dual union all first SELECT statement
5 select 4 col from dual union all
6 select 3 col from dual union all
7 select 2 col from dual)
8 select x.*, rownum
9 from x;
COL ROWNUM
---------- ----------
1 1
2 2
8 3
4 4
3 5
2 6
6 rows selected.
SQL>
Therefore, where clause you wrote doesn't make any sense. Perhaps you should explain what you really have, rules that should be applied to source data and result you'd like to get.
Based on text you put into the title:
compare before column in before row with next column in next row
maybe you'd be interested in lag and lead analytic functions which then let you compare values in adjacent rows (pay attention to NULL values; I didn't). For example:
SQL> with x as (
2 select 1 col from dual union all
3 select 2 col from dual union all
4 select 8 col from dual union all
5 select 4 col from dual union all
6 select 3 col from dual union all
7 select 2 col from dual
8 ),
9 temp as
10 (select col,
11 rownum as rn
12 from x
13 ),
14 temp2 as
15 (select
16 rn,
17 col as this_row,
18 lag(col) over (order by rn) as previous_row,
19 lead(col) over (order by rn) as next_row
20 from temp
21 )
22 select this_row,
23 previous_row,
24 next_row,
25 --
26 case when this_row < previous_row then 'This < previous'
27 when this_row < next_row then 'This < next'
28 else 'something else'
29 end as result
30 from temp2
31 order by rn;
Result:
THIS_ROW PREVIOUS_ROW NEXT_ROW RESULT
---------- ------------ ---------- ---------------
1 2 This < next
2 1 8 This < next
8 2 4 something else
4 8 3 This < previous
3 4 2 This < previous
2 3 This < previous
6 rows selected.
SQL>
Use lead or lag functions. But, please, do not use rownum for such purposes.
Rownum indicates simply the order in which a row was found in the database and cannot be used for other purposes except limiting the number of rows fetched, like where rownum<=1 to be certain you won't get a too_many_rows exception, for instance. Still, if in a query you do fetch the pseud-column rownum, give it an alias so that you may use that value later on.
Moreover, what is supposed to mean col2.ROWNUM or col1.ROWNUM? That is not clear. col1 and col2 are two columns, which do not have the attribute rownum.
Something that may help in the future for analytic queries:
https://oracle-base.com/articles/misc/lag-lead-analytic-functions
And, if you wish to get a working SQL, please explain clearly what you wish to achieve, for I haven't really understood what that code is intended to do.
A way you may use rownum without getting errors:
with x as (
select 1 col from dual union all
select 2 col from dual union all
select 8 col from dual union all
select 4 col from dual union all
select 3 col from dual union all
select 2 col from dual)
,x2 as (
select col col1 ,col col2, col col3 ,rownum rn
from x
)
select *
from x2
where rn between 2 and 3 --- rownum cannot be used in such a
condition!!!
;
Or, to be certain you get only the first row from a table satisfying a given condition:
select x_col1, x_col2 into v_col1, v_col2
from x_table
where ... --- logical conditions
and rownum<=1; --- rownum <= 1 avoids too_many_rows_exception if several rows satisfy the logical conditions given before
In Oracle, results sets have a non-deterministic order (i.e. they are unordered) unless you use an ORDER BY clause. Therefore, if you have a physical table, you need another column to provide the order (rather than relying on the ROWNUM pseudo-column, which may result in unexpected behaviour):
CREATE TABLE x (order_id, col) AS
SELECT 1, 1 FROM DUAL UNION ALL
SELECT 2, 2 FROM DUAL UNION ALL
SELECT 3, 8 FROM DUAL UNION ALL
SELECT 4, 4 FROM DUAL UNION ALL
SELECT 5, 3 FROM DUAL UNION ALL
SELECT 6, 2 FROM DUAL;
If you want to find the rows that go up in succession, then you can use MATCH_RECOGNIZE for row-by-row pattern matching:
SELECT *
FROM x
MATCH_RECOGNIZE(
ORDER BY order_id
MEASURES
any_row.col AS col1,
FIRST(up.col) AS col2,
LAST(up.col) AS col3,
FIRST(order_id) AS start_order_id
PATTERN ( any_row up{2} )
DEFINE up AS ( col > PREV(col) )
)
or the LEAD analytic function:
SELECT *
FROM (
SELECT col AS col1,
LEAD(col, 1) OVER (ORDER BY order_id) AS col2,
LEAD(col, 2) OVER (ORDER BY order_id) AS col3,
order_id
FROM x
)
WHERE col2 > col1
AND col3 > col2;
Which both output:
COL1
COL2
COL3
START_ORDER_ID
1
2
8
1
fiddle
It looks like you want to find the rows where the value of the column is bigger than it is in both - the previous and next row. If so, you could try this:
WITH
tbl (ID, COL) AS -- Sample data (ID column is just to preserve order of the rows)
(
Select 1, 1 From Dual Union All
Select 2, 2 From Dual Union All
Select 3, 8 From Dual Union All
Select 4, 4 From Dual Union All
Select 5, 3 From Dual Union All
Select 6, 2 From DUAL
)
Select ID, COL, CASE WHEN COL > LAG(COL, 1) OVER(Order By ID) And COL > LEAD(COL, 1) OVER(Order By ID) THEN 'YES' END "BIGGER_THAN_PREV_AND_NEXT"
From tbl
Order By ID
ID COL BIGGER_THAN_PREV_AND_NEXT
---------- ---------- -------------------------
1 1
2 2
3 8 YES
4 4
5 3
6 2
... with a bit different sample data this will find the other row(s) that satisfy the condition ...
WITH
tbl (ID, COL) AS -- Sample data (ID column is just to preserve order of the rows)
(
Select 1, 1 From Dual Union All
Select 2, 2 From Dual Union All
Select 3, 8 From Dual Union All
Select 4, 4 From Dual Union All
Select 5, 5 From Dual Union All -- value of COL changed from 3 to 5
Select 6, 2 From DUAL
)
Select ID, COL, CASE WHEN COL > LAG(COL, 1) OVER(Order By ID) And COL > LEAD(COL, 1) OVER(Order By ID) THEN 'YES' END "BIGGER_THAN_PREV_AND_NEXT"
From tbl
Order By ID
ID COL BIGGER_THAN_PREV_AND_NEXT
---------- ---------- -------------------------
1 1
2 2
3 8 YES
4 4
5 5 YES
6 2
OR without ID - using ROWNUM (as in your question), - not adviseable, though...
WITH
tbl (COL) AS -- Sample data (without ID column)
(
Select 1 From Dual Union All
Select 2 From Dual Union All
Select 8 From Dual Union All
Select 4 From Dual Union All
Select 5 From Dual Union All
Select 2 From DUAL
)
Select COL, CASE WHEN COL > LAG(COL, 1) OVER(Order By ROWNUM) And COL > LEAD(COL, 1) OVER(Order By ROWNUM) THEN 'YES' END "BIGGER_THAN_PREV_AND_NEXT"
From tbl
COL BIGGER_THAN_PREV_AND_NEXT
---------- -------------------------
1
2
8 YES
4
5 YES
2
Any Order By clause added to the query could change the ROWNUM values and the result...

Connect by lead incremental values Oracle

I have this table
COL1 COL2
---------
A 1
B 5
C 12
D 14
And I would like to obtain this other one. This is, until the next col2 for each col1 is reached, a row with the COL1 and incremental values.
COL1 COL2
---------
A 1
A 2
A 3
A 4
B 5
B 6
B 7
B 8
B 9
B 10
B 11
C 12
C 13
D 14
EDIT: this is what I've tried so far. It seems I'm not far away from the solution but struggling to progress further than this.
WITH aux (
col1,
col2
) AS (
SELECT
'A',
1
FROM
dual
UNION ALL
SELECT
'B',
5
FROM
dual
UNION ALL
SELECT
'C',
12
FROM
dual
UNION ALL
SELECT
'D',
14
FROM
dual
), aux1 AS (
SELECT
a.*,
nvl(LEAD(a.col2) OVER(
ORDER BY
a.col2
), a.col2) h
FROM
aux a
)
SELECT
*
FROM
aux1
CONNECT BY level >= col2
AND level <= h;
testseq is the table containing your initial 4 rows. Use lead to find the stop value for col2 for each col1, and recursion to iterate and create the additional rows.
WITH xrows (col1, col2, lastcol2) AS (
SELECT t.*, LEAD(col2) OVER (ORDER BY col1) - 1
FROM testseq t
UNION ALL
SELECT col1, col2+1, lastcol2
FROM xrows t
WHERE col2 < lastcol2
)
SELECT col1, col2
FROM xrows
ORDER BY col1, col2
;
First you need to find the "next" number (whatever ordering you prefer) and then generate such number of rows with recursive subquery:
with a(code, num) as(
select 'A', 1 from dual union all
select 'B', 5 from dual union all
select 'C', 12 from dual union all
select 'D', 14 from dual
)
, b as (
select
a.*
, lead(num - 1, 1, num) over(order by code asc) as next_num
from a
)
select
b.code
, gen.val
from b
cross join lateral(
select num + level - 1 as val
from dual
connect by num + level - 1 <= next_num
) gen
order by 2 asc
Or if you prefer recursive CTE:
with a(code, num) as(
select 'A', 1 from dual union all
select 'B', 5 from dual union all
select 'C', 12 from dual union all
select 'D', 14 from dual
)
, b(code, next_num, val) as (
select
a.code
, lead(num - 1, 1, num) over(order by code asc) as next_num
, num
from a
union all
select
code
, next_num
, val + 1
from b
where val < next_num
)
select
b.code
, val
from b
order by 2 asc
CODE
VAL
A
1
A
2
A
3
A
4
B
5
B
6
B
7
B
8
B
9
B
10
B
11
C
12
C
13
D
14
livesql demo

Oracle regex count multiple occurrences of a string surrounded by commas

This question is similar to a previous question of mine. I am looking for a way to count a character string in a comma-separated list of values in a column in an Oracle (11g) SQL database. For example, suppose I have the following data:
SELECT ('SL,PK') as col1 FROM dual
UNION ALL
SELECT ('SL,CR,SL') as col1 FROM dual
UNION ALL
SELECT ('PK,SL') as col1 FROM dual
UNION ALL
SELECT ('SL,SL') as col1 FROM dual
UNION ALL
SELECT ('SL') as col1 FROM dual
UNION ALL
SELECT ('PK') as col1 FROM dual
UNION ALL
SELECT ('PI,SL,PK') as col1 FROM dual
UNION ALL
SELECT ('PI,SL,SL,PK') as col1 FROM dual
UNION ALL
SELECT ('PI,SL,SL,SL,PK') as col1 FROM dual
UNION ALL
SELECT ('PI,SL,SL,SL,SL,PK') as col1 FROM dual
UNION ALL
SELECT ('PI,OSL,SL,PK') as col1 FROM dual
UNION ALL
SELECT ('PI,SL,SLR,PK') as col1 FROM dual
COL1
-----
SL,PK
SL,CR,SL
PK,SL
SL,SL
SL
PK
PI,SL,PK
PI,SL,SL,PK
PI,SL,SL,SL,PK
PI,SL,SL,SL,SL,PK
PI,OSL,SL,PK
PI,SL,SLR,PK
I am looking to count all occurrences of the substring 'SL', strictly (i.e. not including 'OSL', 'SLR', etc). The ideal result would look like this:
COL1 COL2
----- -----
SL,PK 1
SL,CR,SL 2
PK,SL 1
SL,SL 2
SL 1
PK 0
PI,SL,PK 1
PI,SL,SL,PK 2
PI,SL,SL,SL,PK 3
PI,SL,SL,SL,SL,PK 4
PI,OSL,SL,PK 1
PI,SL,SLR,PK 1
I can accomplish this using length and regexp_replace:
SELECT
col1,
(length(col1) - NVL(length(regexp_replace(regexp_replace(col1,'(^|,)(SL)($|,)','\1' || '' || '\3',1,0,'imn'),'(^|,)(SL)($|,)','\1' || '' || '\3',1,0,'imn')),0))/length('SL') as col2
FROM (
SELECT ('SL,PK') as col1 FROM dual
UNION ALL
SELECT ('SL,CR,SL') as col1 FROM dual
UNION ALL
SELECT ('PK,SL') as col1 FROM dual
UNION ALL
SELECT ('SL,SL') as col1 FROM dual
UNION ALL
SELECT ('SL') as col1 FROM dual
UNION ALL
SELECT ('PK') as col1 FROM dual
UNION ALL
SELECT ('PI,SL,PK') as col1 FROM dual
UNION ALL
SELECT ('PI,SL,SL,PK') as col1 FROM dual
UNION ALL
SELECT ('PI,SL,SL,SL,PK') as col1 FROM dual
UNION ALL
SELECT ('PI,SL,SL,SL,SL,PK') as col1 FROM dual
UNION ALL
SELECT ('PI,OSL,SL,PK') as col1 FROM dual
UNION ALL
SELECT ('PI,SL,SLR,PK') as col1 FROM dual
)
COL1 COL2
----- -----
SL,PK 1
SL,CR,SL 2
PK,SL 1
SL,SL 2
SL 1
PK 0
PI,SL,PK 1
PI,SL,SL,PK 2
PI,SL,SL,SL,PK 3
PI,SL,SL,SL,SL,PK 4
PI,OSL,SL,PK 1
PI,SL,SLR,PK 1
but was hoping for a more elegant solution, perhaps with regexp_count. I have achieved my goal successfully in other regex implementations that have the word boundary \b construct available (with \bSL\b), but have not found a solution for Oracle's regex.
You can use regexp_count() if you hack the string:
select col1, regexp_count(replace(col1, ',', ',,'), '(^|\W)SL(\W|$)')
This doubles the delimiter so the first match doesn't eat it up -- getting around the underlying issue which is that Oracle regular expressions do not support look-ahead.
Here is a db<>fiddle.
Here's one option:
SQL> with temp as
2 (select col1,
3 regexp_substr(col1, '[^,]+', 1, column_value) val
4 from test cross join
5 table(cast(multiset(select level from dual
6 connect by level <= regexp_count(col1, ',') + 1
7 ) as sys.odcinumberlist))
8 )
9 select col1,
10 sum(case when val = 'SL' then 1 else 0 end) col2
11 From temp
12 group by col1;
COL1 COL2
----------------- ----------
PI,SL,SLR,PK 1
PK,SL 1
PK 0
SL,CR,SL 2
PI,OSL,SL,PK 1
SL,SL 2
PI,SL,SL,PK 2
PI,SL,SL,SL,PK 3
SL,PK 1
SL 1
PI,SL,PK 1
PI,SL,SL,SL,SL,PK 4
12 rows selected.
SQL>
What does it do?
temp CTE splits each column into rows (separator is comma)
the final select simply counts number of SLs for each col1
You can use an XMLTABLE to spilt the string and then count:
SELECT col1,
(
SELECT COUNT(*)
FROM XMLTABLE(
('"' || REPLACE( col1, ',', '","' ) || '"')
COLUMNS
value CHAR(2) PATH '.'
)
WHERE value = 'SL'
) AS col2
FROM test_data
So, for your test data:
CREATE TABLE test_data ( col1 ) AS
SELECT 'SL,PK' FROM dual UNION ALL
SELECT 'SL,CR,SL' FROM dual UNION ALL
SELECT 'PK,SL' FROM dual UNION ALL
SELECT 'SL,SL' FROM dual UNION ALL
SELECT 'SL' FROM dual UNION ALL
SELECT 'PK' FROM dual UNION ALL
SELECT 'PI,SL,PK' FROM dual UNION ALL
SELECT 'PI,SL,SL,PK' FROM dual UNION ALL
SELECT 'PI,SL,SL,SL,PK' FROM dual UNION ALL
SELECT 'PI,SL,SL,SL,SL,PK' FROM dual UNION ALL
SELECT 'PI,OSL,SL,PK' FROM dual UNION ALL
SELECT 'PI,SL,SLR,PK' FROM dual
This outputs:
COL1 | COL2
:---------------- | ---:
SL,PK | 1
SL,CR,SL | 2
PK,SL | 1
SL,SL | 2
SL | 1
PK | 0
PI,SL,PK | 1
PI,SL,SL,PK | 2
PI,SL,SL,SL,PK | 3
PI,SL,SL,SL,SL,PK | 4
PI,OSL,SL,PK | 1
PI,SL,SLR,PK | 2
db<>fiddle here

How to reverse the string 'ab,cd,ef' to 'ef->cd->ab'

when I select the table from Oracle, I want to handle one col'val :
eg:
'ab,cd,ef' to 'ef->cd->ab';
'AB,BC' to 'BC->AB';
'ACNN,BBCCAC' to 'BBCCAC->ACNN';
'BBBDC,DCCX,FFF' to 'FFF->DCCX->BBBDC'
We have two tasks. The first is to tokenize the original strings. This is quite easy with regular expressions (although there are more performant approaches if you are dealing with large volumes). The second task is to re-assemble the tokens in reverse order; we can use the 11gR2 LISTAGG() function for this:
with tokens as (
select distinct col1, regexp_substr(col1, '[^,]+', 1, level) as tkn, level as rn
from t23
connect by level <= regexp_count (col1, '[,]') +1
)
select col1
, listagg(tkn, '->')
within group (order by rn desc) as rev_col1
from tokens
group by col1
/
Here is a SQL Fiddle.
You can do it with a mix of string split and string aggregation.
Using:
REGEXP_SUBSTR : To split the comma delimited string into rows
LISTAGG : To aggregate the values
You can have a look at this article to understand how string split works http://lalitkumarb.wordpress.com/2015/03/04/split-comma-delimited-strings-in-a-table-using-oracle-sql/
SQL> WITH DATA AS(
2 SELECT 1 ID, 'ab,cd,ef' text FROM dual UNION ALL
3 SELECT 2 ID, 'AB,BC' text FROM dual UNION ALL
4 SELECT 3 ID, 'ACNN,BBCCAC' text FROM dual
5 )
6 SELECT ID,
7 listagg(text, ',') WITHIN GROUP (
8 ORDER BY rn DESC) reversed_indices
9 FROM
10 (SELECT t.id,
11 rownum rn,
12 trim(regexp_substr(t.text, '[^,]+', 1, lines.COLUMN_VALUE)) text
13 FROM data t,
14 TABLE (CAST (MULTISET
15 (SELECT LEVEL FROM dual CONNECT BY LEVEL <= regexp_count(t.text, ',')+1
16 ) AS sys.odciNumberList ) ) lines
17 ORDER BY ID
18 )
19 GROUP BY ID
20 /
ID REVERSED_INDICES
---------- ------------------------------
1 ef,cd,ab
2 BC,AB
3 BBCCAC,ACNN
SQL>
Let's say your table looks like:
SQL> SELECT * FROM t;
ID TEXT
---------- ------------------------------
1 ab,cd,ef
2 AB,BC
3 ACNN,BBCCAC
4 word1,word2,word3
5 1,2,3
SQL>
Using the above query:
SQL> SELECT ID,
2 listagg(text, '-->') WITHIN GROUP (
3 ORDER BY rn DESC) reversed_indices
4 FROM
5 (SELECT t.id,
6 rownum rn,
7 trim(regexp_substr(t.text, '[^,]+', 1, lines.COLUMN_VALUE)) text
8 FROM t,
9 TABLE (CAST (MULTISET
10 (SELECT LEVEL FROM dual CONNECT BY LEVEL <= regexp_count(t.text, ',')+1
11 ) AS sys.odciNumberList ) ) lines
12 ORDER BY ID
13 )
14 GROUP BY ID
15 /
ID REVERSED_INDICES
---------- ------------------------------
1 ef-->cd-->ab
2 BC-->AB
3 BBCCAC-->ACNN
4 word3-->word2-->word1
5 3-->2-->1
SQL>

Oracle SQL : Regexp_substr

I have below sample values in a column
Abc-123-xyz
Def-456-uvw
Ghi-879-rst-123
Jkl-abc
Expected output is the third element split by '-', in case there is no third element, the last element will be retrieve.
See expected output below:
Xyz
Uvw
Rst
Abc
Thanks ahead for the help.
SELECT initcap(nvl(regexp_substr(word, '[^-]+', 1,3),regexp_substr(word, '[^-]+', 1,2))) FROM your_table;
Another approach:
SQL> with t1(col) as(
2 select 'Abc-123-xyz' from dual union all
3 select 'Def-456-uvw' from dual union all
4 select 'Ghi-879-rst-123' from dual union all
5 select 'Jkl-Abc' from dual
6 )
7 select regexp_substr( col
8 , '[^-]+'
9 , 1
10 , case
11 when regexp_count(col, '[^-]+') >= 3
12 then 3
13 else regexp_count(col, '[^-]+')
14 end
15 ) as res
16 from t1
17 ;
Result:
RES
---------------
xyz
uvw
rst
Abc
regexp_substr(column, '(.*?-){0,2}([^-]+)', 1, 1, '', 2)
You can also do it without RegEx:
with t1 as(
select 'Abc-123-xyz' as MyText from dual union all
select 'Def-456-uvw' from dual union all
select 'Ghi-879-rst-123' from dual union all
select 'Jkl-Abc' from dual
)
SELECT
SUBSTR(t1.mytext, LENGTH(t1.mytext) - INSTR(REVERSE(t1.mytext), '-') + 2)
FROM t1
;