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

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

Related

Oracle SQL - SUM aggregate not working in LEVEL

I have this query where it has an output when using count but no output when using sum
select to_char(counter)
from (select level counter from dual connect by level <= 1000000)
where counter =
(select sum(a.amount) from table a)
I'm wondering it's because the query only supports outputs of whole numbers? I am expecting to have outputs with decimals.
I am using this as a table value set in Oracle HCM, if anyone's familiar with that. It's why I don't have the aggregate in the SELECT statement as it doesn't support it.
Are you sure your subquery is returning rows? Because SUM of an empty set is null not zero, eg
SQL> select to_char(counter)
2 from ( select level counter from dual connect by level <= 1000000 )
3 where counter =
4 (select sum(deptno) from scott.emp);
TO_CHAR(COUNTER)
----------------------------------------
310
SQL> select to_char(counter)
2 from ( select level counter from dual connect by level <= 1000000 )
3 where counter =
4 (select sum(deptno) from scott.emp where 1=0);
no rows selected
Your query says: Create all integers from 1 to 1,000,000 and of these show me the one that matches exactly the total amount of the table. So if that total is 123.45, you will get no row. If that amount is -123, you will get no row. If that amount is 1,000,001 you will get no row.
If you simply want the total, but you can have aggregations only in subqueries for some weird reason, just do
select * from (select sum(amount) as total from table) t;

Split column into multiple rows in ORACLE based on static length of substring

