Select into with substring and orderby - oracle

I need to pass a string from a substring of a database column into a variable.
Originally I did this before I realised that this could return more than one row. In the case of this returning more than I row I need to make sure I only return the most recent value (the most recent date).
SELECT SUBSTR (
description
,1
, INSTR (
description
,' ' )
- 1 )
INTO v_value
FROM sdok s
WHERE s.type = 2
AND s.case_no = in_object.case_no;
Which is why I attempted this:
SELECT *
FROM (SELECT SUBSTR (
description
,1
, INSTR (
description
,' ' )
- 1 )
,s.case_no
,s.date
FROM sdok s
WHERE s.type = 2
AND NVL ( s.deleted, 'N' ) <> 'J'
ORDER BY s.date)
WHERE ROWNUM = 1
AND s.case_no = in_object.case_no;
This returns 3 columns of data, which enables me to check I have the right case and date, but I still only really need the value from column 1 (the substring) to pass into my variable. I tried putting INTO after the SUBSTR, but before the call to s.case_no and s.date, but that doesn't work. Yet I need to do the date comparison (to get the most recent date) and get the right case_no, before I can get the first row. I'm guessing there is another way to compare the case_no and order by date, so that I only return the value of the substring?
Help please?

This is how I understood the question:
SELECT x.a_substring
INTO local_variable
FROM (SELECT SUBSTR (description
,1
,INSTR (description, ' ' ) - 1
) a_substring
--
,s.case_no
,s.date
FROM sdok s
WHERE s.type = 2
AND NVL ( s.deleted, 'N' ) <> 'J'
AND s.case_no = 'xxxxxxxxx'
ORDER BY s.date
) x
WHERE ROWNUM = 1

You can do much easier using "first first 1 rows only" or aggregate function max(str)keep(dense_rank first order by s.date):
SELECT
SUBSTR (description
,1
,INSTR (description, ' ' ) - 1
) a_substring INTO local_variable
FROM sdok s
WHERE s.type = 2
AND NVL ( s.deleted, 'N' ) <> 'J'
AND s.case_no = 'xxxxxxxxx'
ORDER BY s.date
fetch first 1 rows only
/
SELECT max(
SUBSTR (description
,1
,INSTR (description, ' ' ) - 1
)
)keep(dense_rank first order by s.date) a_substring
INTO local_variable
FROM sdok s
WHERE s.type = 2
AND NVL ( s.deleted, 'N' ) <> 'J'
AND s.case_no = 'xxxxxxxxx'
/

Related

How to Shorted Regular Expression

I have a RegExp as below, when I use it in Oracle SQL, I got ORA-12723 error, how can I let it in the shortest format?
WITH test_data ( str ) AS (
SELECT 'This is extension 1234, here is mobile phone: 090-1234-5678 maybe 8+24-98765432. Then +1-(234)-090-345 also 86 21-4566-4556' AS str FROM DUAL
)
SELECT TRIM(
TRAILING ',' FROM
REGEXP_REPLACE(
str,
'.*?(\+?\d{1,11}[-,\+]\d{1,11}[-,\+]\d{1,11}[-,\+]\d{1,11}[-,\+]\d{1,11}[-,\+]\d{3,11}|\+?\d{1,11}[-,\+]\d{1,11}[-,\+]\d{1,11}[-,\+]\d{1,11}[-,\+]\d{3,11}|\+?\d{1,11}[-,\+]\d{1,11}[-,\+]\d{1,11}[-,\+]\d{3,11}|\+?\d{1,11}[-,\+]\d{1,11}[-,\+]\d{3,11}|\+?\d{1,11}[-,\+]\d{3,11}|\d{3,11}|$)',
'\1,'
)
) AS replaced_str
FROM test_data
The result what I wonder as below:
1234,090-1234-5678,8+24-98765432,+1-(234)-090-345,86 21-4566-4556
Consider this approach. This uses CONNECT BY to traverse the string and parse it into elements that are separated by a space or the end of the line. Then for each element, remove non-digit characters ('\D'). Lastly use LISTAGG() to put the elements back into one comma delimited string.
WITH test_data(str) AS (
SELECT 'Txa233g141b Ta233141 Ta233142 Ta233147zz Ta233xx148zz' AS str FROM DUAL
)
SELECT listagg(regexp_replace(regexp_substr(str, '(.*?)( |$)', 1, level, null, 1), '\D'), ',')
within group (order by str) replaced_str
FROM test_data
connect by level <= regexp_count(str, ' ') + 1;
REPLACED_STR
--------------------------------------------------------------------------------
233141,233141,233142,233147,233148
1 row selected.

