simplify oracle query without substr and union function - sql

I want to partition column 1 value into multiple values based on character length.
Example:
In address column first 10 characters should load in first row. Second 10 characters should load in 2nd row based on empno,empname.
source table:
empid:1 empname:aaa address: SONY/SB/743-0198/23-7.SCU P/N 743-0198-003. MOD DOT
Target table:
empid:1 empname:aaa address:SONY/SB/74
empid:1 empname:aaa address:3-0198/23-
empid:1 empname:aaa address:SCU P/N 74.
I tried it to using substr fun.
select empno,empname,substr(address,1,10) from emp
union
select empno,empname,substr(address,11,10) from emp;
Rather than using substr and union is their any other way to acheive this.

If you started with a list of numbers, you could do:
with n as (
select 1 as n from dual union all
select 11 from dual union all
select 21 from dual
)
select e.empno, e.empname, substr(e.address, n.n, 10)
from emp e join
n
on length(address) <= n.n + 10;
You can generate the values for n instead of listing them individually, using a recursive subquery, connect by level, or by using rownum on a table. For three values, though, it is easy enough to type them in.

Here is an attempt still using substr, but not union:
with w as
(
select 1 + 10 * (level - 1) n
from dual
connect by level <= 3
)
select t.empid, t.empname, substr(t.address, w.n, 10) address
from w
cross join test t
;

Related

Rewrite a query with "LEVEL" in snowflake

