2011-03-28 74 views
2

我認爲這會有點深奧,但是想要拋出這種情況,以防萬一有人試過這樣的事情,或者如果有人已經嘗試過,發現它是不可能的。使用基於函數的索引推遲唯一約束?

我們有一張表,它需要對某一組列進行唯一性約束,但它也有一個「軟刪除」指示器。已被標記爲「已刪除」的記錄不應包括在唯一性檢查中。

這很好,我可以用一個獨特的基於函數的索引輕鬆解決這個問題。然而,問題更復雜的是,如果我們要在數據庫中實現這個約束,它必須是一個延遲約束,因爲Hibernate的工作方式。如果它不能完成,我們將不得不忽略這個約束,而且我不希望如果可能的話。

例如:

CREATE TABLE jkemp_test 
    (id NUMBER NOT NULL 
    , deleted_ind CHAR(1) DEFAULT 'N' NOT NULL); 

CREATE UNIQUE INDEX jkemp_test_funique 
    ON jkemp_test 
    (CASE WHEN deleted_ind = 'N' THEN id END); 

-- make this use the function-based index, maybe? 
ALTER TABLE jkemp_test 
    ADD CONSTRAINT jkemp_test_unique 
    UNIQUE (id) 
    DEFERRABLE INITIALLY DEFERRED; 

INSERT INTO jkemp_test VALUES (1,'N'); 
INSERT INTO jkemp_test VALUES (2,'N'); 

COMMIT; 

UPDATE jkemp_test SET deleted_ind='Y' WHERE id=1; 

COMMIT; 

-- depending on whether the constraint is deferred or not, either 
-- the insert or the commit will fail "unique constraint violated" 

INSERT INTO jkemp_test VALUES (1,'N'); 
COMMIT; 

一個雙贏的局面將是最後一次提交成功,同時還允許約束被推遲。 (我知道唯一索引的存在意味着約束不是當前推遲的。)

我們現在唯一的選擇是使用應用程序來實現約束,這不會是可靠的。另外,我們不想過多地更改數據模型(例如,我們可以將刪除的行移動到不同的表中,例如JKEMP_TEST_DELETED,但這會涉及應用程序中的太多複雜情況)。

這是在Oracle 11.2.0.1.0上。

回答

5

這適用於apex.oracle.com後面的11.2.0.2。它應該在11.2.0.1工作(也許在11.1,而不是在10g中虛擬colums是一個11克增強)

感謝Lucas Jellma

CREATE TABLE jkemp_test 
    (id NUMBER NOT NULL 
    , deleted_ind CHAR(1) DEFAULT 'N' NOT NULL); 

alter table jkemp_test 
ADD (active_id AS (CASE WHEN deleted_ind = 'N' THEN id END)) 
/

ALTER TABLE jkemp_test 
    ADD CONSTRAINT jkemp_test_unique 
    UNIQUE (active_id) 
    DEFERRABLE INITIALLY DEFERRED; 

,不過需要指定插入的列列表,因爲不應指定派生列(虛擬列)。我非常確定,hibernate有沒有觸及的列是好的。

INSERT INTO jkemp_test (id, deleted_ind) VALUES (1,'N'); 
INSERT INTO jkemp_test (id, deleted_ind) VALUES (2,'N'); 

COMMIT; 

UPDATE jkemp_test SET deleted_ind='Y' WHERE id=1; 

COMMIT; 

INSERT INTO jkemp_test (id, deleted_ind) VALUES (1,'N'); 
+0

+1非常好,我沒有想到一個計算列。謝謝! – 2011-03-28 05:15:14

2

傑夫,

這裏是實現將11g之前的工作中的版本也要求一種方式。

DROP MATERIALIZED VIEW MV_JKEMP ; 
DROP MATERIALIZED VIEW LOG ON jkemp_test ; 
DROP TABLE JKEMP_TEST ; 

CREATE TABLE jkemp_test 
    (id NUMBER NOT NULL 
    , deleted_ind CHAR(1) DEFAULT 'N' NOT NULL); 

CREATE MATERIALIZED VIEW LOG ON jkemp_test WITH ROWID ; 

CREATE MATERIALIZED VIEW MV_JKEMP 
REFRESH FAST ON COMMIT 
AS 
SELECT JT1.ROWID r1, JT2.ROWID r2 
FROM JKEMP_TEST JT1, JKEMP_TEST JT2 
WHERE JT1.ID = JT2.ID 
AND JT1.DELETED_IND = JT2.DELETED_IND 
AND JT1.ROWID != JT2.ROWID 
AND JT1.DELETED_IND = 'N' ; 

ALTER TABLE MV_JKEMP ADD CONSTRAINT MV_CHECK CHECK (R1 IS NULL OR R2 IS NULL) 


INSERT INTO jkemp_test VALUES (1,'N'); 
INSERT INTO jkemp_test VALUES (2,'N'); 
COMMIT; 
UPDATE jkemp_test SET deleted_ind='Y' WHERE id=1 AND deleted_ind = 'N'; 
COMMIT; 
SELECT * FROM JKEMP_TEST ; 
SELECT * FROM MV_JKEMP; 
INSERT INTO JKEMP_TEST VALUES (1,'N'); 
COMMIT; 

SELECT * FROM JKEMP_TEST ; 
SELECT * FROM MV_JKEMP; 

-- The following will succeed on the INSERT but fail on COMMIT 
INSERT INTO JKEMP_TEST VALUES (1,'N'); 
COMMIT; 

-- The following will succeed 
INSERT INTO JKEMP_TEST VALUES (3,'N'); 
COMMIT; 

SELECT * FROM JKEMP_TEST ; 
SELECT * FROM MV_JKEMP; 

-- The following will succeed 
UPDATE JKEMP_TEST SET DELETED_IND='Y' WHERE ID=1 AND DELETED_IND = 'N'; 
COMMIT; 

SELECT * FROM JKEMP_TEST ; 
SELECT * FROM MV_JKEMP; 

DELETE FROM JKEMP_TEST ; 
COMMIT; 

以上是在10.2.0.1上測試的。

+0

+1測試並通過:非常好的解決方案,適用於pre-11g版本。 – 2011-03-30 03:12:48