koşullu benzersiz kısıtlama


95

Bir sütun kümesine benzersiz bir kısıtlama uygulamam gereken bir durum var, ancak bir sütunun yalnızca bir değeri için.

Örneğin, Tablo (ID, Name, RecordStatus) gibi bir tablom var.

RecordStatus yalnızca 1 veya 2 değerine sahip olabilir (aktif veya silinmiş) ve yalnızca RecordStatus = 1 olduğunda (ID, RecordStatus) üzerinde benzersiz bir kısıt oluşturmak istiyorum, çünkü aynı olan birden fazla silinmiş kayıt olması umrumda değil. İD.

Tetikleyici yazmak dışında bunu yapabilir miyim?

SQL Server 2005 kullanıyorum.


1
Bu tasarım yaygın bir acıdır. Tasarımı, kavramsal olarak 'silinen' kayıtların tablodan fiziksel olarak silinmesi ve belki de bir 'arşiv' tablosuna taşınması için değiştirmeyi düşündünüz mü?
08

1
... çünkü basit bir anahtarı zorlamak için EŞSİZ bir kısıtlama yazamama "kod kokusu", IMO olarak düşünülmelidir. Diğer birçok tablo bu tabloya başvurduğu için tasarımı (SQL DDL) değiştiremezseniz, sonuç olarak SQL DML'nizin de zarar gördüğüne bahse girerim, yani şunu eklemeyi hatırlamanız gerekir ... AND Table.RecordStatus = 1 ' çoğu arama koşulu ve bu tabloyu içeren birleştirme koşulları ve ara sıra kaçınılmaz olarak ihmal edildiğinde ince hatalar yaşanıyor.
08

Yanıtlar:


38

Buna benzer bir kontrol kısıtlaması ekleyin. Aradaki fark, Durum = 1 ve Say> 0 ise yanlış döndürmenizdir.

http://msdn.microsoft.com/en-us/library/ms188258.aspx

CREATE TABLE CheckConstraint
(
  Id TINYINT,
  Name VARCHAR(50),
  RecordStatus TINYINT
)
GO

CREATE FUNCTION CheckActiveCount(
  @Id INT
) RETURNS INT AS BEGIN

  DECLARE @ret INT;
  SELECT @ret = COUNT(*) FROM CheckConstraint WHERE Id = @Id AND RecordStatus = 1;
  RETURN @ret;

END;
GO

ALTER TABLE CheckConstraint
  ADD CONSTRAINT CheckActiveCountConstraint CHECK (NOT (dbo.CheckActiveCount(Id) > 1 AND RecordStatus = 1));

INSERT INTO CheckConstraint VALUES (1, 'No Problems', 2);
INSERT INTO CheckConstraint VALUES (1, 'No Problems', 2);
INSERT INTO CheckConstraint VALUES (1, 'No Problems', 2);
INSERT INTO CheckConstraint VALUES (1, 'No Problems', 1);

INSERT INTO CheckConstraint VALUES (2, 'Oh no!', 1);
INSERT INTO CheckConstraint VALUES (2, 'Oh no!', 2);
-- Msg 547, Level 16, State 0, Line 14
-- The INSERT statement conflicted with the CHECK constraint "CheckActiveCountConstraint". The conflict occurred in database "TestSchema", table "dbo.CheckConstraint".
INSERT INTO CheckConstraint VALUES (2, 'Oh no!', 1);

SELECT * FROM CheckConstraint;
-- Id   Name         RecordStatus
-- ---- ------------ ------------
-- 1    No Problems  2
-- 1    No Problems  2
-- 1    No Problems  2
-- 1    No Problems  1
-- 2    Oh no!       1
-- 2    Oh no!       2

ALTER TABLE CheckConstraint
  DROP CONSTRAINT CheckActiveCountConstraint;

DROP FUNCTION CheckActiveCount;
DROP TABLE CheckConstraint;

