Logic kinh doanh này có thể được thi hành bởi một ràng buộc cơ sở dữ liệu có điều kiện không?


7

Tôi đang cố gắng sao chép logic nghiệp vụ thể hiện một ứng dụng web C # mạng nội bộ trong cơ sở dữ liệu để các cơ sở dữ liệu khác có thể truy cập nó và hoạt động theo cùng một quy tắc. "Quy tắc" này có vẻ khó thực hiện nếu không sử dụng hack.

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
);

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));

Logic kinh doanh có thể được tóm tắt là

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

Logic này có thể được thể hiện trong một ràng buộc Kiểm tra hay là một kích hoạt xấu xí là cách duy nhất?

Biên tập:

  • Đối với tất cả các giá trị của CASE_STAGE.STAGE_ID, giá trị cho PREVIOUS_STAGE_ID phải được tìm thấy trong CASE_STAGE.ID
  • Ứng dụng không cho phép xóa khỏi CASE_RECOMMENDATION một khi nó không còn HIỆN TẠI (tức là khi giá trị của CASE_STAGE.CURRENT là 0 thì giai đoạn này đã bị đóng và không thể thay đổi nữa, khi = 1 đây là giai đoạn hoặc hàng, đang hoạt động và có thể thay đổi ngay bây giờ.)

Chỉnh sửa: sử dụng tất cả các ý tưởng và nhận xét xuất sắc ở đây là một giải pháp hiệu quả cho vấn đề này

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)
);

Khi chèn giai đoạn 1646 bằng các gói hiện có mà không có khuyến nghị, lỗi là

ORA-12008: error in materialized view refresh path
ORA-02290: check constraint (APPBASE.CASE_RECOMMENDATION_MV_C01) violated
ORA-06512: at line 49

Công việc hoàn thành! Không phải những gì một khung nhìn cụ thể hóa được dự định nhưng tốt hơn là một kích hoạt.


CASE_RECOMMENDATION.CASE_STAGE_IDduy nhất?
Vincent Malgrat

Không may măn. Một giai đoạn trường hợp có thể có một hoặc nhiều khuyến nghị ở nhiều giai đoạn. Có thể có từ 0 đến nhiều giai đoạn với CASE_STAGE.STAGE_ID = 1646
kevinsky

Thật không may, bạn không thể sử dụng tính toàn vẹn tham chiếu với các cột không duy nhất.
Vincent Malgrat

Có gì đặc biệt 1646? Là thay đổi thiết kế (tách bảng thành hai) là một tùy chọn?
ypercubeᵀᴹ

Và làm thế nào nghiêm túc là "khi chèn vào" để được giải thích? Có hợp lệ để thực hiện một chèn được phép, và sau đó xóa hàng được tham chiếu khỏi cha mẹ không?
Erwin Smout

Câu trả lời:


4

Nếu bạn có các ràng buộc phức tạp mà bạn muốn áp dụng "vô hình" trong cơ sở dữ liệu, bạn có thể làm như vậy bằng cách tạo một khung nhìn cụ thể hóa sau đó áp dụng các ràng buộc cho điều đó.

Trong trường hợp này, bạn có thể làm điều đó bằng một MV ngoài-tham gia CASE_RECOMMENDATION.CASE_STAGE_IDvào CASE_STAGE.PREVIOUS_CASE_STAGE_ID. Sau đó, một kiểm tra nên được thực hiện rằng cả hai đều không có giá trị khi CASE_STAGE.STAGE_ID = 1646, như vậy:

--necessary for fast refresh
create materialized view log on case_stage with rowid;
create materialized view log on case_recommendation with rowid;

create materialized view 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,
         cr.recommendation_id,
         case when cs.stage_id = 1646 then
           'Y'
         else
           'N'
         end do_chk,
         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 (+);

alter table mv add constraint mv_ck check (
    (do_chk = 'Y' and previous_case_stage_id is not null and case_stage_id is not null )
    or
    (do_chk = 'N')
);

insert into  CASE_STAGE values (1, 1, 1, sysdate, null, null, 1, null);

insert into CASE_RECOMMENDATION values (1, 1, 1, sysdate, 1);
commit;

insert into CASE_STAGE values (2, 1646, 1, sysdate, null, null, 1, null);

