Tom said:
Joe's column goes on to discuss Triggers in the next section. As you
likely know, JET does not support triggers. So, you lose that safety valve
for updating a stored value if a dependent value is changed in the table.
If a 'safety value' is what's required, you can write a CHECK
constraint to prevent modifications that would invalidate the trend
values; I'm sure Celko would approve because CHECK constraints are in
the ANSI SQL-92 standard.
[You seem to have missed the fact that Celko derides the use of
triggers e.g. "Did your trigger change the trend in the 2000 April 03
row or not? If I drop a row, does your trigger change the trend in the
affected rows? Probably not."]
To use the example scenario in the Celko article, I think this CHECK
will do the job:
ALTER TABLE StockHistory ADD
CONSTRAINT safety_valve
CHECK (
NOT EXISTS (
SELECT *
FROM StockHistory AS S2,
StockHistory
WHERE S2.stock_id = StockHistory.stock_id
AND S2.sale_date < StockHistory.sale_date
AND S2.sale_date =
(
SELECT MAX(S4.sale_date)
FROM StockHistory AS S4
WHERE S4.stock_id = StockHistory.stock_id
AND S4.sale_date < StockHistory.sale_date
)
AND StockHistory.trend <> SWITCH(
StockHistory.price = S2.price, 0,
StockHistory.price < S2.price, -1,
StockHistory.price > S2.price, 1
)
)
);
To demonstrate that this can be implemented in Jet, here's my demo
code:
Sub Celko_calculated_columns()
Dim cat
Set cat = CreateObject("ADOX.Catalog")
With cat
.Create _
"Provider=Microsoft.Jet.OLEDB.4.0;" & _
"Data Source=C:\DropMe1.mdb"
With .ActiveConnection
.Execute _
"CREATE TABLE StockHistory ( stock_id CHAR(5)" & _
" NOT NULL, sale_date DATETIME DEFAULT DATE()" & _
" NOT NULL, price DECIMAL(10,4) NOT NULL," & _
" trend INTEGER DEFAULT 0 NOT NULL, CHECK(trend" & _
" IN(-1, 0, 1)), PRIMARY KEY (stock_id, sale_date)" & _
" );"
.Execute _
"ALTER TABLE StockHistory ADD CONSTRAINT" & _
" safety_valve CHECK (NOT EXISTS ( SELECT" & _
" * FROM StockHistory AS S2, StockHistory" & _
" WHERE S2.stock_id = StockHistory.stock_id" & _
" AND S2.sale_date < StockHistory.sale_date" & _
" AND S2.sale_date = ( SELECT MAX(S4.sale_date)" & _
" FROM StockHistory AS S4 WHERE S4.stock_id" & _
" = StockHistory.stock_id AND S4.sale_date" & _
" < StockHistory.sale_date ) AND " & _
" StockHistory.trend <> SWITCH(" & _
" StockHistory.price = S2.price," & _
" 0, StockHistory.price < S2.price, -1," & _
" StockHistory.price > S2.price, 1))) "
.Execute _
"INSERT INTO StockHistory (stock_id, sale_date," & _
" price, trend) VALUES ('XXX', #2000-04-01#," & _
" 10.75, 0);"
.Execute _
"INSERT INTO StockHistory (stock_id, sale_date," & _
" price, trend) VALUES ('XXX', #2000-04-02#," & _
" 313.25, 1);"
.Execute _
"INSERT INTO StockHistory (stock_id, sale_date," & _
" price, trend) VALUES ('XXX', #2000-04-03#," & _
" 200.00, -1);"
' Attempt to DELETE row ('XXX', #2000-04-02#)
' should fail with the CHECK biting:
Dim errMsg
On Error Resume Next
.Execute _
"DELETE FROM StockHistory WHERE stock_id" & _
" = 'xxx' AND sale_date = #2000-04-02#"
errMsg = Err.Description
On Error GoTo 0
MsgBox _
"Attempt to DELETE row ('XXX', #2000-04-02#):" & _
" does CHECK bite?" & vbCr & vbCr & _
IIf(Len(errMsg) = 0, "(no error)", errMsg)
' Attempt to INSERT row ('XXX', #2000-04-04#,
' 999.99, 0) should fail with the CHECK biting:
errMsg = vbNullString
On Error Resume Next
.Execute _
"INSERT INTO StockHistory (stock_id, sale_date," & _
" price, trend) VALUES ('XXX', #2000-04-04#," & _
" 999.99, 0);"
errMsg = Err.Description
On Error GoTo 0
MsgBox _
"Attempt to INSERT row ('XXX', #2000-04-04#," & _
" 999.99, 0): does CHECK bite?" & vbCr & vbCr & _
IIf(Len(errMsg) = 0, "(no error)", errMsg)
' Attempt to INSERT row ('abc', #2000-04-01#,
' 55.55, 0) should succeed:
errMsg = vbNullString
On Error Resume Next
.Execute _
"INSERT INTO StockHistory (stock_id, sale_date," & _
" price, trend) VALUES ('abc', #2000-04-01#," & _
" 55.55, 0);"
errMsg = Err.Description
On Error GoTo 0
MsgBox _
"Attempt to INSERT row ('abc', #2000-04-01#," & _
" 55.55, 0): does CHECK bite?" & vbCr & vbCr & _
IIf(Len(errMsg) = 0, "(no error)", errMsg)
End With
Set .ActiveConnection = Nothing
End With
End Sub
Perhaps another CHECK constraint is required to ensure where only one
row exists for a stock_id that the trend is zero...
Jamie.