Oracle - Performance between Regexp_substr and Instr

As my title, somecases I see Regexp_substr faster and less cost than Instr and somecases its opposite.
I don't know when I should use Instr or Regexp_substr, someone can explain for me and tell me benefit of each? The example following:
**Regexp_substr:**
SELECT * FROM tabl1
WHERE 1 = 1
AND col1 IN (
SELECT regexp_substr(abc,'[^,]+',1,level) AS A
FROM (
SELECT 001 abc -- replace with parameter
FROM DUAL
)
CONNECT BY LEVEL <= LENGTH (REGEXP_REPLACE (abc,'[^,]'))+1 );
**Instr:**
SELECT * FROM tabl1
WHERE 1 = 1
AND INSTR (',' || '001' || ',',',' || col1 || ',') > 0 ;
Thanks!

stored procedure parameter of CSV input to return all records

I have the following Oracle stored procedure that takes on a string of CSV of user ID's which would return the list of users to the output cursor which works fine:
create or replace PROCEDURE GET_USERS_BY_IDS
(
v_cur OUT sys_refcursor
,v_userIdsCsv IN varchar2 DEFAULT ''
) AS
BEGIN
open v_cur for
with userIds
as
(
select
trim( substr (txt,
instr (txt, ',', 1, level ) + 1,
instr (txt, ',', 1, level+1) - instr (txt, ',', 1, level) -1 ) )
as token
from (select ','||v_userIdsCsv||',' txt
from dual)
connect by level <=
length(v_userIdsCsv)-length(replace(v_userIdsCsv,',',''))+1
)
select
id
,lastname
,firstname
from
users
where
id in (select * from userIds);
END GET_USERS_BY_IDS;
so by doing exec GET_USERS_BY_IDS(:cur1, '123,456') I can get users of IDs of 123 and 456. However I would like to return ALL users if I pass in an empty string, i.e. exec GET_USERS_BY_IDS(:cur1, '') would return all users. What do I have to change in the sproc code to accomplish that? Thanks.
Consider this solution using REGEXP functions which I feel simplifies things. I also incorporated the test from my comment as well. Note the REGEXP handles a NULL list element too:
create or replace PROCEDURE GET_USERS_BY_IDS
(
v_cur OUT sys_refcursor
,v_userIdsCsv IN varchar2 DEFAULT '1'
) AS
BEGIN
open v_cur for
with userIds
as
(
select trim( regexp_substr(v_userIdsCsv, '(.*?)(,|$)', 1, level, NULL, 1) ) as token
from dual
connect by level <= regexp_count(v_userIdsCsv, ',') + 1
)
select
id
,lastname
,firstname
from
users
where v_userIdsCsv = '1' -- Empty list returns all users
OR id in (select * from userIds);
END GET_USERS_BY_IDS;
Its untested so let us know what happens if you test it.
Do you mean, something as simple as
BEGIN
if v_userIdsCsv = '' then
open v_cur for select id, lastname, firstname from users
else (rest of your code)
end if;
?
OK, with the confirmation in comments...
It seems you should be able to change the WHERE condition at the very end:
where
v_userIdsCsv = '' or id in (select * from userIds);
Outer join between user and user_ids. And clever where condition.
Has it helped?
with csv as (select '321,333' aa from dual)
,userIds
as
(
select
trim( substr (txt,
instr (txt, ',', 1, level ) + 1,
instr (txt, ',', 1, level+1) - instr (txt, ',', 1, level) -1 ) )
as token
from (select ','||(select aa from csv )||',' txt
from dual)
connect by level <=
length((select aa from csv ))-length(replace((select aa from csv),',',''))+1
)
select
user_id
,username
from
all_users a
left join userIds b on a.user_id = b.token
where nvl2((select aa from csv),b.token,a.user_id) = a.user_id
I think I found a more efficient way to do this now. In the where statement, I can just short-circuit it if the input parameter is a blank:
where
v_userIdsCsv = '' or
id in (select * from userIds);

Gettin error ORA-01732: data manipulation operation not legal on this view