pro fails because previous_case_stage_id is null
commit;
SQL Error: ORA-12008: error in materialized view refresh path
ORA-02290: check constraint (CHRIS.MV_CK) violated
12008. 00000 -  "error in materialized view refresh path"

insert into CASE_STAGE values (2, 1646, 1, sysdate, null, 2, 1, null); 

pro fails because previous_case_stage_id doesn't exist in CASE_RECOMMENDATION'
commit;
SQL Error: ORA-12008: error in materialized view refresh path
ORA-02290: check constraint (CHRIS.MV_CK) violated
12008. 00000 -  "error in materialized view refresh path"

pro succeeds !
insert into CASE_STAGE values (2, 1646, 1, sysdate, null, 1, 1, null); 
commit;

pro we can't delete stuff from case recommendation now 
delete CASE_RECOMMENDATION;
commit;
SQL Error: ORA-12008: error in materialized view refresh path
ORA-02290: check constraint (CHRIS.MV_CK) violated
12008. 00000 -  "error in materialized view refresh path"

Ràng buộc kiểm tra trên MV sẽ chỉ được gọi khi được làm mới, do đó, để điều này hoạt động thành công, bạn cần đảm bảo rằng việc này được thực hiện trên CAMIT. Điều này sẽ thêm vào quá trình xử lý thời gian cam kết của bạn, vì vậy bạn sẽ cần ghi nhớ những điều sau:

  • Trừ khi các bộ dữ liệu của bạn nhỏ một cách tầm thường, MV nên được NHANH CHÓNG. Có một số hạn chế về cách bạn xây dựng MV của mình để cho phép điều này
  • Nếu bạn có nhiều chèn trong một giao dịch, lỗi sẽ chỉ được đưa ra khi bạn cam kết, có thể khiến việc xác định tuyên bố vi phạm trở nên khó khăn hơn
  • Nếu bạn có mức độ chèn đồng thời cao, điều này có thể dẫn đến một số vấn đề tương tranh

Vì giải pháp này thực hiện các ràng buộc trong lớp SQL, tuy nhiên, nó khắc phục một số vấn đề tương tranh được thảo luận trong giải pháp thủ tục.

CẬP NHẬT

Như Vincent đã chỉ ra, kích thước của MV chỉ có thể được giảm bằng cách chỉ bao gồm các hàng có stage_id = 1646. Có thể viết lại truy vấn để không tiêu thụ hàng, nhưng tôi không thể nghĩ làm thế nào cho đúng hiện nay:

create materialized view 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,
         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.stage_id = 1646;

alter table mv add constraint mv_ck check (
    (previous_case_stage_id is not null and case_stage_id is not null)
);

Tuyệt vời, tôi đã chạy vào điều này với một MV khác nhưng chỉ thấy thêm một ràng buộc là một vấn đề không phải là một giải pháp
kevinsky

Tôi không thích giải pháp này. Thể hiện một số loại toàn vẹn tham chiếu theo cách này là có thể nhưng rất khó hiểu. Bạn đã chỉ ra rằng việc kiểm tra các ràng buộc sẽ được thực hiện vào cuối giao dịch. Điều đó cũng có thể gây nhầm lẫn. Ngoài ra cách thực hiện kiểm tra bằng cách lưu trữ dữ liệu đã tham gia này sẽ tốn rất nhiều dung lượng. Bạn triển khai thực tế là có n đề xuất cho case_stage bằng cách lưu trữ n hàng. Đối với tôi, điều này trông giống như nếu một người muốn lấy tổng của các số a và b một lần tăng b lần để có kết quả.
phép lạ173

Bạn có thể chỉ lưu trữ các hàng cần kiểm tra trong MV (bằng cách thêm mệnh đề WHERE vào truy vấn MV) không? Hoặc thậm chí tốt hơn, chỉ lưu trữ các hàng vi phạm (để MV trống).
Vincent Malgrat

Xin chào Vincent, tôi đã thêm một bản cập nhật cho điều đó. Nếu tôi có thể nghĩ ra một cách để loại trừ tất cả các hàng (hoặc bất kỳ ai khác có thể cho tôi biết!) Tôi sẽ cập nhật lại.
Chris Saxon

