2012-06-10 6 views
13

Robię projekt tworzenia systemu przyjęć na studia; technologie to Java i Oracle.Jak sprawdzić brakujący numer z serii liczb?

W jednej z tabel przechowywane są wstępnie wygenerowane numery seryjne. Później, w odniesieniu do tych numerów seryjnych, dane formularzy wnioskodawcy zostaną wprowadzone. Moim wymaganiem jest to, że kiedy proces wpisywania zostanie zakończony, będę musiał wygenerować Mądry raport. Jeżeli podczas podawania wstępnie wygenerowanych numerów seryjnych zniknęły numery sekwencji.

Na przykład, powiedzmy w tabeli, numery porządkowe to 7001, 7002, 7004, 7005, 7006, 7010. Z powyższej serii jest jasne, że od 7001 do 7010 brakujące numery to 7003, 7007, 7008 i 7009

Czy w bazie danych Oracle dostępna jest funkcja DBMS, aby znaleźć te numery lub jeśli procedura przechowywana może spełnić mój cel, proszę zasugerować algorytm.

Mogę znaleźć kilka technik w Javie, ale dla prędkości chcę znaleźć rozwiązanie w Oracle.

+0

Dodałem tag luki i-wysp. wyszukanie tego prawdopodobnie przyniesie wystarczającą ilość stanu techniki, w tym kwerendy rekurencyjne. – wildplasser

+0

Zobacz [Znajdź zakres brakujących wartości w sekwencji liczb lub dat] (http://lalitkumarb.wordpress.com/2015/07/22/find-range-of-missing-values-in-a-sequency-of -numbers-or-dates /) –

Odpowiedz

33

Rozwiązanie bez hardcoding na 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 

Wyniki:

MIN_A-1+LEVEL 
------------- 
     7003 
     7007 
     7008 
     7009 

4 rows selected. 
+5

To sprawia, że ​​moja odpowiedź wygląda absurdalnie zbyt skomplikowana! +1 – Ben

+0

Sam badałem logikę i zdecydowałem, że niepotrzebnie tracić czas w ten sposób. Sądzę, że powinienem sprawić, że Google będzie dobrą praktyką. Dlatego daj +1 tej odpowiedzi. –

0

Jeden prosty sposób, aby uzyskać odpowiedź na swoim scenariuszu jest to:

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; 

select daje odpowiedź od Twojego przykładu. Ma to sens tylko wtedy, gdy wiesz z góry, w jakim zakresie są twoje liczby, a zakres nie powinien być zbyt duży. Pierwsza liczba musi być przesunięciem w części ROWNUM, a długość sekwencji jest ograniczeniem do poziomu w części connect by.

+0

Musisz wiedzieć, że wartość wynosi 9. Skąd to wiesz? – Ben

+0

Oto co napisałem: Musisz znać zakres swojej sekwencji. Jeśli dobrze rozumiem zadanie, jest to prawdopodobnie znane. A może źle cię zrozumiałem? – Stefan

1

Zasugerowałbym connect by level jako Stefan has done, jednak nie można użyć zapytania podrzędnego w tym zestawieniu, co oznacza, że ​​nie jest odpowiedni dla ciebie, ponieważ musisz wiedzieć, jakie maksymalne i minimalne wartości twoja sekwencja jest.

Proponuję, aby pipe-lined table function był najlepszym sposobem na wygenerowanie liczb potrzebnych do połączenia. Aby to zadziałało należałoby obiektu w bazie danych, aby powrócić do wartości:

create or replace type t_num_array as table of number; 

Następnie funkcja:

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; 
/

Za pomocą tej funkcji następujące zwróci listę seryjny liczby między minimum i maksimum.

select * from table(generate_serial_nos); 

Co oznacza, że ​​zapytanie, aby dowiedzieć się jakie numery seryjne brakuje staje:

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 
0

To zadziałało, ale wybiera pierwszą sekwencję (wartość początkowa), ponieważ nie ma poprzednika.Testowany w SQL Server, ale powinno działać w Oracle

SELECT 
    s.sequence FROM seqs s 
WHERE 
    s.sequence - (SELECT sequence FROM seqs WHERE sequence = s.sequence-1) IS NULL 

Oto wynik testu

Table 
    ------------- 
    7000 
    7001 
    7004 
    7005 
    7007 
    7008 

    Result 
    ---------- 
    7000 
    7004 
    7007 

Aby uzyskać obsadzony sekwencję, zrób value[i] - 1 gdzie jest większa pierwszym rzędzie na przykład (7004 - 1 = 7003 and 7007 - 1 = 7006) które są dostępne sekwencje

myślę, że można poprawić na tej prostej kwerendy

+0

To wstępnie zakłada tabelę zawierającą wszystkie zapisane numery sekwencji. Nie ma takiej potrzeby w Oracle. – Ben

+0

czy 'connect by' Oracle będzie działać lepiej niż to? – codingbiz

+0

Odpowiedź z najwyższymi głosami tutaj używa 2 funkcji agregujących - a co z wydajnością? – codingbiz

-1

To działa na PostgreSQL> = 8,4. Z niewielkimi modyfikacjami składni CTE można je również zastosować do oracle i microsoft.

-- 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; 
+0

Do niższego poziomu: proszę wyjaśnij. To pytanie jest oznaczone jako "sql", które jest (powinno być) standardowym sql. CTE są częścią tego. – wildplasser

+2

Nie ja, który głosowałem, ale żeby być uczciwym wobec nich, jest również oznaczony Oracle i ta składnia jest niepoprawna. – Ben

+0

Cóż, powiedziano mi, że CTE jest zaimplementowany w wyroczni, patrz: http: //stackoverflow.com/questions/6064970/oracle-cte-merge. Oczywiście konstrukcja typu connect-by/prior istnieje od kilku lat, ale składnia CTE jest przynajmniej częścią standardu i zawsze istnieje powód do pewnej różnorodności, nawet jeśli jest to standard. Jak już powiedziałem w mojej odpowiedzi: niewielkie różnice w składni (takie jak pominięcie słowa kluczowego RECURSIVE) mogą istnieć. I na koniec: przynajmniej zapytanie działa dla mnie (również z kilkoma zmianami dla innych). Pojawiły się tutaj inne odpowiedzi, które * nie działają *. – wildplasser

11

Spróbuj tego:

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) 

Oto wynik dla sekwencji 7001, 7002, 7004, 7005, 7006, 7010:

From To 
7003 7003 
7007 7009 
-2
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 
+0

Brakujące numery sekwencji. –

+0

Edytuj swój wpis, aby podać kontekst swojej odpowiedzi. Tylko kody odpowiedzi są tylko częściowo pomocne: http://stackoverflow.com/help/how-to-answer –

0

Oto rozwiązanie, które :

  • Opiera się na funkcji LGD Oracle
  • Nie wymaga znajomości pełnej sekwencji (ale w ten sposób nie wykrywa, czy bardzo pierwszej lub ostatniej cyfry w kolejności były nieodebranych)
  • Listy wartości otaczającego brakujące wykazy numerów
  • Listy brakujące wykazy numerów jak sąsiednich grup (być wygodne dla raportowania)
  • Tragicznie nie powiedzie się z bardzo dużych list brakujących numerów, z powodu ograniczeń listagg

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*/ 
; 

wyjściowa:

SEQNUM_CURR SEQNUM_PREV DIFF MissingValues 
7004  7002  2 "7003" 
7010  7006  4 "7007,7008,7009"     
Powiązane problemy