SQL check if fields contains same letters - sql

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.

Related

Splitting data field String value in Oracle SQl

I have a String Field value in Oracle SQL table. Is there any query so that I can split the string into new lines with certain number of equal characters in each line and the excess characters at the bottom?
eg- ABCDEFGHIJ
I want to have lines with equal number of characters 4 in each line as follows
ABCD
EFGH
IJ
The remainder of 2 letters should be at the bottom. Is it possible to achieve this using an Oracle sql query?
You can use a query like the one below using CONNECT BY and LEVEL based on the length of the string.
WITH d AS (SELECT 'ABCDEFGHIJ' AS str FROM DUAL)
SELECT SUBSTR (str, ((LEVEL - 1) * 4) + 1, 4) AS four_letters
FROM d
CONNECT BY LEVEL < (LENGTH (str) / 4) + 1;
If you have multiple rows, you can use OUTER APPLY with a hierarchical query:
SELECT s.split_value,
s.position
FROM table_name t
OUTER APPLY (
SELECT LEVEL AS position,
SUBSTR( t.value, 4 * LEVEL - 3, 4 ) AS split_value
FROM DUAL
CONNECT BY LEVEL <= CEIL( LENGTH( t.value ) / 4 )
) s
Which, for the sample data:
CREATE TABLE table_name ( value ) AS
SELECT 'ABCDEFGHIJ' FROM DUAL UNION ALL
SELECT '123456789012' FROM DUAL;
Outputs:
SPLIT_VALUE | POSITION
:---------- | -------:
ABCD | 1
EFGH | 2
IJ | 3
1234 | 1
5678 | 2
9012 | 3
db<>fiddle here
Update
if I want to have like 36 words characters in a line how can I modify your 1st answer?
SELECT s.split_value,
s.position
FROM table_name t
OUTER APPLY (
SELECT LEVEL AS position,
SUBSTR( t.value, 36 * ( LEVEL - 1 ) + 1, 36 ) AS split_value
FROM DUAL
CONNECT BY LEVEL <= CEIL( LENGTH( t.value ) / 36 )
) s
Which, for the sample data:
CREATE TABLE table_name ( value ) AS
SELECT 'ABCDEFGHIJ' FROM DUAL UNION ALL
SELECT '________10________20________30________40________50________60________70________80' FROM DUAL;
Outputs:
SPLIT_VALUE | POSITION
:----------------------------------- | -------:
ABCDEFGHIJ | 1
________10________20________30______ | 1
__40________50________60________70__ | 2
______80 | 3
db<>fiddle here

splitting two columns containing comma separated values in oracle [duplicate]

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

Redshift, count items in column seperated with comma

I have data that a column saved a group of number
| user | col |
| ------- | ------- |
| 1 | 3,7,11,25,44,56,77,32,34,55 |
| 2 | 3,7,25,44,37,89,56,99,103,13 |
| 1 | 3,10,11,25,44,56,33,32,34,55 |
I know I can split part the columns and count but do we have any different way to count the numbers?
|user| new-col | count|
| ------- | ------- |
| 1 | 3 | 2 |
| 1 | 7 | 1 |
| 1 | 11 | 2 |
| 1 | 25 | 2 |
| 1 | 44 |2 |
| 1 | 56 |1 |
| 1 | 77 | 1 |
| 1 | 32 | 2 |
You could use a union query along with SPLIT_PART:
WITH cte AS (
SELECT user, SPLIT_PART(col, ',', 1) AS val FROM yourTable UNION ALL
SELECT user, SPLIT_PART(col, ',', 2) FROM yourTable UNION ALL
SELECT user, SPLIT_PART(col, ',', 3) FROM yourTable UNION ALL
SELECT user, SPLIT_PART(col, ',', 4) FROM yourTable UNION ALL
SELECT user, SPLIT_PART(col, ',', 5) FROM yourTable UNION ALL
SELECT user, SPLIT_PART(col, ',', 6) FROM yourTable UNION ALL
SELECT user, SPLIT_PART(col, ',', 7) FROM yourTable UNION ALL
SELECT user, SPLIT_PART(col, ',', 8) FROM yourTable UNION ALL
SELECT user, SPLIT_PART(col, ',', 9) FROM yourTable UNION ALL
SELECT user, SPLIT_PART(col, ',', 10) FROM yourTable
)
SELECT
user,
val,
COUNT(*) AS cnt
FROM cte
GROUP BY
user,
val;
But note that all we are doing above in the CTE is really just normalizing your data so that each user-value relationship occupies a separate record. Ideally you should change your table design and move away from storing CSV.
If you instead want just the count of numbers per user, then use:
SELECT
user,
COUNT(*) AS cnt
FROM cte
GROUP BY
user;
Query.
with t as (
select 1 as user, '3,7,11,25,44,56,77,32,34,55' as col
union all
select 2 as user, '3,7,25,44,37,89,56,99,103,13' as col
union all
select 1 as user, '3,10,11,25,44,56,33,32,34,55' as col
)
select a.user, a.val, count(*) as cnt
from (
select a.user
, SPLIT_PART(a.col, ',', b.no) as val
from t a
cross join (
select * from generate_series(1,10) as no
) b
) a
group by a.user, a.val
order by a.user, a.val
Count the number of commas in the string using REGEXP_COUNT and add 1.
CREATE TEMP TABLE examples (
user_id INT
, value_list VARCHAR
);
INSERT INTO examples
SELECT 1 , '3,7,11,25,44,56,77,32,34,55'
UNION ALL SELECT 2 , '3,7,25,44,37,89,56,99,103,13'
UNION ALL SELECT 1 , '3,10,11,25,44,56,33,32,34,55'
;
SELECT user_id
, SUM(REGEXP_COUNT(value_list,',')+1) value_count
FROM examples
GROUP BY 1
;
Output
user_id | value_count
---------+-------------
1 | 20
2 | 10
This answers the original version of the question.
You can count the number of comma-delimited values with:
select (case when col = '' then 0
else length(col) - length(replace(col, ',', '')) + 1
end) as values_count
from t;
That said, you should fix your data model so you are not storing multiple values in a column. It is particularly irksome that you are storing numbers as strings, as well. You want a junction/association table.

