2009-03-23 10 views
8

Hier ist ein kleines Experiment, das ich in einer Oracle-Datenbank (10g) ausgeführt habe. Abgesehen von (Oracles) Implementierungskomfort kann ich nicht herausfinden, warum einige Einfügungen akzeptiert und andere zurückgewiesen werden.Wie kann ich mehrere Spalten einschränken, um Duplikate zu verhindern, aber Nullwerte ignorieren?

create table sandbox(a number(10,0), b number(10,0)); 
create unique index sandbox_idx on sandbox(a,b); 

insert into sandbox values (1,1); -- accepted 
insert into sandbox values (1,2); -- accepted 
insert into sandbox values (1,1); -- rejected 

insert into sandbox values (1,null); -- accepted 
insert into sandbox values (2,null); -- accepted 
insert into sandbox values (1,null); -- rejected 

insert into sandbox values (null,1); -- accepted 
insert into sandbox values (null,2); -- accepted 
insert into sandbox values (null,1); -- rejected 

insert into sandbox values (null,null); -- accepted 
insert into sandbox values (null,null); -- accepted 

Unter der Annahme, dass es Sinn macht, einige Zeilen gelegentlich zu haben, mit einigen Spalte unbekannte Werte, kann ich mir vorstellen zwei mögliche Anwendungsfälle beteiligt zu verhindern Duplikate:
1. Ich Duplikate verwerfen wollen, aber akzeptieren, wenn eine eingeschränkte Der Wert der Spalte ist unbekannt.
2. Ich möchte Duplikate ablehnen, auch wenn der Wert einer eingeschränkten Spalte unbekannt ist.

Offenbar implementiert Oracle etwas anderes aber:
3. Duplikate ablehnen, aber akzeptieren (nur), wenn alle eingeschränkten Spaltenwerte unbekannt sind.

Ich kann mir überlegen, wie man die Implementierung von Oracle nutzen kann, um case (2) zu verwenden - zum Beispiel einen speziellen Wert für "unknown" und die Spalten nicht nullbar zu machen. Aber ich kann nicht herausfinden, wie man den Fall (1) benutzt.

Mit anderen Worten, wie kann ich Oracle dazu bringen, so zu handeln?

create table sandbox(a number(10,0), b number(10,0)); 
create unique index sandbox_idx on sandbox(a,b); 

insert into sandbox values (1,1); -- accepted 
insert into sandbox values (1,2); -- accepted 
insert into sandbox values (1,1); -- rejected 

insert into sandbox values (1,null); -- accepted 
insert into sandbox values (2,null); -- accepted 
insert into sandbox values (1,null); -- accepted 

insert into sandbox values (null,1); -- accepted 
insert into sandbox values (null,2); -- accepted 
insert into sandbox values (null,1); -- accepted 

insert into sandbox values (null,null); -- accepted 
insert into sandbox values (null,null); -- accepted 
+0

perfektes Beispiel für eine gute Frage (plus es ist ein antwortete ich gebraucht!) – orbfish

Antwort

7
create unique index sandbox_idx on sandbox 
(case when a is null or b is null then null else a end, 
    case when a is null or b is null then null else b end); 

Ein Funktionsindex! Im Grunde musste ich nur sicherstellen, dass alle Tupel, die ich ignorieren möchte (dh akzeptieren), in alle Nullen übersetzt werden. Hässlich, aber nicht hässlich. Funktioniert wie gewünscht.

es Figured mit Hilfe einer Lösung auf eine andere Frage aus: How to constrain a database table so only one row can have a particular value in a column?

Also los und Tony Andrews Punkte geben.:)

+0

Ich finde das gar nicht hässlich. Viel sauberer IMHO als die angenommene Antwort, die 2 Spalten zusammen, möglicherweise nicht sogar den gleichen Datentyp zusammenbacken kann, um irgendeinen eindeutigen Schlüssel des Frankenstein zu schaffen (nicht dass ich es nicht benutzt hätte, wenn Sie mir die korrekte Syntax für multicolumn nicht gezeigt hatten)). – orbfish

1

Ich denke, Sie können dann.

nur für das Protokoll allerdings lasse ich meinen Absatz zu erklären, warum Oracle so verhält, wenn Sie einen einfachen eindeutigen Index für zwei Spalten:

Oracle wird nie akzeptieren zwei (1, null) Paare, wenn die Spalten sind eindeutig indexiert.

Ein Paar von 1 und Null, wird als "indexable" Paar betrachtet. Ein Paar von zwei Nullen kann nicht indexiert werden. Deshalb können Sie so viele Null-Null-Paare einfügen, wie Sie möchten.

(1, null) wird indiziert, weil 1 indiziert werden kann. Wenn Sie das nächste Mal versuchen, (1, null) einzufügen, wird 1 vom Index übernommen und die eindeutige Einschränkung wird verletzt.

(null, null) wird nicht indiziert, da kein Wert indiziert werden muss. Deshalb verletzt es nicht die einzigartige Einschränkung.

+0

Dies ist nur ein Grund für die Durchführung des funktionsbasierten Indizes in Oracle. Es ermöglicht dem Unternehmen, den Index an seine eigenen Geschäftsregeln anzupassen. – DCookie

+0

Ja, ich stehe korrigiert :-) – Petros

7

Versuchen Sie, eine funktionsbasierte Index:

erstellen eindeutigen Index sandbox_idx auf Sandbox (Fall, wenn ein NULL ist, werden NULL, wenn b NULL THEN NULL ELSE ein || IS '' || b END);

Es gibt andere Möglichkeiten, diese Katze zu häuten, aber das ist einer von ihnen.

2

Ich bin kein Oracle-Typ, aber hier ist eine Idee, die funktionieren sollte, wenn Sie eine berechnete Spalte in einen Index in Oracle aufnehmen können.

Fügen Sie Ihrer Tabelle (und Ihrem UNIQUE-Index) eine zusätzliche Spalte hinzu, die wie folgt berechnet wird: Sie ist NULL, wenn a und b nicht NULL sind und andernfalls der Primärschlüssel der Tabelle. Ich nenne diese zusätzliche Spalte "Nullbuster" aus offensichtlichen Gründen.

alter table sandbox add nullbuster as 
    case when a is null or b is null then pk else null end; 
create unique index sandbox_idx on sandbox(a,b,pk); 

Ich habe dieses Beispiel einige Male um 2002 oder so in der Usenet-Gruppe microsoft.public.de.sqlserver. Sie finden die Diskussionen, wenn Sie groups.google.com nach dem Wort "nullbuster" durchsuchen. Die Tatsache, dass Sie Oracle verwenden, sollte nicht viel ausmachen.

P.S.

create unique index sandbox_idx on sandbox(a,b) 
(where a is not null and b is not null); 

Der Thread Sie schlägt vor, verwiesen, dass Oracle Sie diese Option nicht geben: In SQL Server wird diese Lösung ziemlich durch gefilterte Indizes ersetzt. Hat es auch nicht die Möglichkeit einer indizierten Sicht, was eine andere Alternative ist?

create view sandbox_for_unique as 
select a, b from sandbox 
where a is not null and b is not null; 

create index sandbox_for_unique_idx on sandbox_for_unique(a,b); 
+0

Gute Antwort obwohl ein bisschen zu barock für meine Anwendung. Um Ihre Fragen zu beantworten, hat Oracle keine gefilterten Indizes, aber Ihre "indizierte Sicht" scheint durch materialisierte Ansichten in Oracle abgedeckt zu sein, die indiziert werden können und häufig für referenzielle Integrität dienen. – orbfish

Verwandte Themen