Beginning Microsoft SQL Server 2008 ... - S3 Tech Training
Beginning Microsoft SQL Server 2008 ... - S3 Tech Training Beginning Microsoft SQL Server 2008 ... - S3 Tech Training
Chapter 15: Triggers 460 IF EXISTS ( SELECT ‘True’ FROM Inserted i JOIN Deleted d ON i.ProductID = d.ProductID AND i.LocationID = d.LocationID WHERE (d.Quantity - i.Quantity) > d.Quantity / 2 AND d.Quantity – i.Quantity > 0 ) BEGIN RAISERROR(‘Cannot reduce stock by more than 50%% at once.’,16,1) ROLLBACK TRAN END Before we test this, let’s analyze what we’re doing here. First, we’re making use of an IF EXISTS just as we have throughout this chapter. We want to do the rollback only if something exists that meets the evil, mean, and nasty criteria that we’ll be testing for. Then we join the Inserted and Deleted tables together — this is what gives us the chance to compare the two. Our WHERE clause is the point where things might become a bit confusing. The first line of it is pretty straightforward. It implements the nominal statement of our business requirement; updates to the Quantity column that are more than half the units we previously had on hand will meet the EXISTS criterion and cause the conditional code to run (rejecting the transaction). The next line, though, is not quite so straightforward. As with all things in programming, we need to think beyond the nominal statement of the problem, and think about other ramifications. The requirement really applies only to reductions in orders — we certainly don’t want to restrict how many units are put in stock — so we make sure that we worry only about updates where the number in stock after the update is less than before the update. The > 0 requirement also addresses the scenario where there was only one item in stock. In such a scenario, we want to go ahead and sell that last one, but 1-1 is greater than ½, and the trigger would have rejected it without our > 0 addition. If both of these conditions have been met (over 50 percent and a reduction, rather than addition, to the inventory), then we raise the error. Notice the use of two % signs, rather than one, in RAISERROR. Remember that a % works as a placeholder for a parameter, so one % by itself won’t show up when your error message comes out. By putting two in a row (%%), we let SQL Server know that we really did want to print out a percent sign. OK — let’s check how it works. We’ll just pick a record and try to do an update that reduces the stock by more than 50 percent: UPDATE Production.ProductInventory SET Quantity = 1 -- Was 408 if you want to set it back WHERE ProductID = 1 AND LocationID = 1
I just picked out “Adjustable Race” as our victim, but you could have chosen any ProductID, as long as you set the value to less than 50 percent of its previous value. If you do, you’ll get the expected error: Msg 50000, Level 16, State 1, Procedure ProductIsRationed, Line 16 Cannot reduce stock by more than 50% at once. Msg 3609, Level 16, State 1, Line 1 The transaction ended in the trigger. The batch has been aborted. Note that we could have also implemented this in the SalesOrderDetail table by referencing the actual order quantity against the current Quantity amount, but we would have run into several problems: ❑ Updates that change: Is the process that’s creating the SalesOrderDetail record updating ProductInventory before or after the SalesOrderDetail record? That makes a difference in how we make use of the Quantity value in the ProductInventory table to calculate the effect of the transaction. ❑ The inventory external to the SalesOrderDetails table updates would not be affected: They could still reduce the inventory by more than half (this may actually be a good thing in many circumstances, but it’s something that has to be thought about). Using Triggers for Custom Error Messages We’ve already touched on this in some of our other examples, but remember that triggers can be handy for when you want control over the error message or number that gets passed out to your user or client application. With a CHECK constraint for example, you’re just going to get the standard 547 error along with its rather nondescript explanation. As often as not, this is less than helpful in terms of the user really figuring out what went wrong — indeed, your client application often doesn’t have enough information to make an intelligent and helpful response on behalf of the user. In short, sometimes you create triggers when there is already something that would give you the data integrity that you want, but won’t give you enough information to handle it. It’s worth noting that the need for custom error messages in SQL Server should be relatively rare, although passing custom error codes is often useful. Why not pass the custom error message? Well, one would think that you probably have an application layer on top of it, and it is likely going to want to put more context on the error anyway, so the SQL-Server-specific text may not be all that useful. Using a special error code may, however, be very useful to your application in terms of determining what exactly happened and applying the correct client-side error handling code. Other Common Uses for T riggers In addition to the straight data integrity uses, triggers have a number of other uses. Indeed, the possibilities are fairly limitless, but here are a few common examples: ❑ Updating summary information Chapter 15: Triggers 461
- Page 449 and 450: 13 User-Defined Functions Well, her
- Page 451 and 452: types!), except for BLOBs, cursors,
- Page 453 and 454: We get back the same set as with th
- Page 455 and 456: AS RETURN (SELECT BusinessEntityID,
- Page 457 and 458: in your relational database. These
- Page 459 and 460: AS BEGIN ( EmployeeID int NOT NULL,
- Page 461 and 462: So, as you can see, we can actually
- Page 463 and 464: Despite being schema-bound, this on
- Page 465 and 466: 14 Transactions and Locks This is o
- Page 467 and 468: we are unable or do not want to com
- Page 469 and 470: Figure 14-1 Data needed Data in cac
- Page 471 and 472: Transaction 4 This transaction wasn
- Page 473 and 474: Oops — problem!!! Transaction 2 h
- Page 475 and 476: The only cure for this is setting y
- Page 477 and 478: Exclusive Locks Exclusive locks are
- Page 479 and 480: Also: ❑ The Sch-S is compatible w
- Page 481 and 482: The syntax for switching between th
- Page 483 and 484: As with most things in life, howeve
- Page 485 and 486: purchased. Process 2 records sales;
- Page 487: Chapter 14: Transactions and Locks
- Page 490 and 491: Chapter 15: Triggers the world’s
- Page 492 and 493: Chapter 15: Triggers WITH ENCRYPTIO
- Page 494 and 495: Chapter 15: Triggers FOR|AFTER The
- Page 496 and 497: Chapter 15: Triggers 458 To illustr
- Page 500 and 501: Chapter 15: Triggers ❑ Feeding de
- Page 502 and 503: Chapter 15: Triggers Trigger Firing
- Page 504 and 505: Chapter 15: Triggers Like regular t
- Page 506 and 507: Chapter 15: Triggers The COLUMNS_UP
- Page 508 and 509: Chapter 15: Triggers This is the sa
- Page 510 and 511: Chapter 15: Triggers 472 we have th
- Page 512 and 513: Chapter 16: A Brief XML Primer So,
- Page 514 and 515: Chapter 16: A Brief XML Primer Figu
- Page 516 and 517: Chapter 16: A Brief XML Primer Elem
- Page 518 and 519: Chapter 16: A Brief XML Primer ❑
- Page 520 and 521: Chapter 16: A Brief XML Primer 482
- Page 522 and 523: Chapter 16: A Brief XML Primer Name
- Page 524 and 525: Chapter 16: A Brief XML Primer The
- Page 526 and 527: Chapter 16: A Brief XML Primer 2. W
- Page 528 and 529: Chapter 16: A Brief XML Primer SQL
- Page 530 and 531: Chapter 16: A Brief XML Primer So,
- Page 532 and 533: Chapter 16: A Brief XML Primer If,
- Page 534 and 535: Chapter 16: A Brief XML Primer 496
- Page 536 and 537: Chapter 16: A Brief XML Primer Note
- Page 538 and 539: Chapter 16: A Brief XML Primer RAW
- Page 540 and 541: Chapter 16: A Brief XML Primer AUTO
- Page 542 and 543: Chapter 16: A Brief XML Primer EXPL
- Page 544 and 545: Chapter 16: A Brief XML Primer Chec
- Page 546 and 547: Chapter 16: A Brief XML Primer 508
Chapter 15: Triggers<br />
460<br />
IF EXISTS<br />
(<br />
SELECT ‘True’<br />
FROM Inserted i<br />
JOIN Deleted d<br />
ON i.ProductID = d.ProductID<br />
AND i.LocationID = d.LocationID<br />
WHERE (d.Quantity - i.Quantity) > d.Quantity / 2<br />
AND d.Quantity – i.Quantity > 0<br />
)<br />
BEGIN<br />
RAISERROR(‘Cannot reduce stock by more than 50%% at once.’,16,1)<br />
ROLLBACK TRAN<br />
END<br />
Before we test this, let’s analyze what we’re doing here.<br />
First, we’re making use of an IF EXISTS just as we have throughout this chapter. We want to do the<br />
rollback only if something exists that meets the evil, mean, and nasty criteria that we’ll be testing for.<br />
Then we join the Inserted and Deleted tables together — this is what gives us the chance to compare<br />
the two.<br />
Our WHERE clause is the point where things might become a bit confusing. The first line of it is pretty<br />
straightforward. It implements the nominal statement of our business requirement; updates to the<br />
Quantity column that are more than half the units we previously had on hand will meet the EXISTS<br />
criterion and cause the conditional code to run (rejecting the transaction).<br />
The next line, though, is not quite so straightforward. As with all things in programming, we need to<br />
think beyond the nominal statement of the problem, and think about other ramifications. The requirement<br />
really applies only to reductions in orders — we certainly don’t want to restrict how many units<br />
are put in stock — so we make sure that we worry only about updates where the number in stock after<br />
the update is less than before the update. The > 0 requirement also addresses the scenario where there<br />
was only one item in stock. In such a scenario, we want to go ahead and sell that last one, but 1-1 is greater<br />
than ½, and the trigger would have rejected it without our > 0 addition.<br />
If both of these conditions have been met (over 50 percent and a reduction, rather than addition, to the<br />
inventory), then we raise the error. Notice the use of two % signs, rather than one, in RAISERROR. Remember<br />
that a % works as a placeholder for a parameter, so one % by itself won’t show up when your error message<br />
comes out. By putting two in a row (%%), we let <strong>SQL</strong> <strong>Server</strong> know that we really did want to print<br />
out a percent sign.<br />
OK — let’s check how it works. We’ll just pick a record and try to do an update that reduces the stock by<br />
more than 50 percent:<br />
UPDATE Production.ProductInventory<br />
SET Quantity = 1 -- Was 408 if you want to set it back<br />
WHERE ProductID = 1<br />
AND LocationID = 1