Is there a SUBSTR alternative in Oracle SQL

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.

regexp_substr to bring back data before a foward slash

I have the following pattern of characters in a dataset. I need to manipulate the data & cross refer it to another table. I'm trying to write a regexp_substr to bring back data before a foward slash starting from the left. for example:-
abc/ab/123/zzz
so I need to get the following results back to then compare to another table
abc
abc/ab
abc/ab/123
I have worked out the other logic but an struggling with the various regexp.
Here is the recursive query with SUBSTR and INSTR:
with cte(col) as
(
select substr(col, 1, instr(col, '/', -1) - 1) from mytable
union all
select substr(col, 1, instr(col, '/', -1) - 1) from cte where instr(col, '/') > 0
)
select col from cte;
And here is the query with REGEXP_REPLACE:
with cte(col) as
(
select regexp_replace(col, '/[^/]*$', '') from mytable
union all
select regexp_replace(col, '/[^/]*$', '') from cte where instr(col, '/') > 0
)
select col from cte;
You don't need a regular expression. You can do it with (faster) string functions:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE test_data ( id, value ) AS
SELECT 1, 'abc/ab/123/zzz' FROM DUAL;
Query 1:
WITH bounds ( id, value, end_pos ) AS (
SELECT id,
value,
INSTR( value, '/', 1 )
FROM test_data
WHERE INSTR( value, '/', 1 ) > 0
UNION ALL
SELECT id,
value,
INSTR( value, '/', end_pos + 1 )
FROM bounds
WHERE INSTR( value, '/', end_pos + 1 ) > 0
)
SELECT id,
SUBSTR( value, 1, end_pos ) AS item
FROM bounds
ORDER BY id, end_pos
Results:
| ID | ITEM |
|----|-------------|
| 1 | abc/ |
| 1 | abc/ab/ |
| 1 | abc/ab/123/ |
However, if you did want to use regular expressions then you could do:
Query 2:
WITH bounds ( id, value, lvl, item ) AS (
SELECT id,
value,
1,
REGEXP_SUBSTR( value, '.*?/', 1, 1 )
FROM test_data
WHERE REGEXP_SUBSTR( value, '.*?/', 1, 1 ) IS NOT NULL
UNION ALL
SELECT id,
value,
lvl + 1,
item || REGEXP_SUBSTR( value, '.*?/', 1, lvl + 1 )
FROM bounds
WHERE REGEXP_SUBSTR( value, '.*?/', 1, lvl + 1 ) IS NOT NULL
)
SELECT id,
item
FROM bounds
Results:
| ID | ITEM |
|----|-------------|
| 1 | abc/ |
| 1 | abc/ab/ |
| 1 | abc/ab/123/ |