Hi Gurus I am getting the error ORA-01732: data manipulation operation not legal on this view
when executing the below query
UPDATE (SELECT CR.AMOUNT AS AMOUNT,
CASE
WHEN MRG.AMOUNT_USD=0
THEN CR.AMOUNT
ELSE MRG.AMOUNT_USD
END AS AMOUNT_BILAT,
CR.ISUPDATED
FROM CRS_TT_BILAT_EXCL_MERGE1 MRG,CRS_T_CURRENT_RATES1 CR
WHERE SUBSTR(CR.DNIS_CD,1,3)=MRG.DNIS_CD
AND CR.PRODUCT_CUST_ID = MRG.PRODUCT_CUST_ID
AND CR.ISUPDATED <> 'Y'
AND ROWNUM = 1)
SET AMOUNT = AMOUNT_BILAT;
CR.ISUPDATED = 'Y';
I have simplified the above code from the below query
UPDATE CRS_T_CURRENT_RATES1 CR
SET CR.AMOUNT =
(SELECT
CASE
WHEN MRG.AMOUNT_USD=0
THEN CR.AMOUNT
ELSE MRG.AMOUNT_USD
END
FROM CRS_TT_BILAT_EXCL_MERGE1 MRG
WHERE SUBSTR(CR.DNIS_CD,1,3)=MRG.DNIS_CD
AND CR.PRODUCT_CUST_ID = MRG.PRODUCT_CUST_ID
AND ROWNUM = 1),
CR.ISUPDATED = 'Y'
WHERE EXISTS
(SELECT 1 FROM CRS_TT_BILAT_EXCL_MERGE1 MRG WHERE MRG.DNIS_CD = SUBSTR(CR.DNIS_CD, 1,3) AND CR.PRODUCT_CUST_ID = MRG.PRODUCT_CUST_ID )
AND
CR.ISUPDATED <> 'Y';
I was trying to optimize the 2nd query, since the second query uses two selects i was trying to replace that with a single query. Can any one please help me on this?
MERGE statement with selecting 1st row for each (AMOUNT_USD, DNIS_CD, PRODUCT_CUST_ID) - ROWNUM=1 condition from your query:
MERGE INTO CRS_T_CURRENT_RATES1 CR
USING (SELECT * FROM (
SELECT AMOUNT_USD,
DNIS_CD,
PRODUCT_CUST_ID
ROW_NUMBER() OVER (PARTITION BY AMOUNT_USD, DNIS_CD, PRODUCT_CUST_ID ORDER BY 1) AS ORD_NO
FROM CRS_TT_BILAT_EXCL_MERGE1
) WHERE ORD_NO = 1
) MGR
ON CR.PRODUCT_CUST_ID = MRG.PRODUCT_CUST_ID AND
SUBSTR(CR.DNIS_CD,1,3)=MRG.DNIS_CD
WHEN MATCHED THEN
UPDATE SET CR.AMOUNT = (CASE
WHEN MRG.AMOUNT_USD=0 THEN CR.AMOUNT
ELSE MRG.AMOUNT_USD
END),
ISUPDATED = 'Y'
WHERE ISUPDATED <> 'Y';
UPDATE: Using MERGE and ROWNUM won't work.
MERGE can help you avoid repeating SQL:
MERGE INTO CRS_T_CURRENT_RATES1 CR
USING
(
SELECT AMOUNT_USD, DNIS_CD, PRODUCT_CUST_ID
FROM CRS_TT_BILAT_EXCL_MERGE1
) MRG
ON
(
SUBSTR(CR.DNIS_CD,1,3) = MRG.DNIS_CD
AND CR.PRODUCT_CUST_ID = MRG.PRODUCT_CUST_ID
AND ROWNUM = 1
)
WHEN MATCHED THEN UPDATE SET
CR.ISUPDATED = 'Y',
CR.AMOUNT = CASE WHEN MRG.AMOUNT_USD=0 THEN CR.AMOUNT ELSE MRG.AMOUNT_USD END
Update
ROWNUM in the ON clause works on the updated table, not the data in the USING clause. The example below starts with two identical rows and only one gets updated:
create table test1(a number, b number);
insert into test1 values(1, 1);
insert into test1 values(1, 1);
merge into test1
using
(
select 1 a from dual
) test2
on (test1.a = test2.a and rownum = 1)
when matched then update set b = 0;
select * from test1;
A B
- -
1 0
1 1

