Als PDF Downloaden!

Werbung
Tipps & Tricks: September 2011
Bereich:
SQL
Erstellung:
09/2011 EF
Versionsinfo:
10.2, 11.1, 11.2
Letzte Überarbeitung:
09/2011 EF
Löschen von doppelten Datensätzen
Zum Löschen von doppelten Datensätzen gibt es eine ganze Reihe von Ansätzen. Die Suche nach der
Schlagwort-Kombination duplicate delete und Oracle ergibt bei Google ca. 451.000 Treffer. Viele der Methoden
funktionieren hervorragend bei den 10 bis 30 Beispieldatensätzen, die meist auch keine NULL-Werte enthalten.
Wenn die Tabellen aber größer werden, stellt sich die Frage nach der performantesten Methode.
Dieser Monatstip stellt die gängigsten Methoden am Beispiel einer manipulierten emp-Tabelle vor und vergleicht
danach deren Performance anhand einer Tabelle mit 2 Mio. Datensätzen.
Vorbereitung der emp-Tabelle:
Ein Datensatz wird verdoppelt, ein anderer verdreifacht, nur die Primärschlüssel bleiben unangetastet.
UPDATE emp SET (ename, job, mgr, hiredate, sal, comm, deptno)
= (SELECT ename, job, mgr, hiredate, sal, comm, deptno
FROM emp WHERE empno = 7369)
WHERE empno = 7566;
UPDATE emp SET (ename, job, mgr, hiredate, sal, comm, deptno)
= (SELECT ename, job, mgr, hiredate, sal, comm, deptno
FROM emp WHERE empno = 7499)
WHERE empno in (7839, 7902);
COMMIT;
Wie findet man die Duplikate
Auch hier gibt es mehrere Ansätze, der gebräuchlichste ist sicher:
SELECT spaltenliste, COUNT(*)
FROM tabelle
GROUP BY spaltenliste
HAVING COUNT(*) > 1;
Mit Spaltenliste ist hier die Kombination der Spalten gemeint, die keine Duplikate aufweisen soll.
SELECT ename, job, mgr, hiredate, sal, comm, deptno, COUNT(*)
FROM emp
GROUP BY ename, job, mgr, hiredate, sal, comm, deptno
HAVING COUNT(*) > 1;
ENAME JOB
MGR HIREDATE
SAL COMM DEPTNO COUNT(*)
------- ---------- ----- -------- ------ ----- ------ ---------ALLEN SALESMAN
7698 20.02.81 1600 300
30
3
SMITH CLERK
7902 17.12.80
800
20
2
Muniqsoft GmbH
Schulungszentrum, Grünwalder Weg 13a, 82008 Unterhaching, Tel. 089 / 679090-40
IT-Consulting & Support, Witneystraße 1, 82008 Unterhaching, Tel. 089 / 6228 6789-0
Seite 1 von 7
Dieser alternative Select mit einer korrelierten Unterabfrage liefert die kompletten Datensätze der Duplikate,
sofern man NULL-Spalten mit NVL entschärft (sonst wird hier z.B. das Duplikat von Smith nicht angezeigt)
SELECT * FROM emp e1
WHERE EXISTS (SELECT 1 FROM emp e2
WHERE e2.ename = e1.ename
AND e1.job = e2.job
AND e1.mgr = e1.mgr
AND e1.hiredate = e2.hiredate
AND e1.sal = e2.sal
AND NVL(e1.comm,0) = NVL(e2.comm,0)
AND e1.deptno = e1.deptno
AND e2.rowid < e1.rowid);
EMPNO ENAME JOB
MGR HIREDATE
SAL COMM DEPTNO
----- ------- ---------- ----- -------- ------ ----- -----7566 SMITH CLERK
7902 17.12.80
800
20
7839 ALLEN SALESMAN
7698 20.02.81 1600 300
30
7902 ALLEN SALESMAN
7698 20.02.81 1600 300
30
Eine dritte, eher exotische Methode besteht darin, einen Unique-Constraint auf die Spaltenkombination zu setzen,
die eindeutig sein soll und die Fehler (die Duplikate) in die von Oracle über das Skript utlexcpt.sql zur Verfügung
gestellte Exceptions-Tabelle zu schreiben.
@ ?\rdbms\admin\utlexcpt
ALTER TABLE emp ADD CONSTRAINT emp_uq
UNIQUE (ename, job, mgr, hiredate, sal, comm, deptno)
EXCEPTIONS INTO EXCEPTIONS;
SELECT e.* FROM exceptions x JOIN emp e ON e.rowid = x.row_id;
=>
EMPNO ENAME JOB
MGR HIREDATE
SAL COMM DEPTNO
----- ------- ---------- ----- -------- ------ ----- -----7369 SMITH CLERK
7902 17.12.80
800
20
7499 ALLEN SALESMAN
7698 20.02.81 1600 300
30
7566 SMITH CLERK
7902 17.12.80
800
20
7839 ALLEN SALESMAN
7698 20.02.81 1600 300
30
7902 ALLEN SALESMAN
7698 20.02.81 1600 300
30
Methode 1: Löschen der Duplikate über eine nicht-korrelierte Unterabfrage
Dies ist sicher der bekannteste Ansatz:
DELETE FROM tabelle
WHERE rowid NOT IN (SELECT MIN(rowid) FROM tabelle
GROUP BY spaltenliste);
In unserem Beispiel also:
DELETE FROM emp
WHERE rowid NOT IN (SELECT MIN(rowid) FROM emp
GROUP BY ename, job, mgr, hiredate, sal, comm, deptno);
3 rows deleted.
Muniqsoft GmbH
Schulungszentrum, Grünwalder Weg 13a, 82008 Unterhaching, Tel. 089 / 679090-40
IT-Consulting & Support, Witneystraße 1, 82008 Unterhaching, Tel. 089 / 6228 6789-0
Seite 2 von 7
ROLLBACK;
Methode 2: Löschen der Duplikate über eine korrelierte Unterabfrage.
Allgemeine Syntax:
DELETE FROM tabelle t1
WHERE rowid < (SELECT MAX(rowid) FROM tabelle t2
WHERE t1.col1 = t2.col1
AND t1.col2 = t2.col2
AND t1.col3 = t2.col3 ....);
Statt rowid < (SELECT MAX(rowid)... kann man natürlich auch rowid > (SELECT MIN(rowid)... oder
rowid < ANY (SELECT rowid... verwenden
Nachteile:
Ohne die Behandlung von NULL-Spalten mit NVL erwischt man nur einen Teil der Datensätze !!!!
Korrelierte Update- und Delete-Statements sind als besonders unperformant berüchtigt
Bei Tabellen mit vielen Spalten wird das Statement sehr lang
Das Statement sieht bei der manipulierten emp-Tabelle dann so aus:
DELETE FROM emp e1
WHERE rowid < (SELECT MAX(rowid) FROM emp e2
WHERE e1.ename
= e2.ename
AND e1.job
= e2.job
AND e1.mgr
= e2.mgr
AND e1.hiredate = e2.hiredate
AND e1.sal
= e2.sal
AND NVL(e1.comm,0) = NVL(e2.comm,0)
AND e1.deptno
= e2.deptno);
3 rows deleted.
ROLLBACK;
Methode 3: Löschen der Duplikate über analytische Funktionen
Von Tom Kyte empfohlen (s. http://www.oracle.com/technetwork/issue-archive/o44asktom-089519.html).
Näheres zu analytischen Funktionen erfahren Sie in unserem SQL-II-Kurs oder ab dem nächsten Frühjahr in
einem eintägigen Spezial-Kurs zu diesem Thema.
Allgemeine Syntax:
DELETE FROM tabelle
WHERE rowid IN
(SELECT rid FROM
(SELECT rowid rid,
ROW_NUMBER() OVER(PARTITION BY spaltenliste ORDER BY rowid) rn
FROM tabelle)
WHERE rn <> 1);
In unserem Beispiel also:
Muniqsoft GmbH
Schulungszentrum, Grünwalder Weg 13a, 82008 Unterhaching, Tel. 089 / 679090-40
IT-Consulting & Support, Witneystraße 1, 82008 Unterhaching, Tel. 089 / 6228 6789-0
Seite 3 von 7
DELETE FROM emp
WHERE rowid IN
(SELECT rid FROM
(SELECT rowid rid,
ROW_NUMBER()
OVER(PARTITION BY ename, job, mgr, hiredate, sal, comm, deptno
ORDER BY rowid) rn
FROM emp)
WHERE rn <> 1);
3 rows deleted.
ROLLBACK;
Methode 4 (außer Konkurrenz): Erstellen einer neuen Tabelle ohne Duplikate
Auch diesen Ansatz findet man in dem oben angeführten Artikel von Tom Kyte. Er ist besonders schnell, aber in
Produktivumgebungen kaum umzusetzen. Man erstellt aus den gewünschten Daten eine neue Tabelle, löscht die
alte, erstellt die Indizes neu und benennt die neue Tabelle um.
CREATE TABLE emp2 AS
SELECT empno, ename, job, mgr, hiredate, sal, comm, deptno
FROM (SELECT b.*,
ROW_NUMBER()
OVER(PARTITION BY ename, job, mgr, hiredate, sal, comm, deptno
ORDER BY rowid) rn
FROM emp b)
WHERE rn = 1;
DROP TABLE emp PURGE;
ALTER TABLE emp2 ADD CONSTRAINT emp_pk PRIMARY KEY(empno);
RENAME emp2 TO emp;
Performance-Tests mit großen Tabellen
Für die Performance-Tests wurde eine Tabelle mit 2 Mio. Datensätzen auf Basis der dba_objects (Tom Kytes
big_tab) im Schema Scott verwendet (create_bigtab_2M.sql). Auch hier sind nur die Primärschlüssel noch
unique.
Damit die Tests auch auf einer 10g-DB laufen, wurden die Spalten edition_name und namespace weggelassen.
Das Skript duplikate_loeschen.sql testet die oben erwähnten Methoden nacheinander durch und schreibt die
Ergebnisse samt Timing und Ausführungsplan in ein Spool-File duplikate_loeschen_test.txt. Zwischen den
einzelnen Durchgängen wird die Tabelle jeweils neu erstellt sowie Buffer cache und shared pool geflusht.
Lassen Sie das Skript auf keinen Fall in einer Produktivumgebung laufen !!
Hier werden nur die Ausführungszeiten wiedergegeben. Die kompletten Ergebnisse für die Tests auf 3 Servern,
jeweils mit einer 10g und einer 11g-DB finden Sie hier.
Duplikate ermitteln:
conn sys/sys as sysdba
set timing on
SELECT COUNT(*), zahl
FROM (SELECT owner, object_name, subobject_name, object_id, data_object_id,
object_type, created, last_ddl_time, timestamp, status,
temporary, generated, secondary, COUNT(*) zahl
FROM scott.big_tab
GROUP BY owner, object_name, subobject_name, object_id, data_object_id,
Muniqsoft GmbH
Schulungszentrum, Grünwalder Weg 13a, 82008 Unterhaching, Tel. 089 / 679090-40
IT-Consulting & Support, Witneystraße 1, 82008 Unterhaching, Tel. 089 / 6228 6789-0
Seite 4 von 7
object_type, created, last_ddl_time, timestamp, status,
temporary, generated, secondary
HAVING COUNT(*) > 1)
GROUP BY zahl;
=>
-- für 10g
COUNT(*)
ZAHL
---------- ---------48518
40
1520
39
-- für 11g
COUNT(*)
ZAHL
---------- ---------36128
28
36608
27
Löschen der Duplikate über eine nicht-korrelierte Unterabfrage
DELETE FROM scott.big_tab
WHERE rowid NOT IN (SELECT MIN(rowid) FROM scott.big_tab
GROUP BY owner, object_name, subobject_name, object_id,
data_object_id, object_type, created, last_ddl_time,
timestamp, status, temporary, generated, secondary);
-- Laufzeiten für 10g: zwischen 1:29,76 und 1:35.00 Minuten
-- Laufzeiten für 11g: zwischen 1:30.18 und 2:00.85 Minuten
Löschen der Duplikate über eine korrelierte Unterabfrage.
Trotz der prinzipiell gleichen Hard- und Software-Ausstattung waren die Laufzeiten hier sehr unterschiedlich. 2
Server lagen reproduzierbar zwischen 16 und 18 Minuten, einer brachte es auf 5-6 Minuten.
DELETE FROM scott.big_tab b1
WHERE rowid <
(SELECT MAX(rowid) FROM scott.big_tab b2
WHERE b1.owner
= b2.owner
AND b1.object_name
= b2.object_name
AND NVL(b1.subobject_name, 'nn')
= NVL(b2.subobject_name, 'nn')
AND b1.object_id
= b2.object_id
AND NVL(b1.data_object_id,0)
= NVL(b2.data_object_id,0)
AND b1.object_type
= b2.object_type
AND b1.created
= b2.created
AND NVL(b1.last_ddl_time,sysdate) = NVL(b2.last_ddl_time,sysdate)
AND NVL(b1.timestamp, systimestamp) = NVL(b2.timestamp, systimestamp)
AND b1.status
= b2.status
AND b1.temporary
= b2.temporary
AND b1.generated
= b2.generated
AND b1.secondary
= b2.secondary);
-- Laufzeiten für 10g: zwischen 5:44,96 und 16:39,74 Minuten
-- Laufzeiten für 11g: zwischen 6:31,56 und 18:22.90 Minuten
Muniqsoft GmbH
Schulungszentrum, Grünwalder Weg 13a, 82008 Unterhaching, Tel. 089 / 679090-40
IT-Consulting & Support, Witneystraße 1, 82008 Unterhaching, Tel. 089 / 6228 6789-0
Seite 5 von 7
Löschen der Duplikate über analytische Funktionen
DELETE FROM scott.big_tab
WHERE rowid IN
(SELECT rid FROM
(SELECT rowid rid,
ROW_NUMBER()
OVER(PARTITION BY owner, object_name, subobject_name, object_id,
data_object_id, object_type, created,
last_ddl_time, timestamp,
status, temporary, generated, secondary
ORDER BY rowid) rn
FROM scott.big_tab)
WHERE rn <> 1);
-- Laufzeiten für 10g: zwischen 3:11,59 und 3:41,30 Minuten
-- Laufzeiten für 11g: zwischen 4:40,64 und 5:06.32 Minuten
Duplikate eliminieren über eine neue Tabelle
--Tabelle ohne Duplikate erstellen
CREATE TABLE big_tab2 AS
SELECT id, owner, object_name, subobject_name, object_id, data_object_id,
object_type, created, last_ddl_time, timestamp, status, temporary, generated,
secondary
FROM (SELECT b.*,
ROW_NUMBER()
OVER(PARTITION BY owner, object_name, subobject_name, object_id,
data_object_id, object_type, created, last_ddl_time,
timestamp, status, temporary, generated, secondary
ORDER BY rowid) rn
FROM big_tab b)
WHERE rn = 1;
--alte Tabelle löschen
DROP TABLE big_tab PURGE;
--Primärschlüssel auf neuer Tabelle erstellen
ALTER TABLE big_tab2 ADD CONSTRAINT big_tab_pk PRIMARY KEY(id);
--neue Tabelle umbenennen
RENAME big_tab2 TO big_tab;
-- Gesamtlaufzeiten für 10g und 11g unter 20 Sekunden!!
Fazit
Der bekannte Ansatz zum Löschen von Duplikaten über eine nicht korrelierte Unterabfrage ist zumindest bei
unseren Tests sehr schnell. Die Verwendung von analytischen Funktionen ist dann vorteilhaft, wenn man
Duplikate nur in einer Spalte entfernen will und nicht über eine Spaltenkombination partionieren muss. Von
Löschvorgängen über korrelierte Unterabfragen sollte man lieber absehen, vor allem, wenn man nicht genau
weiß, in welchen Spalten NULL-Werte zu erwarten sind.
Die Erstellung einer neuen Tabelle ohne Duplikate ist zwar gerade bei großen Tabellen der einfachste und
schnellste Weg, aber in Produktivumgebungen meist nicht einsetzbar.
Muniqsoft GmbH
Schulungszentrum, Grünwalder Weg 13a, 82008 Unterhaching, Tel. 089 / 679090-40
IT-Consulting & Support, Witneystraße 1, 82008 Unterhaching, Tel. 089 / 6228 6789-0
Seite 6 von 7
Muniqsoft GmbH
Schulungszentrum, Grünwalder Weg 13a, 82008 Unterhaching, Tel. 089 / 679090-40
IT-Consulting & Support, Witneystraße 1, 82008 Unterhaching, Tel. 089 / 6228 6789-0
Seite 7 von 7
Herunterladen