I have seen multiple topics here for "Split column into multiple rows" but they all are based on some delimiter.
I want to split the column based on length in oracle.
Suppose i have a table
codes | product
--------------------------+--------
C111C222C333C444C555..... | A
codes are type VARCHAR2(800) and product is VARCHAR2(1).
Here in codes field we have many codes (maximum 200) which belongs to product A. and length of each code is 4 ( so C111, C222, C333 are different codes)
I want output of my select query like this-
code | product
---------------+-------
C111 | A
C222 | A
C333 | A
C444 | A
C555 | A
...
and so on.
please help me with this. Thanks in advance.
Here's yet another variation using regexp_substr() along with CONNECT BY to "loop" through the string by 4 character substrings:
SQL> with tbl(codes, product) as (
select 'C111C222C333C444C555', 'A' from dual union all
select 'D111D222D333', 'B' from dual
)
select regexp_substr(codes, '(.{4})', 1, level, null, 1) code, product
from tbl
connect by level <= (length(codes)/4)
and prior codes = codes
and prior sys_guid() is not null;
CODE P
-------------------- -
C111 A
C222 A
C333 A
C444 A
C555 A
D111 B
D222 B
D333 B
8 rows selected.
SQL>
Here is how I would do it. Let me know if you need more input / better explanations:
select substr(tt.codes,(((t.l-1)*4)+1),4) code,tt.product from tst_tab tt
join (select level l from dual connect by level <= (select max(length(codes)/4) from tst_tab)) t
on t.l <= length(tt.codes)/4
order by tt.product,t.l;
Some explanantions:
-- this part gives the numbers from 1 ... maximum number of codes in codes column
select level l from dual connect by level <= (select max(length(codes)/4) from tst_tab);
-- here is the query without the code extraction, it is just the numbers 1... numbers of codes for the product
select t.l,tt.product from tst_tab tt
join (select level l from dual connect by level <= (select max(length(codes)/4) from tst_tab)) t
on t.l <= length(tt.codes)/4
order by tt.product,t.l;
-- and then the substr just extracts the right code:
substr(tt.codes,(((t.l-1)*4)+1),4)
Set up of my test data:
create table tst_tab (codes VARCHAR2(800),product VARCHAR2(1));
insert into tst_tab values ('C111C222C333C444C555','A');
insert into tst_tab values ('C111C222C333C444C555D666','B');
insert into tst_tab values ('C111','C');
commit;
One option might be this:
SQL> with test (codes, product) as
2 (select 'C111C222C333C444C555', 'A' from dual union all
3 select 'D555D666D777', 'B' from dual
4 )
5 select substr(codes, 4 * (column_value - 1) + 1, 4) code, product
6 from test,
7 table(cast(multiset(select level from dual
8 connect by level <= length(codes) / 4
9 ) as sys.odcinumberlist))
10 order by 1;
CODE P
---- -
C111 A
C222 A
C333 A
C444 A
C555 A
D555 B
D666 B
D777 B
8 rows selected.
SQL>
Yet another a little bit different option of using recursive SQL to do this.
(To make it more concise I didn't add an example of test data. It could be taken from #Littlefoot or #Peter answers)
select code, product
from (
select distinct
substr(codes, (level - 1) * 4 + 1, 4) as code,
level as l,
product
from YourTable
connect by substr(codes, (level - 1) * 4 + 1, 4) is not null
)
order by product, l
P.S. #Thorsten Kettner made a fair point about considering to restructure your tables. That would be the right thing to do for sake of easier maintenance of your database in future

Repeat each value n times as rows in SQL

I have been trying to achieve this in SQL (Oracle 11g) for a while but could not find a proper way to do it.
My table names has the following rows:
NAME REPEAT
---- ------
KAUSHIK 2
KARTHIK 3
NIDHI 1
ASHWINI 5
JAGADEESH 6
What I need is an output like this:
NAME
----
KAUSHIK -- 2 rows
KAUSHIK
KARTHIK
KARTHIK -- 3 rows
KARTHIK
NIDHI -- 1 row
ASHWINI
ASHWINI -- 5 rows
...
and so on.
One of the queries which I have tried so far, which of course is not working. I tried to use unpivot as well but could not seem to find a proper way to accomplish this.
SELECT m.name
FROM names m
INNER JOIN
( SELECT name, repeat FROM names
) n
ON m.name = n.name
CONNECT BY LEVEL <= n.repeat;
Try this:
select * from names
cross join (select rownum n from dual
connect by level <= (select max(repeat) from names))
where n <= repeat
order by name
You may use some temp table containing list of numbers 1 to N, where N is the highest number in your table names. Let call it num(o int) Then the query will be
SELECT *
FROM names, num
WHERE num.o <= names.repeat
If we presume that your all_objects system object has more objects than the max repeat...
SELECT n.name
FROM names n
LEFT JOIN (Select rownum from all_objects) z
on z.rowNum < n.repeat
I just found an alternative with connect by and correlated sub-query.
select name
from names o
connect by level <= ( select repeat from names
i where i.name = o.name )
AND
prior name = name and
prior sys_guid() is not null
order by name;

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;

SQL: create sequential list of numbers from various starting points

I'm stuck on this SQL problem.
I have a column that is a list of starting points (prevdoc), and anther column that lists how many sequential numbers I need after the starting point (exdiff).
For example, here are the first several rows:
prevdoc | exdiff
----------------
1 | 3
21 | 2
126 | 2
So I need an output to look something like:
2
3
4
22
23
127
128
I'm lost as to where even to start. Can anyone advise me on the SQL code for this solution?
Thanks!
;with a as
(
select prevdoc + 1 col, exdiff
from <table> where exdiff > 0
union all
select col + 1, exdiff - 1
from a
where exdiff > 1
)
select col
If your exdiff is going to be a small number, you can make up a virtual table of numbers using SELECT..UNION ALL as shown here and join to it:
select prevdoc+number
from doc
join (select 1 number union all
select 2 union all
select 3 union all
select 4 union all
select 5) x on x.number <= doc.exdiff
order by 1;
I have provided for 5 but you can expand as required. You haven't specified your DBMS, but in each one there will be a source of sequential numbers, for example in SQL Server, you could use:
select prevdoc+number
from doc
join master..spt_values v on
v.number <= doc.exdiff and
v.number >= 1 and
v.type = 'p'
order by 1;
The master..spt_values table contains numbers between 0-2047 (when filtered by type='p').
If the numbers are not too large, then you can use the following trick in most databases:
select t.exdiff + seqnum
from t join
(select row_number() over (order by column_name) as seqnum
from INFORMATION_SCHEMA.columns
) nums
on t.exdiff <= seqnum
The use of INFORMATION_SCHEMA columns in the subquery is arbitrary. The only purpose is to generate a sequence of numbers at least as long as the maximum exdiff number.
This approach will work in any database that supports the ranking functions. Most databases have a database-specific way of generating a sequence (such as recursie CTEs in SQL Server and CONNECT BY in Oracle).