Отложенное уникальное ограничение с использованием индекса на основе функций? - PullRequest
2 голосов
/ 28 марта 2011

Я думаю, что это будет немного эзотерично, но я хотел бы добавить это на тот случай, если кто-нибудь попробовал что-то подобное или кто-то уже пытался и нашел это невозможным.

У нас есть таблица, которая требует ограничения уникальности для определенного набора столбцов, но она также имеет индикатор «мягкого удаления». Записи, помеченные как «удаленные», не должны включаться в проверку уникальности.

Все хорошо, я мог бы легко решить эту проблему с помощью уникального индекса на основе функций. Однако, что усложняет дело, так это то, что если мы собираемся реализовать это ограничение в базе данных, оно должно быть отложенным, из-за того, как работает 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 ]

5 голосов
/ 28 марта 2011

Это работает в 11.2.0.2 за apex.oracle.com.Это ДОЛЖНО работать в 11.2.0.1 (и, возможно, в 11.1, но не в 10g, так как виртуальные столбцы были улучшением в 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;

Вы должны указать список столбцов для вставок, поскольку производный столбец (виртуальный столбец) не должен быть указан.Я почти уверен, что спящий режим хорош тем, что он не имеет столбцов.

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');
2 голосов
/ 29 марта 2011

Джефф,

Вот один из способов реализовать требование, которое будет работать и в версиях до 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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...