Invalid number of returned comma delimited string in a IN clause in Oracle

I am trying to use a subquery that returns a comma delimited string in a IN clause.
The following way:
SELECT p.person_id, g.name || '>>' || p.firstname || ' ' || p.lastname as GROUP_PATH
FROM PERSON p
LEFT JOIN GROUP g ON (
g.group_id = p.group_id
)
WHERE p.person_id IN (
SELECT person_ids FROM other WHERE other_id = :OTHER_ID
)
ORDER BY lower(GROUP_PATH)
And I am getting the following error:
ORA-01722: invalid number.
Is there a better way to do this or even possible?
The most obvious explanation is that you are trying to do math with a string...
The attempted conversion of a
character string to a number failed
because the character string was not a
valid numeric literal. Only numeric
fields or character fields containing
numeric data may be used in arithmetic
functions or expressions. Only numeric
fields may be added to or subtracted
from dates.
http://ora-01722.ora-code.com/
Update #1:
Your description worries me:
I am trying to use a subquery that
returns a comma delimited string in a
IN clause.
Your subquery should not return a comma delimited string (unless g.group_id is a string and expects a comma delimited string). You must retrieve individual items in as many rows as needed (less than 1,000 anyway).
Update #2:
Just to make it clear:
SELECT *
FROM (
SELECT 1 AS FOO_ID FROM DUAL UNION SELECT 2 FROM DUAL UNION SELECT 3 FROM DUAL
) FOO;
FOO_ID
----------------------
1
2
3
You can do this:
SELECT *
FROM (
SELECT 1 AS FOO_ID FROM DUAL UNION SELECT 2 FROM DUAL UNION SELECT 3 FROM DUAL
) FOO
WHERE FOO_ID IN (1, 2);
FOO_ID
----------------------
1
2
But not this:
SELECT *
FROM (
SELECT 1 AS FOO_ID FROM DUAL UNION SELECT 2 FROM DUAL UNION SELECT 3 FROM DUAL
) FOO
WHERE FOO_ID IN ('1,2');
SQL Error: ORA-01722: invalid number
Because you cannot compare number 1 with string '1,2'. Subqueries follow similar rules:
SELECT *
FROM (
SELECT 1 AS FOO_ID FROM DUAL UNION SELECT 2 FROM DUAL UNION SELECT 3 FROM DUAL
) FOO
WHERE FOO_ID IN (
SELECT 1 AS FOO_ID FROM DUAL UNION SELECT 2 FROM DUAL
);
FOO_ID
----------------------
1
2
SELECT *
FROM (
SELECT 1 AS FOO_ID FROM DUAL UNION SELECT 2 FROM DUAL UNION SELECT 3 FROM DUAL
) FOO
WHERE FOO_ID IN (
SELECT '1,2' AS FOO_ID FROM DUAL
);
SQL Error: ORA-01722: invalid number
At a minimum, in order to reference the alias GROUP_PATH, you would need to need to use a nested subquery where the alias is defined before you reference it in your ORDER BY clause. That's realistically not causing the ORA-01722 error, but it is a problem
SELECT group_id, group_path
FROM (SELECT g.group_id,
g.name || '>>' || p.firstname || ' ' || p.lastname as GROUP_PATH
FROM PERSON p
LEFT JOIN GROUP g ON (
g.group_id = p.group_id
)
WHERE p.person_id IN (
SELECT person_ids FROM other WHERE other_id = :OTHER_ID
)
ORDER BY lower(GROUP_PATH)
If the PERSON_IDS column in the OTHER table is a comma separated list of values, your IN list is not going to do what you'd expect. You would need to transform the scalar string (that happens to have commas in it) into some sort of collection of multiple PERSON_ID values. There are various approaches to doing this, Tom Kyte has one example of using a variable IN list. Assuming you copy Tom's IN_LIST function, you should be able to do something like
SELECT group_id, group_path
FROM (SELECT g.group_id,
g.name || '>>' || p.firstname || ' ' || p.lastname as GROUP_PATH
FROM PERSON p
LEFT JOIN GROUP g ON (
g.group_id = p.group_id
)
WHERE p.person_id IN (
SELECT column_value
FROM TABLE(SELECT in_list(person_ids)
FROM other
WHERE other_id = :OTHER_ID)
)
ORDER BY lower(GROUP_PATH)

Resources