使用基于函数的索引延迟唯一约束?

时间:2011-03-28 01:31:39

标签: sql oracle unique-constraint

我认为这会有点深奥但是想把它扔出去,万一有人试过这样的事情,或者如果有人已经尝试过并发现它是不可能的。

我们有一个表需要对某组列进行唯一性约束,但它还有一个“软删除”指示符。已标记为“已删除”的记录不应包含在唯一性检查中。

这很好,我可以使用基于函数的独特索引轻松解决这个问题。然而,使问题复杂化的是,如果我们要在数据库中实现这个约束,它必须是延迟约束,因为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上。

2 个答案:

答案 0 :(得分:5)

这适用于apex.oracle.com后面的11.2.0.2。它应该在11.2.0.1中工作(可能在11.1中,但不是在10g中,因为虚拟colums是11g增强)

归功于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');

答案 1 :(得分: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上进行了测试。