The safest way to do this is to enforce your business rules using the built-in referential integrity constraints, as Alexander Kuznetsov describes in his article, "Storing intervals of time with no overlaps".
Applying the techniques listed there to your sample table results in the following script:
CREATE TABLE [Z_STORE_TEAM](
[STORENUM] [int] NOT NULL,
[TEAM] [varchar](10) NULL,
[EFFECTIVE] [date] NOT NULL,
[FINISHED] [date] NULL,
PRIMARY KEY CLUSTERED
(
[STORENUM] ASC,
[EFFECTIVE] ASC
)
) ON [PRIMARY];
INSERT [Z_STORE_TEAM]
([STORENUM], [TEAM], [EFFECTIVE], [FINISHED])
VALUES
(1, N'1', CAST(0x01380B00 AS Date), CAST(0x81380B00 AS Date)),
(1, N'2', CAST(0x81380B00 AS Date), NULL),
(2, N'1', CAST(0x01380B00 AS Date), NULL);
Modifications:
-- New column to hold the previous finish date
ALTER TABLE dbo.Z_STORE_TEAM
ADD PreviousFinished date NULL;
GO
-- Populate the previous finish date
UPDATE This
SET PreviousFinished = Previous.FINISHED
FROM dbo.Z_STORE_TEAM AS This
CROSS APPLY
(
SELECT TOP (1)
Previous.FINISHED
FROM dbo.Z_STORE_TEAM AS Previous
WHERE
Previous.STORENUM = This.STORENUM
AND Previous.FINISHED <= This.EFFECTIVE
ORDER BY
Previous.FINISHED DESC
) AS Previous;
GO
ALTER TABLE dbo.Z_STORE_TEAM
ADD CONSTRAINT UQ_STORENUM_PreviousFinished
UNIQUE (STORENUM, PreviousFinished);
GO
ALTER TABLE dbo.Z_STORE_TEAM
ADD CONSTRAINT CK_PreviousFinished_NotAfter_Effective
CHECK (PreviousFinished = EFFECTIVE);
GO
ALTER TABLE dbo.Z_STORE_TEAM
ADD CONSTRAINT UQ_STORENUM_FINISHED
UNIQUE (STORENUM, FINISHED);
GO
ALTER TABLE dbo.Z_STORE_TEAM
ADD CONSTRAINT FK_STORENUM_PreviousFinished
FOREIGN KEY (STORENUM, PreviousFinished)
REFERENCES dbo.Z_STORE_TEAM (STORENUM, FINISHED);
GO
ALTER TABLE dbo.Z_STORE_TEAM
ADD CONSTRAINT CK_EFFECTIVE_Before_FINISHED
CHECK (EFFECTIVE < FINISHED);
An attempt to insert the fourth row of sample data now fails with an error message:
INSERT [Z_STORE_TEAM]
([STORENUM], [TEAM], [EFFECTIVE], [FINISHED])
VALUES
(2, N'2', '20140201', NULL);
Please read Alex's article to understand how these constraints ensure your table data will always be valid. Having a set of constraints enforce your data integrity means no trigger code is required.
Related article by the same author:
Modifying Contiguous Time Periods in a History Table
Why do you expect B to be updated, when you inserted a row with a different id? Actually, your INSERT ALL
does not insert anything into B
.
The above works fine after fixing your trigger and sample data:
create or replace trigger updB
after update on B
for each row
declare
v varchar2(5);
begin
v := :new.val;
end;
/
This is how you reference the new val in the trigger, not with a select. Your original trigger would run into the infamous ORA-04091
"table is mutating" error.
SQL> insert all into A values ('a', 'false') into b values ('a', 'false') select * from dual;
2 rows created.
SQL> commit;
Commit complete.
SQL> update A set val = 'true';
Cek cek: false
1 row updated.
SQL> commit;
Commit complete.
SQL> select * from a;
ID VAL
---------- ----------
a true
SQL> select * from b;
ID VAL
---------- ----------
a true
For the output:
Your updA
trigger runs in an autonomous transaction. When you select val from A
, your original update is still not finished (commited), you will see the old value. Fixing your trigger for A
:
create or replace trigger updA
after update on A
for each row
declare
pragma autonomous_transaction;
cek varchar2(5);
begin
commit;
cek := :new.val;
dbms_output.put_line('Cek cek: ' || cek);
update B set val = :new.val where id = :new.id;
commit;
end;
/
Best Answer
The problem with your code is that you are declaring your variable as
varchar
without any length. With variable declarations,varchar
defaults to a length of 1 (note that in other scenarios it will have a different default length).As a best practice, you should always specify a length on the
varchar
datatype. For more details, see this article on the issue.