@ miracle173 - Tôi chưa bao giờ nói đây là một giải pháp tốt, chỉ là nó có thể! Tôi đồng ý rằng điều này là phức tạp và có khả năng gây nhầm lẫn. Tuy nhiên, mã thủ tục cần thiết để thực hiện chức năng tương tự trong môi trường nhiều người dùng cũng có thể phức tạp - đặc biệt nếu việc xóa được cho phép. Ít nhất giải pháp này đảm bảo cơ sở dữ liệu không thể ở trạng thái không nhất quán như được xác định bởi các quy tắc trong câu hỏi.
Chris Saxon

6

Nếu CASE_RECOMMENDATION.CASE_STAGE_IDlà duy nhất, bạn có thể sử dụng tính toàn vẹn tham chiếu với một cột ảo (11g +) để làm cho nó có điều kiện:

alter table CASE_RECOMMENDATION ADD CONSTRAINT unique_case_stage unique (CASE_STAGE_ID);

-- virtual column only defined when stage_id=1646
alter table CASE_STAGE add 
   (case_1646 as (case when stage_id=1646 then previous_case_stage_id end));

-- check that the virtual column is defined when stage_id=1646
alter table case_stage add 
    constraint chk_1646 check ( stage_id!=1646 or previous_case_stage_id is not null);

-- referential integrity
alter table case_stage add 
     constraint fk_1646 foreign key (case_1646) 
     references case_recommendation (case_stage_id);

Hãy kiểm tra:

SQL> insert into  CASE_STAGE values (1, 1, 1, sysdate, null, null, 1, null, default);

1 row(s) inserted.

SQL> insert into CASE_RECOMMENDATION values (1, 1, 1, sysdate, 1);

1 row(s) inserted.

SQL> -- fails because previous_case_stage_id is null
SQL> insert into CASE_STAGE values (2, 1646, 1, sysdate, null, null, 1, null, default);

ORA-02290: check constraint (VNZ_TEST3.CHK_1646) violated

SQL> -- fails because previous_case_stage_id doesn't exist in CASE_RECOMMENDATION
SQL> insert into CASE_STAGE values (2, 1646, 1, sysdate, null, 2, 1, null, default); 

ORA-02291: integrity constraint (VNZ_TEST3.FK_1646) violated - parent key not found

SQL> -- succeeds !
SQL> insert into CASE_STAGE values (2, 1646, 1, sysdate, null, 1, 1, null, default); 

1 row(s) inserted.

Cảm ơn bạn, một câu trả lời tuyệt vời mà tôi sẽ có thể sử dụng cho các "quy tắc" khác, không phải là quy tắc này.
kevinsky

3

Đưa logic kinh doanh vào cơ sở dữ liệu là đáng ngưỡng mộ, và bạn chắc chắn nên thực hiện các ràng buộc cho những thứ như thế này khi bạn có thể. Tuy nhiên, một kích hoạt không phải là sự thay thế duy nhất. Bạn có thể giải quyết vấn đề trong gói PL / SQL khi thực hiện thao tác chèn. Bằng cách làm như vậy, bạn sẽ đạt được các lợi ích khác như giảm mã phía máy khách, chuyển đổi ngữ cảnh ít hơn, liên kết tự động, độc lập ứng dụng khách, v.v ... Dưới đây là một ví dụ không đầy đủ (không có khóa cần thiết để chạy đồng thời).

CREATE OR REPLACE PACKAGE CaseStage As

Procedure InsertCaseStage (
   pId                      In Case_Stage.Id%Type,
   pStage_Id                In Case_Stage.Stage_Id%Type,
   pDate_Created            In Case_Stage.Date_Created%Type DEFAULT Current_Timestamp,
   pEnd_Reason_Id           In Case_Stage.End_Reason_Id%Type,
   pPrevious_Case_Stage_Id  In Case_Stage.Previous_Case_Stage_Id%Type,
   pCurrent                 In Case_Stage."CURRENT"%Type,
   pDate_Closed             In Case_Stage.Date_Closed%Type DEFAULT NULL
   );
END;
/

CREATE OR REPLACE PACKAGE BODY CaseStage As

Procedure InsertCaseStage (
   pId                      In Case_Stage.Id%Type,
   pStage_Id                In Case_Stage.Stage_Id%Type,
   pDate_Created            In Case_Stage.Date_Created%Type DEFAULT Current_Timestamp,
   pEnd_Reason_Id           In Case_Stage.End_Reason_Id%Type,
   pPrevious_Case_Stage_Id  In Case_Stage.Previous_Case_Stage_Id%Type,
   pCurrent                 In Case_Stage."CURRENT"%Type,
   pDate_Closed             In Case_Stage.Date_Closed%Type DEFAULT NULL
   ) 