Tablo düzeyinde kontrol kısıtlamalarına baktım ama işleve eklenen veya güncellenen değerleri geçirmenin herhangi bir yolu yok, nasıl yapılacağını biliyor musunuz?
np-hard

Tamam, neden bahsettiğimi kanıtlamanıza yardımcı olacak örnek bir komut dosyası gönderdim. Test ettim ve işe yarıyor. Yorumlu iki satıra bakarsanız, aldığım mesajı görürsünüz. Dikkat edin, benim uygulamamda, yalnızca, halihazırda aktif bir tane varsa, aktif olan aynı kimliğe sahip ikinci bir öğe ekleyemeyeceğinizden emin oluyorum. Mantığı, etkin bir tane varsa, aynı kimliğe sahip herhangi bir öğe ekleyemeyecek şekilde değiştirebilirsiniz. Bu modelle, olasılıklar neredeyse sonsuzdur.
D. Patrick

Bir tetikleyicide aynı mantığı tercih ederim. "bir skaler işlevdeki bir sorgu ... CHECK kısıtlamanız bir sorguya dayanıyorsa ve herhangi bir güncellemeden birden fazla satır etkileniyorsa büyük sorunlar yaratabilir. Olan şey, ifade tamamlanmadan önce kısıtlamanın her satır için bir kez kontrol edilmesidir. . Bu, ifadenin atomikliğinin bozulduğu ve işlevin tutarsız bir durumda veritabanına sunulacağı anlamına gelir. Sonuçlar tahmin edilemez ve yanlıştır. " Bakınız: blogs.conchango.com/davidportas/archive/2007/02/19/…
onedaywhen

Bu sadece birgün birgün kısmen doğrudur. Veritabanı tutarlı ve tahmin edilebilir şekilde davranır. Kontrol kısıtlaması, satır tabloya eklendikten sonra ve işlem dbms tarafından taahhüt edilmeden önce yürütülür ve buna güvenebilirsiniz. Bu blog, kısıtlamayı bir seferde yalnızca bir ekleme yerine bir dizi eke karşı uygulamanız gereken oldukça benzersiz bir sorundan bahsediyordu. Ashish, her seferinde bir kesici uçta sınırlama istiyor ve bu kısıtlama doğru, tahmin edilebilir ve tutarlı bir şekilde çalışacaktır. Bu kulağa kısa geldiyse özür dilerim; Karakterlerim bitiyordu.
D. Patrick

3
Bu, ekler için harika çalışıyor ancak güncellemeler için çalışmıyor gibi görünüyor. EG Bunu, beklemediğim halde diğer eklerden sonra eklemek burada çalışıyor. CheckConstraint VALUES INSERT INTO (1, 'Sorun YokA', 2); CheckConstraint'i güncelle Recordstatus = 1 olarak ayarlayın, burada name = 'Sorun
YokA

152

Bakın, filtrelenmiş indeks . Belgelerden (vurgu benim):

Filtrelenmiş bir dizin, özellikle iyi tanımlanmış bir veri alt kümesinden seçim yapan sorguları kapsamaya uygun, optimize edilmiş kümelenmemiş bir dizindir. Tablodaki satırların bir bölümünü dizine eklemek için bir filtre koşulu kullanır. İyi tasarlanmış filtrelenmiş bir dizin, tam tablo dizinlerine kıyasla sorgu performansını artırabilir ve dizin bakımı ve depolama maliyetlerini azaltabilir.

Ve işte benzersiz bir dizini bir filtre koşulu ile birleştiren bir örnek:

create unique index MyIndex
on MyTable(ID)
where RecordStatus = 1;

Bu, esasen IDne zaman RecordStatusolduğu konusunda benzersizliği zorlar 1.

Bu dizinin oluşturulmasının ardından, bir benzersizlik ihlali bir hata yaratacaktır:

Msg 2601, Düzey 14, Durum 1, Satır 13
Benzersiz dizin 'MyIndex' olan 'dbo.MyTable' nesnesine yinelenen anahtar satırı eklenemiyor. Yinelenen anahtar değeri (9999).

