Ich mache ein Projekt, das ein Aufnahmesystem für ein College schafft. Die Technologien sind Java und Oracle.
In einer der Tabellen werden vorgenerierte Seriennummern gespeichert. Gegen diese Seriennummern werden später die Formulardaten des Antragstellers eingegeben. Meine Forderung ist, dass nach Abschluss des Einreichungsprozesses ein lotweiser Bericht erstellt werden muss. Wenn während der Zuführung vorgenerierter Seriennummern keine Sequenznummern fehlen.
Zum Beispiel, in einer Tabelle, sind die Folgenummern 7001, 7002, 7004, 7005, 7006, 7010 . Aus der obigen Serie ist klar, dass von 7001 bis 7010 die fehlenden Nummern 7003, 7007, 7008 und 7009 sind
Gibt es in Oracle eine DBMS-Funktion, um diese Zahlen herauszufinden, oder falls eine gespeicherte Prozedur meinen Zweck erfüllt, schlagen Sie bitte einen Algorithmus vor.
Ich kann einige Techniken in Java finden, aber aus Gründen der Schnelligkeit möchte ich die Lösung in Oracle finden.
Eine Lösung ohne Hardcoding der 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
Ergebnisse:
MIN_A-1+LEVEL
-------------
7003
7007
7008
7009
4 rows selected.
Versuche dies:
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)
Hier ist das Ergebnis für die Sequenz 7001, 7002, 7004, 7005, 7006, 7010:
From To
7003 7003
7007 7009
Das funktioniert bei Postgres> = 8.4. Mit einigen geringfügigen Änderungen an der CTE-Syntax könnte es auch für Oracle und Microsoft funktionieren.
-- 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;
Dies hat funktioniert, aber die erste Sequenz (Startwert) wird ausgewählt, da sie keinen Vorgänger hat. In SQL Server getestet, sollte jedoch in Oracle funktionieren
SELECT
s.sequence FROM seqs s
WHERE
s.sequence - (SELECT sequence FROM seqs WHERE sequence = s.sequence-1) IS NULL
Hier ist ein Testergebnis
Table
-------------
7000
7001
7004
7005
7007
7008
Result
----------
7000
7004
7007
Um eine nicht zugewiesene Sequenz zu erhalten, machen Sie einfach value[i] - 1
, wobei i die erste Zeile größer ist, z. (7004 - 1 = 7003 and 7007 - 1 = 7006)
welche Sequenzen verfügbar sind
Ich denke, Sie können diese einfache Abfrage verbessern
Ich hätte connect by level
vorgeschlagen, da Stefan dies getan hat Sie können jedoch keine Unterabfrage in dieser Anweisung verwenden, was bedeutet, dass sie nicht wirklich für Sie geeignet ist, da Sie wissen müssen, was das Maximum und das Minimum ist Werte Ihrer Sequenz sind.
Ich würde vorschlagen, dass eine Pipeline-Tabellenfunktion der beste Weg ist, um die Zahlen zu erzeugen, die Sie für den Join benötigen. Damit dies funktioniert, benötigen Sie ein Objekt in Ihrer Datenbank, in das die Werte zurückgegeben werden:
create or replace type t_num_array as table of number;
Dann die Funktion:
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;
/
Bei Verwendung dieser Funktion würde das Folgende eine Liste von Seriennummern zwischen dem Minimum und dem Maximum zurückgeben.
select * from table(generate_serial_nos);
Was bedeutet, dass Ihre Abfrage, um herauszufinden, welche Seriennummern fehlen, zu werden:
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
Hier ist eine Lösung, die:
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*/
;
Ausgabe:
SEQNUM_CURR SEQNUM_PREV DIFF MissingValues
7004 7002 2 "7003"
7010 7006 4 "7007,7008,7009"
Eine einfache Möglichkeit, um eine Antwort auf Ihr Szenario zu erhalten, ist folgende:
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;
Die Auswahl gibt Ihnen die Antwort aus Ihrem Beispiel. Das macht nur Sinn, wenn Sie vorher wissen, in welchem Bereich Ihre Zahlen liegen und der Bereich nicht zu groß sein sollte. Die erste Zahl muss der Versatz im ROWNUM
-Teil sein und die Länge der Sequenz ist die Begrenzung der Ebene im connect by
-Teil.
SELECT ROWNUM "Missing_Numbers" FROM dual CONNECT BY LEVEL <= (SELECT MAX(a) FROM test1)
MINUS
SELECT a FROM test1 ;