I want to match all characters in a string until I hit one of the following substrings:
'='
'+%'
'-%'
I had no problem negating a set of single characters (i.e. [^=+-]), and I can positively match the strings with
(=|\+%|-%)
but I can't seem to figure out the syntax for negating the set. Any suggestions?
Couldn't do it with REGEXP_SUBSTR alone but here is one option:
with temp as
(
select 'TEST1=TEST1' test1 from dual union all
select 'TEST2+%TEST2' test1 from dual union all
select 'TEST3+TEST3' test1 from dual union all
select 'TEST4-%TEST4' test1 from dual union all
select 'TEST5-TEST5' test1 from dual
)
SELECT test1,
CASE
WHEN REGEXP_INSTR(test1, '+%|=|-%') = 0 THEN test1
ELSE REGEXP_REPLACE(SUBSTR(test1, 1, REGEXP_INSTR(test1, '-%|+%|=')), '[-=+%]')
END
result
FROM temp
Related
I have the following query:
SELECT * from tWords where WordAton IN ("bbb", "aaa2", "ccc", "aaa1")
the query returns first the results for "aaa1" then for "aaa2" then for "bbb" and then for "ccc". Is there a way to return the results in the order of the input array, which means first the results for "bbb" then for "aaa2"... etc.
Thank you in advance.
You can apply conditional ordering like this:
SELECT *
from tWords
where WordAton IN ('bbb', 'aaa2', 'ccc', 'aaa1')
order by case WordAton
when 'bbb' then 1
when 'aaa2' then 2
when 'ccc' then 3
when 'aaa1' then 4
end
In SQL (not just SQLite), the only way to always return rows in a given order is with a SQL ORDER BY ... clause. So the short answer is, "No", there's no simple way to return rows in the order given by the contents of an IN (...) clause.
You could use a common table expression (CTE) to define a sort order, but that's usually not worth the trouble. This isn't the same thing as ordering by the contents of a IN (...) clause, but it looks the same. (You're ordering by the sort order specified in the CTE.)
with ordered_words as (
select 1 as sort_order, 'bbb' as WordAton union
select 2, 'aaa2' union
select 3, 'ccc' union
select 4, 'aaa1'
)
select t.WordAton
from tWords t
join ordered_words o on t.WordAton = o.WordAton
where t.WordAton in ('bbb', 'aaa2', 'ccc', 'aaa1')
order by o.sort_order;
I want to sort semicolon separated values per row in a column. Eg.
Input:
abc;pqr;def;mno
xyz;pqr;abc
abc
xyz;jkl
Output:
abc;def;mno;pqr
abc;pqr;xyz
abc
jkl;xyz
Can anyone help?
Perhaps something like this. Breaking it down:
First we need to break up the strings into their component tokens, and then reassemble them, using LISTAGG(), while ordering them alphabetically.
There are many ways to break up a symbol-separated string. Here I demonstrate the use of a hierarchical query. It requires that the input strings be uniquely distinguished from each other. Since the exact same semicolon-separated string may appear more than once, and since there is no info from the OP about any other unique column in the table, I create a unique identifier (using ROW_NUMBER()) in the most deeply nested subquery. Then I run the hierarchical query to break up the inputs and then reassemble them in the outermost SELECT.
with
test_data as (
select 'abc;pqr;def;mno' as str from dual union all
select 'xyz;pqr;abc' from dual union all
select 'abc' from dual union all
select 'xyz;jkl' from dual
)
-- End of test data (not part of the solution!)
-- SQL query begins BELOW THIS LINE.
select str,
listagg(token, ';') within group (order by token) as sorted_str
from (
select rn, str,
regexp_substr(str, '([^;]*)(;|$)', 1, level, null, 1) as token
from (
select str, row_number() over (order by null) as rn
from test_data
)
connect by level <= length(str) - length(replace(str, ';')) + 1
and prior rn = rn
and prior sys_guid() is not null
)
group by rn, str
;
STR SORTED_STR
--------------- ---------------
abc;pqr;def;mno abc;def;mno;pqr
xyz;pqr;abc abc;pqr;xyz
abc abc
xyz;jkl jkl;xyz
4 rows selected.
I am new in oracle and I want to get the value from a column which is stored as "Ashu||123 ||Main Menu|ENG||1|1".
as you can see each value is separated by || symbol.in the above value Ashu is the customer name and 123 is the id, I want both value as customer-name and customer id.
In the query below, I include some test data "on the fly" (not part of the solution; use your actual table name instead of test_data in the main query, and your actual column name instead of str). I included several special cases for testing, to make sure the query works correctly in all cases. I assume the first value (before the first ||) is the customer name and the second the customer id, and the rest of the input string can be ignored. I looked in particular to see that the query handles null values correctly (assuming they may happen in your data).
I left the customer id as a string; if it must be a number, it may be better to wrap it all within to_number().
with
test_data ( str ) as (
select 'Ashu||123||Main Menu|ENG||1|1' from dual union all
select 'Misha||125' from dual union all
select 'Babu||||Main Menu|NZL||?' from dual union all
select 'Rim||' from dual union all
select 'Todd' from dual union all
select '||139||Other Stuff' from dual
)
-- end of test data (only for testing and illustration) - not part of solution
-- SQL query begins BELOW THIS LINE
select str,
regexp_substr(str, '([^|]*)(\|\||$)', 1, 1, null, 1) as cust_name,
regexp_substr(str, '([^|]*)(\|\||$)', 1, 2, null, 1) as cust_id
from test_data
;
STR CUST_NAME CUST_ID
----------------------------- --------- -------
Ashu||123||Main Menu|ENG||1|1 Ashu 123
Misha||125 Misha 125
Babu||||Main Menu|NZL||? Babu
Rim|| Rim
Todd Todd
||139||Other Stuff 139
6 rows selected.
Is it possible to check multiple Expressions inside "Decode" function in PL SQL.
My requirement is basically
SELECT value1,
DECODE(Expression1 AND Expression2,
search1 , search2,
,'pass result',
'fail result') value2
FROM dual;
Or is there are workaround for this?
The simplest answer is don't use decode, use a case statement instead, e.g.
select 1 value1,
case when 'a' = 'a' and 'b' = 'b' then 'pass'
else 'fail'
end result
from dual;
VALUE1 RESULT
---------- ------
1 pass
It is possible to use DECODE, but you'd have to use nested DECODEs and you'd end up with something that's much harder to read, understand and therefore maintain. And what happens if you need to add a third condition? Or maybe there are multiple sets of results that would result in a pass? You'd end up with something truly horrendous to try and understand!
But, if you really must, here's an example (along with the corresponding CASE expression solution, which is (IMHO) much easier to read):
with sample_data AS (SELECT 1 ID, 'a' col1, 'b' col2 FROM dual UNION ALL
SELECT 2 ID, 'a' col1, 'c' col2 FROM dual UNION ALL
SELECT 3 ID, 'd' col1, 'b' col2 FROM dual UNION ALL
SELECT 4 ID, 'e' col1, 'f' col2 FROM dual)
SELECT ID,
CASE WHEN col1 = 'a' AND col2 = 'b' THEN 'pass'
ELSE 'fail'
END case_result,
DECODE(col1, 'a', DECODE(col2, 'b', 'pass',
'fail'),
'fail') decode_result
FROM sample_data;
ID CASE_RESULT DECODE_RESULT
---------- ----------- -------------
1 pass pass
2 fail fail
3 fail fail
4 fail fail
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