How can I write this SQL query into SNOWFLAKE?
SELECT LEVEL lv FROM DUAL CONNECT BY LEVEL <= 3;
You can find some good starting points by using CONNECT BY (https://docs.snowflake.com/en/sql-reference/constructs/connect-by.html) and here (https://docs.snowflake.com/en/user-guide/queries-hierarchical.html).
Snowflake is also supporting recursive CTEs.
It seems like you want to duplicate the rows three times. For a fixed, small multiplier such as 3, you could just enumerate the numbers:
select c.lv, a.*
from abc a
cross join (select 1 lv union all select 2 union all select 3) c
A more generic approach, in the spirit of the original query, uses a standard recursive common-table-expression to generate the numbers:
with cte as (
select 1 lv
union all select lv + 1 from cte where lv <= 3
)
select c.lv, a.*
from abc a
cross join cte c
There is level in snowflake. The differences from Oracle are:
In snowflake it's neccesary to use prior with connect by expression.
And you can't just select level - there should be any existing column in the select statement.
Example:
SELECT LEVEL, dummy FROM
(select 'X' dummy ) DUAL
CONNECT BY prior LEVEL <= 3;
LEVEL DUMMY
1 X
2 X
3 X
4 X
As per #Danil Suhomlinov post, we can further simplify using COLUMN1 for special table dual single column:
SELECT LEVEL, column1 FROM dual
CONNECT BY prior LEVEL <= 3;
LEVEL | COLUMN1
------+--------
1 |
2 |
3 |
4 |

How to remove first 3 and last 5 characters from account column in oracle sql?

How to remove first 3 and last 5 characters from account column in oracle sql ?
I have a column Account.
Sample account number "99k9220000709999"
The result should display the account number as "92200007".
Need oracle sql query. Can some one suggest ?
substr should do the trick - you start from position 4, and take the length of the column minus 5+3=8:
SELECT SUBSTR(mycolumn, 4, LENGTH(mycolumn) - 8)
FROM mytable
Another method left-trims the part upto the character where the letter ends and then extracts the remaing string without the last five chars as
WITH t(str) AS
(
SELECT '99k9220000709999' FROM DUAL
), t2 AS
(
SELECT regexp_substr(str,'[^[:alpha:]]+$') as str
FROM t
)
SELECT substr( str,1, length(str)-5 ) as "Result"
FROM t2
Why make it simple if we can make it complex (for fun)?
SQL> with test (id, account) as
2 (select 1, '99k9220000709999' from dual union all
3 select 2, '002LF004828xx' from dual
4 ),
5 split as
6 (select id,
7 column_value lvl,
8 regexp_substr(account, '[^.]', 1, column_value) chr,
9 max(length(account)) over (order by null) len
10 from test cross join table(cast(multiset(select level from dual
11 connect by level <= length(account)
12 ) as sys.odcinumberlist))
13 )
14 select id,
15 listagg(chr, '') within group (order by lvl) result
16 from split
17 where lvl between 4 and len - 5
18 group by id;
ID RESULT
---------- --------------------
1 92200007
2 LF004828
SQL>
You can play with substr() and length(). Another method is to use regular expressions:
select regexp_replace('99k9220000709999', '^.{3}(.*).{5}$', '\1')
from dual;
Not necessarily the most efficient way, though.

Oracle - generate records with alphanumeric number and character ascending

I want to have a column which holds customer unique key, consisting of number+character in an ascending order.
Is it possible to somehow instruct Oracle to generate many records, from 1a to 1z, then 2a to 2z, etc. up until 300000z:
CUSTOMER_NUM
------------
10a
10b
10c
.
.
10z
11a
11b
.
Best I have reached so far is something like this:
select ROUND(DBMS_RANDOM.VALUE(9,21)), dbms_random.string('l', 1) from dual;
Any ideas anyone please? I would like to generate test table with at least 300000 records.
Thanks!
Below query should give you desired result.
WITH
TABLE1 AS (SELECT LEVEL PART_1 FROM DUAL CONNECT BY LEVEL <= 300000),
TABLE2 AS (SELECT CHR(LEVEL+96) AS PART_2 FROM DUAL CONNECT BY LEVEL <27)
SELECT PART_1||PART_2 AS KEY_ID FROM TABLE1,TABLE2
You may need something like the following:
select alpha || num
from
(select substr('qwertyuiopasdfghjklzxcvbnm', level, 1) as alpha from dual connect by level <= 26)
cross join
(select level as num from dual connect by level <= 2)
order by num, alpha
The first query uses a string containing all the characters and splits it into 26 rows containing a single character.
The second query generates a given number of numbers to join with the characters.
You can try this one:
WITH n AS
(SELECT LEVEL AS Num FROM dual CONNECT BY LEVEL < 10),
c AS
(SELECT CHR(LEVEL + 96) AS alpha FROM dual CONNECT BY LEVEL <= 26)
SELECT num||alpha
FROM n
CROSS JOIN c;

Count number of vowels in a word and generate a report summary using SQL in Oracle [duplicate]

This question already has answers here:
Select vowels from a varchar, Oracle PL/SQL
(3 answers)
Closed 9 years ago.
I have a requirement to count number of vowels in a word. Is it possible to do it in SQL? I can easily implement it using a function(PL/SQL).
But I want it to be done in SQL.
I have no clue about how to start. To enhance my requirement, it is not just vowels. Just to count whatever alphabets list given. also To display the the number of occurences as well.
Example : STACKOVERFLOW
A 1
E 1
I 0
O 2
U 0
Note on possible duplicate
The possible duplicate link you suggest just count the letters; in order to generate a report, that approach won't help! If you wish, please re-evaluate.
Here is a solution:
with vowels as (
select 'a' as c from dual union all
select 'e' as c from dual union all
select 'i' as c from dual union all
select 'o' as c from dual union all
select 'u' as c from dual
)
select vowels.c,
sum(length(replace(lower(val), vowels.c, 'xx')) - length(val))
from (select 'StackOverflow' as val from dual) t cross join
vowels
group by vowels.c
order by vowels.c;
The count is managed by looking at the length of the string. Each vowel is replaced by two characters, increasing the overall length of the string by one each time the vowel appears.
SELECT vowel_list.chr,
COUNT(my_string.chr)
FROM
(SELECT UPPER(SUBSTR('STACKOVERFLOW',level,1)) AS chr
FROM dual
CONNECT BY level <=LENGTH('STACKOVERFLOW')
) my_string,
(SELECT UPPER(SUBSTR('AEIOU',level,1)) AS chr
FROM dual
CONNECT BY level <=LENGTH('AEIOU')
) vowel_list
WHERE vowel_list.chr = my_string.chr(+)
GROUP BY vowel_list.chr
ORDER BY 1;
CONNECT BY level <= n generates n virtual rows with level from 1,2... n
SUBSTR() extracts one character at a time, and finally the word is split into columns.
Outer join with the vowels word table, and you're done!

How to check any missing number from a series of numbers?

I am doing a project creating an admission system for a college; the technologies are Java and Oracle.
In one of the tables, pre-generated serial numbers are stored. Later, against those serial numbers, the applicant's form data will be entered. My requirement is that when the entry process is completed I will have to generate a Lot wise report. If during feeding pre-generated serial numbers any sequence numbers went missing.
For example, say in a table, the sequence numbers are 7001, 7002, 7004, 7005, 7006, 7010.
From the above series it is clear that from 7001 to 7010 the numbers missing are 7003, 7007, 7008 and 7009
Is there any DBMS function available in Oracle to find out these numbers or if any stored procedure may fulfill my purpose then please suggest an algorithm.
I can find some techniques in Java but for speed I want to find the solution in Oracle.
A solution without hardcoding the 9:
select min_a - 1 + level
from ( select min(a) min_a
, max(a) max_a
from test1
)
connect by level <= max_a - min_a + 1
minus
select a
from test1
Results:
MIN_A-1+LEVEL
-------------
7003
7007
7008
7009
4 rows selected.
Try this:
SELECT t1.SequenceNumber + 1 AS "From",
MIN(t2.SequenceNumber) - 1 AS "To"
FROM MyTable t1
JOIN MyTable t2 ON t1.SequenceNumber < t2.SequenceNumber
GROUP BY t1.SequenceNumber
HAVING t1.SequenceNumber + 1 < MIN(t2.SequenceNumber)
Here is the result for the sequence 7001, 7002, 7004, 7005, 7006, 7010:
From To
7003 7003
7007 7009
This worked but selects the first sequence (start value) since it doesn't have predecessor. Tested in SQL Server but should work in Oracle
SELECT
s.sequence FROM seqs s
WHERE
s.sequence - (SELECT sequence FROM seqs WHERE sequence = s.sequence-1) IS NULL
Here is a test result
Table
-------------
7000
7001
7004
7005
7007
7008
Result
----------
7000
7004
7007
To get unassigned sequence, just do value[i] - 1 where i is greater first row e.g. (7004 - 1 = 7003 and 7007 - 1 = 7006) which are available sequences
I think you can improve on this simple query
This works on postgres >= 8.4. With some slight modifications to the CTE-syntax it could be made to work for oracle and microsoft, too.
-- EXPLAIN ANALYZE
WITH missing AS (
WITH RECURSIVE fullhouse AS (
SELECT MIN(num)+1 as num
FROM numbers n0
UNION ALL SELECT 1+ fh0.num AS num
FROM fullhouse fh0
WHERE EXISTS (
SELECT * FROM numbers ex
WHERE ex.num > fh0.num
)
)
SELECT * FROM fullhouse fh1
EXCEPT ( SELECT num FROM numbers nx)
)
SELECT * FROM missing;
Here's a solution that:
Relies on Oracle's LAG function
Does not require knowledge of the complete sequence (but thus doesn't detect if very first or last numbers in sequence were missed)
Lists the values surrounding the missing lists of numbers
Lists the missing lists of numbers as contiguous groups (perhaps convenient for reporting)
Tragically fails for very large lists of missing numbers, due to listagg limitations
SQL:
WITH MentionedValues /*this would just be your actual table, only defined here to provide data for this example */
AS (SELECT *
FROM ( SELECT LEVEL + 7000 seqnum
FROM DUAL
CONNECT BY LEVEL <= 10000)
WHERE seqnum NOT IN (7003,7007,7008,7009)--omit those four per example
),
Ranges /*identifies all ranges between adjacent rows*/
AS (SELECT seqnum AS seqnum_curr,
LAG (seqnum, 1) OVER (ORDER BY seqnum) AS seqnum_prev,
seqnum - (LAG (seqnum, 1) OVER (ORDER BY seqnum)) AS diff
FROM MentionedValues)
SELECT Ranges.*,
( SELECT LISTAGG (Ranges.seqnum_prev + LEVEL, ',') WITHIN GROUP (ORDER BY 1)
FROM DUAL
CONNECT BY LEVEL < Ranges.diff) "MissingValues" /*count from lower seqnum+1 up to lower_seqnum+(diff-1)*/
FROM Ranges
WHERE diff != 1 /*ignore when diff=1 because that means the numers are sequential without skipping any*/
;
Output:
SEQNUM_CURR SEQNUM_PREV DIFF MissingValues
7004 7002 2 "7003"
7010 7006 4 "7007,7008,7009"
One simple way to get your answer for your scenario is this:
create table test1 ( a number(9,0));
insert into test1 values (7001);
insert into test1 values (7002);
insert into test1 values (7004);
insert into test1 values (7005);
insert into test1 values (7006);
insert into test1 values (7010);
commit;
select n.n from (select ROWNUM + 7001 as n from dual connect by level <= 9) n
left join test1 t on n.n = t.a where t.a is null;
The select will give you the answer from your example. This only makes sense, if you know in advance in which range your numbers are and the range should not too big. The first number must be the offset in the ROWNUM part and the length of the sequence is the limit to the level in the connect by part.
I would have suggested connect by level as Stefan has done, however, you can't use a sub-query in this statement, which means that it isn't really suitable for you as you need to know what the maximum and minimum values of your sequence are.
I would suggest a pipe-lined table function might be the best way to generate the numbers you need to do the join. In order for this to work you'd need an object in your database to return the values to:
create or replace type t_num_array as table of number;
Then the function:
create or replace function generate_serial_nos return t_num_array pipelined is
l_first number;
l_last number;
begin
select min(serial_no), max_serial_no)
into l_first, l_last
from my_table
;
for i in l_first .. l_last loop
pipe row(i);
end loop;
return;
end generate_serial_nos;
/
Using this function the following would return a list of serial numbers, between the minimum and maximum.
select * from table(generate_serial_nos);
Which means that your query to find out which serial numbers are missing becomes:
select serial_no
from ( select *
from table(generate_serial_nos)
) generator
left outer join my_table actual
on generator.column_value = actual.serial_no
where actual.serial_no is null
SELECT ROWNUM "Missing_Numbers" FROM dual CONNECT BY LEVEL <= (SELECT MAX(a) FROM test1)
MINUS
SELECT a FROM test1 ;
Improved query is:
SELECT ROWNUM "Missing_Numbers" FROM dual CONNECT BY LEVEL <= (SELECT MAX(a) FROM test1)
MINUS
SELECT ROWNUM "Missing_Numbers" FROM dual CONNECT BY LEVEL < (SELECT Min(a) FROM test1)
MINUS
SELECT a FROM test1;
Note: a is column in which we find missing value.
Try with a subquery:
SELECT A.EMPNO + 1 AS MissingEmpNo
FROM tblEmpMaster AS A
WHERE A.EMPNO + 1 NOT IN (SELECT EMPNO FROM tblEmpMaster)
select A.ID + 1 As ID
From [Missing] As A
Where A.ID + 1 Not IN (Select ID from [Missing])
And A.ID < n
Data: ID
1
2
5
7
Result: ID
3
4
6