Not: Filtrelenmiş dizin, SQL Server 2008'de tanıtıldı. SQL Server'ın önceki sürümleri için lütfen bu yanıta bakın .


SQL Server'ın ansi_paddingfiltrelenmiş indeksler gerektirdiğini unutmayın , bu nedenle SET ANSI_PADDING ONfiltrelenmiş bir indeks oluşturmadan önce bu seçeneğin açık olduğundan emin olun .
naXa

10

Silinen kayıtları kısıtlama içermeyen bir tabloya taşıyabilir ve belki de tek bir tablonun görünümünü korumak için iki tablonun UNION ile bir görünümünü kullanabilirsiniz.


2
Aslında oldukça zeki Carl. Sorunun cevabı değil, ama iyi bir çözüm. Tabloda çok sayıda satır varsa, bu da aktif kayıt aramayı hızlandırabilir çünkü aktif kayıt tablosuna bakabilirsiniz. Ayrıca, benzersiz kısıtlama, aşağıda yazdığım ve bir sayım yürütmesi gereken kontrol kısıtlamasının aksine bir indeks kullandığı için kısıtlamayı da hızlandıracaktır. Bunu sevdim.
D. Patrick

3

Bunu gerçekten hilekâr bir şekilde yapabilirsiniz ...

Tablonuzda bir şema görünümü oluşturun.

RecordStatus = 1 olan tablodan SEÇİN * ne olursa olsun GÖRÜNÜM OLUŞTUR

Şimdi, istediğiniz alanlarla görünümde benzersiz bir kısıtlama oluşturun.

Şemaya bağlı görünümler hakkında bir not olsa da, temel tabloları değiştirirseniz görünümü yeniden oluşturmanız gerekecektir. Bu yüzden bol bol aldatma.


Bu oldukça iyi bir öneri ve o kadar "hacky" değil. İşte bu filtrelenmiş indeks alternatifi hakkında daha fazla bilgi .
Scott Whitlock

Bu kötü bir fikir. Soru o değil.
FabianoLothor 01

Bir kez şematik bir görünüm kullandım ve bu hatayı asla tekrar etmedim. Çalışmak için büyük bir acı olabilirler. Altta yatan tabloyu değiştirirseniz görünümü yeniden oluşturmanız gerekmediğinden - en azından SQL sunucusunda, tüm görünümler için potansiyel olarak bunu yapmanız gerekir. İlk önce görünümü bırakmadan tabloyu değiştiremezsiniz, bu da önce referansları düşürmeden yapamayabilirsiniz. Ayrıca, depolama alanı sorunlu olabilir - ya alan nedeniyle ya da ekleme ve güncelleme için eklediği maliyet nedeniyle.
MattW

1

Çünkü kopyalara izin vereceksiniz, benzersiz bir kısıtlama çalışmayacaktır. RecordStatus sütunu için bir kontrol kısıtlaması ve yinelenen kimlikleri eklemeden önce var olan aktif kayıtları kontrol eden INSERT için bir saklı yordam oluşturabilirsiniz.


1

Bill'in önerdiği gibi bir RecordStatus olarak NULL kullanamazsanız, onun fikrini işlev tabanlı bir dizinle birleştirebilirsiniz. RecordStatus, kısıtlamanızda (ve aksi takdirde RecordStatus) dikkate almak istediğiniz değerlerden biri değilse NULL döndüren bir işlev oluşturun ve bunun üzerinde bir dizin oluşturun.

Bu, kısıtlamanızdaki tablodaki diğer satırları açıkça incelemek zorunda kalmamanız avantajına sahip olacak ve bu da performans sorunlarına neden olabilir.

SQL sunucusunu hiç bilmediğimi söylemeliyim ama bu yaklaşımı Oracle'da başarıyla kullandım.


iyi fikir, ancak yanıt için teşekkürler sql sunucusunda işlev tabanlı dizin yok
np-hard
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.