I am migrating procedural structure of PostgreSQL code to Oracle. Is there any alternative function present in Oracle for PostgreSQL's unnest(string_to_array)?
select a.finalval
from (select unnest(string_to_array(vturs_id, ',')) as finalval)
Use a table collection expression and, rather than using a delimited string, use a collection or VARRAY (like SYS.ODCINUMBERLIST):
SELECT COLUMN_VALUE as finalval
FROM TABLE( SYS.ODCINUMBERLIST( 1, 2, 4 ) )
outputs:
| FINALVAL |
| -------: |
| 1 |
| 2 |
| 4 |
db<>fiddle here
If you have to use delimited string (don't) then you can use a recursive sub-query factoring clause to parse the string:
WITH test_data ( delimited_string ) AS (
SELECT '1,2,40,-5,72' FROM DUAL
),
bounds ( delimited_string, start_idx, end_idx ) AS (
SELECT delimited_string,
1,
INSTR( delimited_string, ',', 1 )
FROM test_data
UNION ALL
SELECT delimited_string,
end_idx + 1,
INSTR( delimited_string, ',', end_idx + 1 )
FROM bounds
WHERE end_idx > 0
)
SELECT CASE end_idx
WHEN 0
THEN SUBSTR( delimited_string, start_idx )
ELSE SUBSTR( delimited_string, start_idx, end_idx - start_idx )
END AS finalval
FROM bounds;
outputs:
| FINALVAL |
| :------- |
| 1 |
| 2 |
| 40 |
| -5 |
| 72 |
db<>fiddle here
Related
I am trying to convert the following code from oracle:
SELECT DISTINCT REPLACE(SUBSTR (REGEXP_SUBSTR (code,'[^,]+',1,LEVEL),2,8),']','') code
FROM DUAL
CONNECT BY REGEXP_SUBSTR (code, '[^,]+', 1, LEVEL) IS NOT NULL;
Please assist to convert it into some SUBSTR / INSTR combination or CASE function.
Thanks.
Regards,
Your code does not work, as the DUAL table does not have a code column; however, if we assume that you are using a different table which does have that column:
CREATE TABLE table_name ( code ) AS
SELECT '[1],[22],[333],[4444],[55555],[666666],[7777777],[88888888],[999999999],[0000000000]' FROM DUAL
Then you can use a recursive sub-query factoring clause:
WITH rsqfc ( code, start_pos, end_pos ) AS (
SELECT code,
1,
INSTR( code, ',', 1 )
FROM table_name
UNION ALL
SELECT code,
end_pos + 1,
INSTR( code, ',', end_pos + 1 )
FROM rsqfc
WHERE end_pos > 0
)
SELECT DISTINCT
REPLACE(
CASE end_pos
WHEN 0
THEN SUBSTR( code, start_pos + 1, 8 )
ELSE SUBSTR( code, start_pos + 1, LEAST( end_pos - start_pos - 1, 8 ) )
END,
']'
) AS code
FROM rsqfc;
Which outputs:
| CODE |
| :------- |
| 99999999 |
| 4444 |
| 55555 |
| 1 |
| 333 |
| 22 |
| 666666 |
| 88888888 |
| 00000000 |
| 7777777 |
db<>fiddle here
This question already has answers here:
Splitting string into multiple rows in Oracle
(14 answers)
Closed 2 years ago.
I have two columns in a table with comma separated values, how do it split it into rows?
Would this help?
SQL> with test (col1, col2) as
2 (select 'Little,Foot,is,stupid', 'poor,bastard' from dual union all
3 select 'Green,mile,is,a' , 'good,film,is,it,not?' from dual
4 )
5 select regexp_substr(col1 ||','|| col2, '[^,]+', 1, column_value) str
6 from test cross join
7 table(cast(multiset(select level from dual
8 connect by level <= regexp_count(col1 ||','|| col2, ',') + 1
9 ) as sys.odcinumberlist));
STR
--------------------------------------------------------------------------------
Little
Foot
is
stupid
poor
bastard
Green
mile
is
a
good
film
is
it
not?
15 rows selected.
SQL>
Use a recursive sub-query factoring clause and simple string functions:
WITH splits ( id, c1, c2, idx, start_c1, end_c1, start_c2, end_c2 ) AS (
SELECT id,
c1,
c2,
1,
1,
INSTR( c1, ',', 1 ),
1,
INSTR( c2, ',', 1 )
FROM test_data
UNION ALL
SELECT id,
c1,
c2,
idx + 1,
CASE end_c1 WHEN 0 THEN NULL ELSE end_c1 + 1 END,
CASE end_c1 WHEN 0 THEN NULL ELSE INSTR( c1, ',', end_c1 + 1 ) END,
CASE end_c2 WHEN 0 THEN NULL ELSE end_c2 + 1 END,
CASE end_c2 WHEN 0 THEN NULL ELSE INSTR( c2, ',', end_c2 + 1 ) END
FROM splits
WHERE end_c1 > 0
OR end_c2 > 0
)
SELECT id,
idx,
CASE end_c1
WHEN 0
THEN SUBSTR( c1, start_c1 )
ELSE SUBSTR( c1, start_c1, end_c1 - start_c1 )
END AS c1,
CASE end_c2
WHEN 0
THEN SUBSTR( c2, start_c2 )
ELSE SUBSTR( c2, start_c2, end_c2 - start_c2 )
END AS c2
FROM splits s
ORDER BY id, idx;
So for the test data:
CREATE TABLE test_data ( id, c1, c2 ) AS
SELECT 1, 'a,b,c,d', 'e,f,g' FROM DUAL UNION ALL
SELECT 2, 'h', 'i' FROM DUAL UNION ALL
SELECT 3, NULL, 'j,k,l,m,n' FROM DUAL;
This outputs:
ID | IDX | C1 | C2
-: | --: | :--- | :---
1 | 1 | a | e
1 | 2 | b | f
1 | 3 | c | g
1 | 4 | d | null
2 | 1 | h | i
3 | 1 | null | j
3 | 2 | null | k
3 | 3 | null | l
3 | 4 | null | m
3 | 5 | null | n
db<>fiddle here
I want to check if there is a row in my table that contains the same letters but in different order, but it must have the exact same letters, no more and no less.
For example, I have the letters "abc":
bca -> true
acb -> true
abcd -> **false**
ab -> **false**
Thanks!
You can use recursive CTEs to split the parameter 'abc' and each column value to letters and compare them:
with
recursive paramletters as (
select 'abc' col, 1 pos, substr('abc', 1, 1) letter
union all
select col, pos + 1, substr(col, pos + 1, 1)
from paramletters
where pos < length(col)
),
param as (
select group_concat(letter, '') over (order by letter) paramvalue
from paramletters
order by paramvalue desc limit 1
),
cteletters as (
select col, 1 pos, substr(col, 1, 1) letter
from tablename
union all
select col, pos + 1, substr(col, pos + 1, 1)
from cteletters
where pos < length(col)
),
cte as (
select * from (
select col, group_concat(letter, '') over (partition by col order by letter) colvalue
from cteletters
)
where length(colvalue) = length(col)
)
select c.col, c.colvalue = p.paramvalue result
from cte c cross join param p
See the demo.
Results:
| col | result |
| ---- | ------ |
| ab | 0 |
| abcd | 0 |
| acb | 1 |
| bca | 1 |
If the letters of the parameter are already sorted (like 'abc') then this code can be simplified to use only the last 2 CTEs.
I'm trying to extract the the different sub-strings within one string. The I want different strings for every string divided by the dash (-) symbol.
I have tried using the SUBSTR position function. It does not work since sometimes there are 4 chars in the second sub string, therefore, the 3rd sub string is not correct.
SELECT SUBSTR(STR, INSTR (STR, '-', -1)+ 1)
STR = F-123-A123-B12 or F-1234-A123-B12
I am trying to get a query that will give me F.
I need another query that will give me 123 or 1234 if there are 4 chars
I need another query to get me A123
I need another query to get B12
I was thinking there would be a regex function that I could use. I could not find one.
For example:
SQL> with test (col) as
2 (select 'F-123-A123-B12' from dual)
3 select regexp_substr(col, '\w+', 1, level) result
4 from test
5 connect by level <= regexp_count(col, '-') + 1;
RESULT
--------------
F
123
A123
B12
SQL>
You don't need regular expressions. INSTR and SUBSTR will work (and are faster):
Oracle Setup:
CREATE TABLE test_data ( str ) AS
SELECT 'F-123-A123-B12' FROM DUAL UNION ALL
SELECT 'F-1234-A123-B12' FROM DUAL
Query 1:
SELECT SUBSTR( str, 1, delimiter1 - 1 ) AS substr1,
SUBSTR( str, delimiter1 + 1, delimiter2 - delimiter1 - 1 ) AS substr2,
SUBSTR( str, delimiter2 + 1, delimiter3 - delimiter2 - 1 ) AS substr3,
SUBSTR( str, delimiter3 + 1 ) AS substr4
FROM (
SELECT str,
INSTR( str, '-', 1, 1 ) AS delimiter1,
INSTR( str, '-', 1, 2 ) AS delimiter2,
INSTR( str, '-', 1, 3 ) AS delimiter3
FROM test_data
) s
Output:
SUBSTR1 | SUBSTR2 | SUBSTR3 | SUBSTR4
:------ | :------ | :------ | :------
F | 123 | A123 | B12
F | 1234 | A123 | B12
If you do want to use regular expressions, then there is no need for a hierarchical query:
Query 2:
SELECT REGEXP_SUBSTR( str, '[^-]+', 1, 1 ) AS substr1,
REGEXP_SUBSTR( str, '[^-]+', 1, 2 ) AS substr2,
REGEXP_SUBSTR( str, '[^-]+', 1, 3 ) AS substr3,
REGEXP_SUBSTR( str, '[^-]+', 1, 4 ) AS substr4
FROM test_data
(Output as Query 1 above.)
Query 3
If you don't know how many delimited values there will be and want to parse them all to rows then you still don't need to use (slow) regular expressions or hierarchical queries and can just use a recursive sub-query factoring clause with simple string functions (and it works with zero-width/NULL sub-strings between delimiters):
WITH substr_bounds ( str, idx, startidx, endidx ) AS (
SELECT str,
1,
1,
INSTR( str, '-', 1 )
FROM test_data
UNION ALL
SELECT str,
idx + 1,
endidx + 1,
INSTR( str, '-', endidx + 1 )
FROM substr_bounds
WHERE endidx > 0
)
SELECT str,
idx,
CASE
WHEN endidx = 0
THEN SUBSTR( str, startidx )
ELSE SUBSTR( str, startidx, endidx - startidx )
END AS substr
FROM substr_bounds
ORDER BY str, idx
Output:
STR | IDX | SUBSTR
:-------------- | --: | :-----
F-123-A123-B12 | 1 | F
F-123-A123-B12 | 2 | 123
F-123-A123-B12 | 3 | A123
F-123-A123-B12 | 4 | B12
F-1234-A123-B12 | 1 | F
F-1234-A123-B12 | 2 | 1234
F-1234-A123-B12 | 3 | A123
F-1234-A123-B12 | 4 | B12
db<>fiddle here
If your string could have a NULL element, use this format to handle it (Note list element 2 is NULL), else you risk the following elements being returned in the wrong positions:
with test (col) as
(select 'F--A123-B12' from dual)
select regexp_substr(col, '(.*?)(-|$)', 1, level, null, 1) result
from test
connect by level <= regexp_count(col, '-') + 1;
RESULT
-----------
F
A123
B12
4 rows selected.
I am working with newest Node 17.3.1 where "substr" function is obsolete and I replace it with "substring" instead and it is the nearest and simplest alternative.
I want to transform this output from the row "topic"...
SMARTBASE/N0184/1/MOISTURE/value
SMARTBASE/N0184/1/MOISTURE/unit
SMARTBASE/N0184/1/MOISTURE/timestamp
SMARTBASE/N0184/1/CONDUCTIVITY/value
SMARTBASE/N0184/1/CONDUCTIVITY/unit
SMARTBASE/N0184/1/CONDUCTIVITY/timestamp
to a new table like:
SENSORS|MOISTURE(value)|MOISTURE(unit)|CONDUCTIVITY(value)|CONDUCTIVITY(unit)
N0184|0.41437244624|Raw VWC|0.5297062938712509|mS/cm
first line: values of topic(row), second line: values of value(row)(values of mqtt-topics)
but that's a sensor of 500++... SMARTBASE is not always SMARTBASE, so regexp _... is not a good idea ... At the end this should be saved as a view.
Is that even possible? I don't know how to implement it... or how to start with it. to transform a row in a table, I can use the pivot-function, but the rest, I don't know.
my main problem: How can I access the individual values of the topic?
Use REGEXP_SUBSTR to get the substring components of your topic column and then use PIVOT:
Oracle Setup:
CREATE TABLE table_name ( topic, value ) AS
SELECT 'SMARTBASE/N0184/1/MOISTURE/value', '0.414' FROM DUAL UNION ALL
SELECT 'SMARTBASE/N0184/1/MOISTURE/unit', 'Raw VWC' FROM DUAL UNION ALL
SELECT 'SMARTBASE/N0184/1/MOISTURE/timestamp', '2019-01-01T00:00:00.000' FROM DUAL UNION ALL
SELECT 'SMARTBASE/N0184/1/CONDUCTIVITY/value', '0.529' FROM DUAL UNION ALL
SELECT 'SMARTBASE/N0184/1/CONDUCTIVITY/unit', 'mS/cm' FROM DUAL UNION ALL
SELECT 'SMARTBASE/N0184/1/CONDUCTIVITY/timestamp', '2019-01-01T00:00:00.000' FROM DUAL;
Query:
SELECT SENSOR_TYPE,
SENSOR,
TO_NUMBER( moisture_value ) AS moisture_value,
moisture_unit,
TO_TIMESTAMP( moisture_timestamp, 'YYYY-MM-DD"T"HH24:MI:SS.FF3' ) AS moisture_timestamp,
TO_NUMBER( conductivity_value ) AS conductivity_value,
conductivity_unit,
TO_TIMESTAMP( conductivity_timestamp, 'YYYY-MM-DD"T"HH24:MI:SS.FF3' ) AS conductivity_timestamp
FROM (
SELECT REGEXP_SUBSTR( topic, '[^/]+', 1, 1 ) AS sensor_type,
REGEXP_SUBSTR( topic, '[^/]+', 1, 2 ) AS sensor,
REGEXP_SUBSTR( topic, '[^/]+', 1, 4 ) AS measurement_name,
REGEXP_SUBSTR( topic, '[^/]+', 1, 5 ) AS measurement_metadata_type,
value
FROM table_name
)
PIVOT(
MAX( value )
FOR ( measurement_name, measurement_metadata_type )
IN (
( 'MOISTURE', 'value' ) AS MOISTURE_value,
( 'MOISTURE', 'unit' ) AS MOISTURE_unit,
( 'MOISTURE', 'timestamp' ) AS MOISTURE_timestamp,
( 'CONDUCTIVITY', 'value' ) AS CONDUCTIVITY_value,
( 'CONDUCTIVITY', 'unit' ) AS CONDUCTIVITY_unit,
( 'CONDUCTIVITY', 'timestamp' ) AS CONDUCTIVITY_timestamp
)
)
Output:
SENSOR_TYPE | SENSOR | MOISTURE_VALUE | MOISTURE_UNIT | MOISTURE_TIMESTAMP | CONDUCTIVITY_VALUE | CONDUCTIVITY_UNIT | CONDUCTIVITY_TIMESTAMP
:---------- | :----- | -------------: | :------------ | :------------------------------ | -----------------: | :---------------- | :------------------------------
SMARTBASE | N0184 | .414 | Raw VWC | 01-JAN-19 12.00.00.000000000 AM | .529 | mS/cm | 01-JAN-19 12.00.00.000000000 AM
db<>fiddle here