Oracle SQL - Select Max Integer Value from UserID String - sql

I want to select the MAX value of the integer associated with the UserID in my Oracle database table to generate the next username for users with similar UserID.
The UserID contains values such as below. There is no fixed pattern of characters before the integer as the string is a username.
TKe10
TKe9
TKe12
TomKelly13
TomKelly9
PJames12
PJames7
I tried using the query below but it always gives TKe9 OR TomKelly9 OR PJames7 as the MAX value.
SELECT * FROM
(SELECT MAX(UserID) from PV_USERS
WHERE REGEXP_LIKE (UserID, '^'|| '<some_user_id>'|| '[^A-
Za-z][0-9]*'));
I have also tried using ORDER BY DESC WHERE ROWNUM<=1 but it also gives the same output.

You need to extract just the numeric part of the ID, which you can do with
regexp_substr(userid, '[0-9]*$')
and then convert that to a number before finding the maximum (otherwise you'll still be doing string comparison, and sorting 9 before 10):
max(to_number(regexp_substr(userid, '[0-9]*$')))
and you probably want to allow for the ID root you're checking to not exist at all yet, which you can do with nvl() or coalesce():
select coalesce(max(to_number(regexp_substr(userid, '[0-9]*$'))), 0) as max_num
from pv_users
where regexp_like(userid, '^'|| 'TomKelly'|| '[0-9]*');
MAX_NUM
----------
13
select coalesce(max(to_number(regexp_substr(userid, '[0-9]*$'))), 0) as max_num
from pv_users
where regexp_like(userid, '^'|| 'PJames'|| '[0-9]*');
MAX_NUM
----------
12
select coalesce(max(to_number(regexp_substr(userid, '[0-9]*$'))), 0) as max_num
from pv_users
where regexp_like(userid, '^'|| 'NewName'|| '[0-9]*');
MAX_NUM
----------
0
... and then add 1 and append back onto the root to get the next ID.
Depending on your business rules, you might want to make the filter case-insensitive.
You should be aware that two sessions performing this operation simultaneously will see the same result, so both would try to create the same ID, e.g. TomKelly14. You either need to serialise this generation operation, or include a fall back - like checking if you get a PK violation when you try to insert the new value into the table and repeating if that happens.

with temp as
(
select 'Tke10' userid from dual
union all
select 'Tke9' userid from dual
union all
select 'Tke12' userid from dual
union all
select 'Tomkelly13' userid from dual
union all
select 'Tomkelly9' userid from dual
union all
select 'Pjames12' userid from dual
union all
select 'Pjames7' userid from dual
)
select A||B from (
select
substr(userid,1,instr(userid,to_number(regexp_substr(userid,'\d+$')))-1) A
,max(to_number(regexp_substr(userid,'\d+$'))) B
from temp
group by substr(userid,1,instr(userid,to_number(regexp_substr(userid,'\d+$')))-1)
)
;

Related

SQL Query to select a specific part of the values in a column

I have a table in a database and one of the columns of the table is of the format AAA-BBBBBBB-CCCCCCC(in the table below column Id) where A, B and C are all numbers (0-9). I want to write a SELECT query such that for this column I only want the values in the format BBBBBBB-CCCCCCC. I am new to SQL so not sure how to do this. I tried using SPLIT_PART on - but not sure how to join the second and third parts.
Table -
Id
Name
Age
123-4567890-1234567
First Name
199
456-7890123-4567890
Hulkamania
200
So when the query is written the output should be like
Output
4567890-1234567
7890123-4567890
As mentioned in the request comments, you should not store a combined number, when you are interested in its parts. Store the parts in separate columns instead.
However, as the format is fixed 'AAA-BBBBBBB-CCCCCCC', it is very easy to get the substring you are interested in. Just take the string from the fifth position on:
select substr(col, 5) from mytable;
You can select the right part of a column starting at the 4th character
SELECT RIGHT(Id, LEN(Id)-4) AS TrimmedId;
Another option using regexp_substr
with x ( c1,c2,c3 ) as
(
select '123-4567890-1234567', 'First Name' , 199 from dual union all
select '456-7890123-4567890', 'Hulkamania' , 200 from dual
)
select regexp_substr(c1,'[^-]+',1,2)||'-'||regexp_substr(c1,'[^-]+',1,3) as result from x ;
Demo
SQL> with x ( c1,c2,c3 ) as
(
select '123-4567890-1234567', 'First Name' , 199 from dual union all
select '456-7890123-4567890', 'Hulkamania' , 200 from dual
)
select regexp_substr(c1,'[^-]+',1,2)||'-'||regexp_substr(c1,'[^-]+',1,3) as result from x ; 2 3 4 5 6
RESULT
--------------------------------------------------------------------------------
4567890-1234567
7890123-4567890
SQL>

Extracting most recent date from VARCHAR2

Data:
USER_ID VIOLATION_DATES
--------------------------------
1 18-Jul-21 > 24-Jul-21
2 05-Aug-21
3 09-Jun-21
1 18-Jul-21
I have a table that has columns for Users and their dates of violations. I want to extract the most recent violation for each user.
This is the query I've written:
select
USR_ID,
max(to_date(VIOLATION_DATES, 'DD-MON-YY')) as Most_Recent_VIOLATIONS
from
table
group by
USR_ID
However I get this error:
ORA-01830: date format picture ends before converting entire input string
I believe it has something to do with the way the most recent violation is appended (18-Jul-21 > 24-Jul-21 ). Can anyone provide any clarity on how I can extract the most recent date for each user? for example:
USER_ID VIOLATION_DATES
--------------------------
1 24-Jul-21
2 05-Aug-21
3 09-Jun-21
I understand that this storage method isn't ideal but this is out of my control.
You can split the multi-value string into separate rows, then "group by" as normal (the with clause is just to provide test data - substitute your actual table):
with demo (user_id, violation_dates) as
( select 1, '18-Jul-21 > 24-Jul-21' from dual union all
select 2, '05-Aug-21' from dual union all
select 3, '09-Jun-21' from dual union all
select 1, '18-Jul-21' from dual )
select user_id
, max(to_date(regexp_substr(d.violation_dates, '[^> ]+', 1, r.rn) default null on conversion error,'DD-MON-YY')) as violation_date
from demo d
cross apply
( select rownum as rn
from dual connect by rownum <= regexp_count(d.violation_dates,'>') +1 ) r
group by user_id
order by 1;
"default null on conversion error" requires Oracle 12.2.

Make in (SQL) dynamic for incoming values

Is it possible for in statement to be dynamic? like dynamic comma separation
for example:
DATA=1
select * from dual
where
account_id in (*DATA);
DATA=2
select * from dual
where
account_id in (*DATA1,*DATA2);
FOR DATA=n
how will i make the in statement dynamic/flexible (comma) for unknown quantity.
select * from dual
where
account_id in (*DATAn,*DATAn+1,etc);
A hierarchical query might help.
acc CTE represents sample data
lines #9 - 11 are what you might be looking for; data is concatenated with level pseudocolumn's value returned by a hierarchical query
Here you go:
SQL> with acc (account_id) as
2 (select 'data1' from dual union all
3 select 'data2' from dual union all
4 select 'data3' from dual union all
5 select 'data4' from dual
6 )
7 select *
8 from acc
9 where account_id in (select 'data' || level
10 from dual
11 connect by level <= &n
12 );
Enter value for n: 1
ACCOU
-----
data1
SQL> /
Enter value for n: 3
ACCOU
-----
data1
data2
data3
SQL>
As I can see, You are using the number in Where clause, Substitution will be enough to solve your problem.
See the example below:
CREATE table t(col1 number);
insert into t values(1);
insert into t values(2);
insert into t values(3);
-- Substitution variable initialization
define data1=1;
define data2='1,2';
--
-- With data1
select * from t where col1 in (&data1);
Output:
-- With data2
select * from t where col1 in (&data2);
Output:
Hope, This will be helpful to you.
Cheers!!
The basic problem is not the listagg function but a major misconception that just because elements in a string list are comma separated that a string with commas in it is a list. Not So. Consider a table with the following rows.
Key
- Data1
- Data2
- Data1,Data2
And the query: Select * from table_name where key = 'wanted_key'; Now if all commas separate independent elements then what value in for "wanted_Key" is needed to return only the 3rd row above? Even with the IN predicate 'Data1,Data2' is still just 1 value, not 2. For 2 values it would have to be ('Data1','Data2').
The problem you're having with Listagg is not because of the comma but because it's not the appropriate function. Listagg takes values from multiple rows and combines then into a single comma separated string but not comma separated list. Example:
with elements as
( select 'A' code, 'Data1' item from dual union all
select 'A', 'Data2' from dual union all
select 'A', 'Data3' from dual
)
select listagg( item, ',') within group (order by item)
from elements group by code;
(You might also want to try 'Data1,Data2' as a single element. Watch out.
What you require is a query that breaks out each element separately. This can be done with
with element_list as
(select 'Data1,Data2,Data3' items from dual) -- get paraemter string
, parsed as
(select regexp_substr(items,'[^,]+',1,level) item
from element_list connect by regexp_substr(items,'[^,]+',1,level) is not null -- parse string to elements
)
The CTE "parsed" can now be used as table/view in your query.
This will not perform as well as querying directly with a parameter, but performance degradation is the cost of dynamic/flexible queries.
Also as set up this will NOT handle parameters which contain commas within an individual element. That would require much more code as you would have to determine/design how to keep the comma in those elements.

how to select a list of 10,000 unique ids from dual in oracle SQL

So I can't create or edit tables (I'm a user with read only permission) and I want to look up 10,000 unique id's. I can't put them inside of an IN() statement because oracle limits over 1000 items.
Is it possible to select this entire list from the DUAL table in oracle? Something like:
select
'id123,id8923,id32983,id032098,id308230,id32983289'
from DUAL
Use a collection (they are not limited to 1000 items like an IN clause is):
SELECT COLUMN_VALUE AS id
FROM TABLE(
SYS.ODCIVARCHAR2LIST(
'id123', 'id8923', 'id32983', 'id032098', 'id308230', 'id32983289'
)
)
SYS.ODCIVARCHAR2LIST and SYS.ODCINUMBERLIST are collection types that are supplied in the SYS schema.
You can join this directly to whichever table you are SELECTing from without needing to use the DUAL table:
SELECT y.*
FROM your_table y
INNER JOIN TABLE(
SYS.ODCIVARCHAR2LIST(
'id123', 'id8923', 'id32983', 'id032098', 'id308230', 'id32983289'
)
) i
ON (y.id = i.COLUMN_VALUE);
If you can get a collection type created then you do not even need the TABLE expression and can use it directly in the WHERE clause using the MEMBER OF operator:
CREATE OR REPLACE TYPE stringlist IS TABLE OF VARCHAR2(200);
/
SELECT *
FROM yourtable
WHERE id MEMBER OF stringlist(
'id123', 'id8923', 'id32983', 'id032098', 'id308230', 'id32983289'
);
You can even pass the values as a bind parameter - see my answer here
Oracle still doesn't support the VALUES row constructor, so there are only two ugly workarounds:
The 1000 item limit does not apply for multi-column IN conditions
Expression Lists
A comma-delimited list of expressions can contain no more than 1000
expressions. A comma-delimited list of sets of expressions can contain
any number of sets, but each set can contain no more than 1000
expressions.
so you can do:
where (1,id) in ( (1,'id123'),
(1,'id8923'),
(1,'id32983'),
(1,'id032098'), .... )
Or using a big ugly UNION ALL:
with idlist (xid) as (
select 'id123' from dual union all
select 'id8923' from dual union all
.....
select 'id32983' from dual
)
select ...
from some_table
where id in (select xid from idlist);
One solution is the WITH clause:
with ids as (
select 'id123' as uid from dual union all
select 'id8923' as uid from dual union all
select 'id32983' as uid from dual union all
select 'id032098' as uid from dual union all
select 'id308230' as uid from dual union all
select 'id32983289' as uid from dual
)
select *
from ids
join your_table yt
on yt.id = ids.uid
This may seem like a bit of a chore but presumably you have your list of UIDs in a spreadsheet or whatever. If so it's a cinch to generate those select statements using regular expressions. Just cut'n'paste the column into an editor which supports regex search and replace.
Yet another work-around
select *
from t
where id in ('id1','id2','id3',...,'id1000')
or id in ('id1001','id1002','id1003',...,'id2000')
or id in ('id2001','id2002','id2003',...,'id3000')
or ...

Numeric Overflow in Recursive Query : Teradata

I'm new to teradata. I want to insert numbers 1 to 1000 into the table test_seq, which is created as below.
create table test_seq(
seq_id integer
);
After searching on this site, I came up with recusrive query to insert the numbers.
insert into test_seq(seq_id)
with recursive cte(id) as (
select 1 from test_dual
union all
select id + 1 from cte
where id + 1 <= 1000
)
select id from cte;
test_dual is created as follows and it contains just a single value. (something like DUAL in Oracle)
create table test_dual(
test_dummy varchar(1)
);
insert into test_dual values ('X');
But, when I run the insert statement, I get the error, Failure 2616 Numeric overflow occurred during computation.
What did I do wrong here? Isn't the integer datatype enough to hold numeric value 1000?
Also, is there a way to write the query so that i can do away with test_dual table?
When you simply write 1 the parser assigns the best matching datatype to it, which is a BYTEINT. The valid range of values for BYTEINT is -128 to 127, so just add a typecast to INT :-)
Usually you don't need a dummy DUAL table in Teradata, "SELECT 1;" is valid, but in some cases the parser still insists on a FROM (don't ask me why). This trick should work:
SEL * FROM (SELECT 1 AS x) AS dt;
You can create a view on this:
REPLACE VIEW oDUAL AS SELECT * FROM (SELECT 'X' AS dummy) AS dt;
Explain "SELECT 1 FROM oDUAL;" is a bit stupid, so a real table might be better. But to get efficient access (= single AMP/single row) it must be defined as follows:
CREATE TABLE dual_tbl(
dummy VARCHAR(1) CHECK ( dummy = 'X')
) UNIQUE PRIMARY INDEX(dummy); -- i remember having fun when you inserted another row in Oracle's DUAL :_)
INSERT INTO dual_tbl VALUES ('X');
REPLACE VIEW oDUAL AS SELECT dummy FROM dual_tbl WHERE dummy = 'X';
insert into test_seq(seq_id)
with recursive cte(id) as (
select cast(1 as int) from oDUAL
union all
select id + 1 from cte
where id + 1 <= 1000
)
select id from cte;
But recursion is not an appropriate way to get a range of numbers as it's sequential and always an "all-AMP step" even if it the data resides on a single AMP like in this case.
If it's less than 73414 values (201 years) better use sys_calendar.calendar (or any other table with a known sequence of numbers) :
SELECT day_of_calendar
FROM sys_calendar.CALENDAR
WHERE day_of_calendar BETWEEN 1 AND 1000;
Otherwise use CROSS joins, e.g. to get numbers from 1 to 1,000,000:
WITH cte (i) AS
( SELECT day_of_calendar
FROM sys_calendar.CALENDAR
WHERE day_of_calendar BETWEEN 1 AND 1000
)
SELECT
(t2.i - 1) * 1000 + t1.i
FROM cte AS t1 CROSS JOIN cte AS t2;