As
   cPreviousCaseStageCheck Case_Stage.Stage_Id%Type := 1646;   
   vPreviousCount Number(1);
Begin
   If (pStage_Id = cPreviousCaseStageCheck) Then
      SELECT count(*) INTO vPreviousCount FROM Case_Recommendation 
      WHERE Case_Stage_Id = pPrevious_Case_Stage_Id
      AND rownum<=1;
      If (vPreviousCount <> 1) Then
         Raise_Application_Error(-20001,'Previous_Stage_Id must be found in '
            || 'Case_Recommendation. ' || pPrevious_Case_Stage_Id || ' was not.');
      End If;
   End If;

   /* INSERT... */
End;

END;
/

Tôi là một người tin tưởng tuyệt vời vào các gói và đã có một gói thực hiện các thao tác CRUD và đăng nhập vào các bảng này. Tuy nhiên, tôi không thể thực thi việc sử dụng gói nếu cơ sở dữ liệu khác cố gắng chèn thông qua một liên kết. Tôi phải dựa vào sự siêng năng của lập trình viên. Một ràng buộc là sạch hơn, dễ dàng hơn để khám phá và tài liệu nhưng câu trả lời của bạn sẽ hoạt động nếu một gói được sử dụng.
kevinsky

1
Mặc dù một thủ tục chắc chắn là sự lựa chọn tốt hơn khi các ràng buộc không có sẵn hoặc quá phức tạp, chúng khó mã hóa đúng hơn. Chẳng hạn, quy trình của bạn có thể bị lạm dụng trong môi trường nhiều người dùng: Người dùng A chèn 1646, người dùng B xóa đề xuất, người dùng A cam kết => DB ở trạng thái không nhất quán. Bạn sẽ cần khóa mạnh để đạt được các chức năng tương tự như một ràng buộc.
Vincent Malgrat

Điểm tuyệt vời, bây giờ tôi thấy lý do tại sao ứng dụng không cho phép xóa các mục trong CASE_STAGE hoặc CASE_RECOMMENDATION sau khi đóng giai đoạn.
kevinsky

1
@kevinsky - Tôi đồng ý rằng logic này sẽ tốt hơn trong một ràng buộc, bất kể, tại sao bạn không thể thực thi việc sử dụng gói? Bằng cách không cho phép bất kỳ ai đăng nhập với tư cách là chủ sở hữu bảng và không cấp đặc quyền chèn trên bảng, bạn có thể đảm bảo rằng tất cả quyền truy cập đều đi qua gói.
Leigh Riffel

@Vincent Malgrat - Bạn hoàn toàn chính xác. Các ràng buộc được ưu tiên khi có thể và các khóa sẽ cần thiết trong môi trường đồng thời.
Leigh Riffel

0

Tôi đang thiếu một bảng như CASE_RECOMMENDATION_LIST (CASE_STAGE_ID SỐ (9) KEY PRIMARY) trong thiết kế của bạn. Mỗi CASE_RECOMMENDATION là thành viên của CASE_RECOMMENDATION_LIST tương ứng Điều này có thể được xử lý bởi một khóa ngoại. Một CASE_RECOMMENDATION_LIST phải chứa ít nhất một CASE_RECOMMENDATION. Việc tạo và xóa CASE_RECOMMENDATION_LIST có thể được xử lý bằng các trình kích hoạt đơn giản: tạo CASE_RECOMMENDATION_LIST cho CASE_STAGE_ID trước khi CASE_RECOMMENDATION cho CASE_STAGE_ID này được tạo sau đó. CASE_STAGE tham chiếu nhiều nhất một CASE_RECOMMENDATION_LIST bằng cách sử dụng CASE_STAGE.PREVIOUS_CASE_STAGE_ID. CASE_STAGE.PREVOUS_STAGE_ID không được rỗng nếu CASE_STAGE.STAGE_ID = 1646.

Có lẽ nó sẽ là một cách tốt hơn để tạo một thực thể riêng (và do đó là một bảng) cho 1646 CASE_STAGE nhưng tôi sẽ không phân tích thêm về điều này.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.