Estou tentando duplicar a lógica de negócios incorporada a um aplicativo da Web C# da intranet no banco de dados para que outros bancos de dados possam acessá-lo e trabalhar sob as mesmas regras. Essa "regra" parece difícil de implementar sem o uso de hacks.
CREATE TABLE CASE_STAGE
(
ID NUMBER(9) PRIMARY KEY NOT NULL,
STAGE_ID NUMBER(9) NOT NULL,
CASE_PHASE_ID NUMBER(9) NOT NULL,
DATE_CREATED TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP NOT NULL,
END_REASON_ID NUMBER(9),
PREVIOUS_CASE_STAGE_ID NUMBER(9),
"CURRENT" NUMBER(1) NOT NULL,
DATE_CLOSED TIMESTAMP(6) DEFAULT NULL
);
e
CREATE TABLE CASE_RECOMMENDATION
(
CASE_ID NUMBER(9) NOT NULL,
RECOMMENDATION_ID NUMBER(9) NOT NULL,
"ORDER" NUMBER(9) NOT NULL,
DATE_CREATED TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP NOT NULL,
CASE_STAGE_ID NUMBER(9) NOT NULL
);
ALTER TABLE CASE_RECOMMENDATION ADD (
CONSTRAINT SYS_C00000
PRIMARY KEY
(CASE_ID, RECOMMENDATION_ID));
A lógica de negócios pode ser resumida como
When Inserting into CASE_STAGE
If CASE_STAGE.STAGE_ID = 1646
THEN
CASE_STAGE.PREVIOUS_STAGE_ID must be found in CASE_RECOMMENDATION.CASE_STAGE_ID
Essa lógica pode ser incorporada em uma restrição Check ou um gatilho feio é o único caminho?
Editar:
- Para todos os valores de CASE_STAGE.STAGE_ID, o valor de PREVIOUS_STAGE_ID deve ser encontrado em CASE_STAGE.ID
- O aplicativo não permite exclusões de CASE_RECOMMENDATION, uma vez que não é mais CURRENT (ou seja, quando o valor de CASE_STAGE.CURRENT é 0, este estágio é fechado e não pode mais ser alterado, quando = 1, este é o estágio, ou linha, que está ativo e pode ser alterado agora.)
Editar: usar todas as excelentes ideias e comentários aqui é uma solução funcional para esse problema
CREATE MATERIALIZED VIEW LOG ON CASE_STAGE
TABLESPACE USERS
STORAGE (
BUFFER_POOL DEFAULT
)
NOCACHE
LOGGING
NOPARALLEL
WITH ROWID;
CREATE MATERIALIZED VIEW LOG ON CASE_RECOMMENDATION
TABLESPACE USERS
STORAGE (
BUFFER_POOL DEFAULT
)
NOCACHE
LOGGING
NOPARALLEL
WITH ROWID;
CREATE MATERIALIZED VIEW CASE_RECOMMENDATION_MV REFRESH FAST ON COMMIT AS
SELECT
cr.ROWID cr_rowid, --necessary for fast refresh
cs.ROWID cs_rowid, --necessary for fast refresh
cr.case_id,
cs.stage_id,
cr.recommendation_id
cr.case_stage_id,
cs.previous_case_stage_id
FROM CASE_RECOMMENDATION cr,
case_stage cs
WHERE cs.previous_case_stage_id = cr.case_stage_id (+)
AND CS.PREVIOUS_CASE_STAGE_ID IS NOT NULL
AND EXTRACT (YEAR FROM CS.DATE_CREATED) > 2010 --covers non conforming legacy data
AND CR.RECOMMENDATION_ID IS NULL
AND cs.stage_id =1646;
--this last line excludes everything but problem cases due to the outer join
ALTER TABLE CASE_RECOMMENDATION_MV ADD CONSTRAINT CASE_RECOMMENDATION_ck CHECK (
(previous_case_stage_id IS NOT NULL AND case_stage_id IS NOT NULL)
);
Ao inserir um estágio 1646 usando pacotes existentes sem recomendação, o erro era
ORA-12008: error in materialized view refresh path
ORA-02290: check constraint (APPBASE.CASE_RECOMMENDATION_MV_C01) violated
ORA-06512: at line 49
Tarefa concluída! Não é o objetivo de uma visão materializada, mas é melhor do que um gatilho.
Se
CASE_RECOMMENDATION.CASE_STAGE_ID
for exclusivo, você pode usar a integridade referencial com uma coluna virtual (11g+) para torná-la condicional:Vamos checar:
Se você tiver restrições complexas que deseja aplicar "invisivelmente" no banco de dados, poderá fazê-lo criando uma visualização materializada e aplicando restrições a ela.
Nesse caso, você pode fazer isso usando uma junção externa MV
CASE_RECOMMENDATION.CASE_STAGE_ID
paraCASE_STAGE.PREVIOUS_CASE_STAGE_ID
. Uma verificação deve ser feita para que nenhum deles seja nulo quando oCASE_STAGE.STAGE_ID = 1646
, assim:A restrição de verificação no MV só será invocada quando for atualizada, portanto, para que isso funcione com sucesso, você precisa garantir que isso seja feito no COMMIT. Isso aumentará o processamento do tempo de confirmação, portanto, você precisará ter em mente o seguinte:
Como esta solução implementa as restrições na camada SQL, ela supera alguns dos problemas de simultaneidade discutidos na solução procedural.
ATUALIZAR
Conforme apontado por Vincent, o tamanho do MV pode ser reduzido incluindo apenas as linhas com stage_id = 1646. Pode ser possível reescrever a consulta para não consumir nenhuma linha, mas não consigo pensar em como fazer isso direito agora:
Colocar a lógica de negócios no banco de dados é admirável e você certamente deve implementar restrições para coisas como essa quando puder. No entanto, um gatilho não é a única alternativa. Você pode resolver o problema no pacote PL/SQL fazendo o insert. Ao fazer isso, você obtém outros benefícios, como código do lado do cliente reduzido, menos trocas de contexto, vinculações automáticas, independência do aplicativo cliente, etc. Aqui está um exemplo incompleto (sem o bloqueio que seria necessário para executar isso simultaneamente).
Estou perdendo uma tabela como CASE_RECOMMENDATION_LIST(CASE_STAGE_ID NUMBER(9) PRIMARY KEY) em seu design. Cada CASE_RECOMMENDATION é membro do correspondente CASE_RECOMMENDATION_LIST Isso pode ser tratado por uma chave estrangeira. Um CASE_RECOMMENDATION_LIST deve conter pelo menos um CASE_RECOMMENDATION. A criação e exclusão de um CASE_RECOMMENDATION_LIST podem ser tratadas por gatilhos simples: crie um CASE_RECOMMENDATION_LIST para um CASE_STAGE_ID antes que o primeiro CASE_RECOMMENDATION para este CASE_STAGE_ID seja criado, exclua-o após o último CASE_RECOMMENDATION para este CASE_STGE_ID ser excluído. CASE_STAGE faz referência a no máximo um CASE_RECOMMENDATION_LIST usando o CASE_STAGE.PREVIOUS_CASE_STAGE_ID. CASE_STAGE.PREVOUS_STAGE_ID não deve ser nulo se CASE_STAGE.STAGE_ID=1646.
Talvez seja uma maneira melhor de criar uma entidade própria (e, portanto, uma tabela) para os 1646 CASE_STAGEs, mas não vou analisar isso mais adiante.