In this video, we'll introduce the concept of triggers. In a separate video, we'll give an extensive demonstration of triggers in a running system. As a reminder, triggers are event condition action rules. They specify that whenever a certain type of event occurs in the database, check the condition over the database and if it's true execute an action automatically. There are a couple of reasons that triggers are used, fairly extensively actually in database applications. One of them is to move logic that monitors the database from the applications into the database system itself. That allows the monitoring to be done more efficiently, and it's also more modular, so it doesn't have to be repeated in every application. A second and probably the most common use of triggers is simply to enforce integrity constraints. And you might wonder why are those constraints not enforced by the constraint system. Well, one reason is that some constraint systems are limited and we can enforce more expressive integrity constraints using triggers and second of all triggers can do automatic repair of constraints when they are violated, by specifying the repair as the action portion of the trigger. I do want to mention that implementations of triggers vary significantly across the different database systems. In this introductory video, we'll be talking about the SQL standard for triggers. But in our demonstration, we'll be using the triggers as supported by SQLite. So, here we have the syntax of creating a trigger using the SQL standard. The second line is the event portion of the trigger. It says that the trigger should be activated either before, or after, or instead of specific events, and the specific events that can be specified are insert on a Table T, or delete on a table T, or update two particular columns on a table T. And actually, the columns themselves are optional. Update up columns on table T. Let's skip the referencing variables clause for a moment, and go on to the for each row. So, for each row is an optional clause that states that the trigger should be activated once for each modified tuple. Let me explain, what the deal is here. So, when we run, say, a delete command on the database; that delete command might delete, say, ten tuples. If we specify for each row in our trigger, then, we will run the trigger ten times, once for each deleted tuple. On the other hand, if for each row is not present, then we will execute the trigger once for the entire statement. Now, one tricky thing is that no matter what, the trigger is activated at the end of the statement. But its trigger, it's activated either ten times, for the ten deleted tuples or once if for each row is not present. Now, let's talk about the referencing variables. I'm going to write them down here. The idea of referencing variables is that they give us a way to reference the data that was modified that caused the trigger to be activated. So, we can have, in the referencing variables, these are key words, old row as. And then we can give a name to the old row. We can have new row as, and again name, and we can also have old table as a name and new table. And there's a whole bunch of things to explain here So, it's possible to have up to all four of these on a single trigger, but there are certain restrictions, and let me explain. First of all, if we have a trigger that is based on insertions, then we can only refer to new data. That would be the new inserted data. If we have a trigger activated by deletions, then we can only refer to the old variables for the deleted data. If we have the case of update, then we can refer to both old and new, and we will get the previous version of the updated values, and the new version of those values. So, we can only have both old and new in the case when our trigger is activated by an update. Now, let's talk about about row versus table. So, if we have a row-level trigger, as a reminder, that will be triggered once for each modified tuple, but after the entire statement has run. So, lets take, for example, a row level delete. In the case of deletes, we can only have old, but we could have for a row level trigger, both the old row and the old table. The old row would refer to the specific tuple that the trigger is activated for. And again, if we deleted ten rows and it will be activated ten times, once for each deleted tupel. While the old table will refer to all ten of the updated, of the deleted tuples. Now, there's often a lot of confusion with the old table. It's not referring to the old state of the database, it's referring specifically to the set of tuples that were in this case deleted. If our tuple, if our trigger is not for each row, if it's a statement level trigger, then we cannot refer to the row level variables, but we only have the table level variables. So, to reiterate, if we had, say, an insert that was row-level, then we could have both new row and new table. If we have a statement-level insert, we can only have new table. If we have a row-level delete, then we can have both old row and old table, but if it's a statement-level delete, then we can only have old table. Finally, if we have a row-level update, then, we can have all four of these. But if we have a statement-level update, then, we would only have the old table and the new table. Just to clarify, when I say row-level I mean that for each row is present, and when I say statement-level, I mean that for each row is not present. OK. So, now we covered the those clauses. Fortunately, the last two are a little bit easier. The condition here is, it's like a SQL wear condition. It's going to test the condition on the database, and if the condition is true, then, the action will be performed. Actually, what this is really like is like a general assertion. We saw there were certain ways of describing conditions that are on entire databases, and we will see a number of examples. And finally, last of all the action, in the SQL standard, the action is a SQL statement. In systems, some systems will have a set of simple statements and a begin-end bracket. Some will have stored procedures. So this is a case where the systems do vary quite a bit. We'll be using SQL light, which has, as it's action, begin and end with any number of SQL statements within it. Well, that all seems very complicated and there are quite a few complications with triggers, but in many cases they're relatively straight forward, and I think the next thing we'll do, just to relax for a moment, is take a look at a fairly simple example. In this example, we're going to implement referential integrity as we discussed in the previous video. Let's say that we have a table R whose attribute A references attribute B of table S and we want to implement cascaded delete. As a reminder, what that means is, if we delete from table 's' then any 'a' values that reference the deleted B values will themselves also be deleted. Ok, so let's specify that in a trigger, it's really quite simple. We give the trigger we say after we delete on 's', so this trigger will be activated whenever we delete from 's'. We're going to make it a row level trigger So that means we're going to activate the trigger once for each deleted row. We're going to set up that deleted row to be called O. And finally there's no conditions so whenever we have a delete from S, then in our action, we're going to delete from R all tuples where the A value equals the B value of the deleted couple from S. So that should all be pretty easy to understand. Just as one little change let's take a look at writing the same trigger as a statement level instead of a row row-level trigger. So now I've taken away for each row, and let's look at what changes we need to make. Well, first of all, we don't have old row anymore, as I mentioned, for on statement level triggers we only have old tables. So now we're going to set up a variable called OT that's referencing old tables and remember this is going to the set of deleted couples. It's not the old value of the table, but just the value of the tuples that have been deleted. The other thing we need to change is the action of the trigger. Instead of matching one tuple at a time, we just look for tuples in R where the A value is among the B values that were deleted from S and we delete those couples from are. And that works exactly the same as the row level version of the trigger. Now you might wonder which version you should use, well it's turns out some systems don't support both systems and you don't have a choice. For this particular example, probably the statement level trigger would be more efficient. Since it only activates the trigger once, and takes care of all the referential integrity cascaded deletes in one fell swoop. So this example shows that triggers can be quite natural. But there are a lot of complexities as I alluded to in the original slide that showed the full trigger syntax. So just to go through some of the trickier issues, we talked already a bit about row level versus statement level. And the use of the different new and old row, and new and old table. With triggers that execute after the modification, this is fairly understandable. But things can get more complicated when we have or instead of the modification that causes the trigger to be activated. Secondly, we can have multiple triggers activated at this Same time, its pretty simple. What if I declared two separate triggers that are activated by deletes say on a particular table. The we have to ask which one is going to go first, and maybe the behavior will differ depending on which one goes first. So that's something that needs to be thought about when one defines triggers, and understands how they're going to behave when the database is modified. Another possibility that we have to consider is not when triggers are activated at the same time But when triggers activate each other in a chaining effect, so I might have a trigger that's activated and it performs an action that the database modification that activates another trigger which in turn can activate other triggers. We can also have triggers that trigger themselves potentially. We can have cycles well, when a trigger, triggers itself, it's a cycle. But we can also have t-1 that triggers t-2 that triggers t-3 that triggers t-1 again, then we need to worry about issues is like termination, both for cycles and self-triggering. We can also have a case where a trigger has multiple actions, and each one of those actions individually activates other triggers. So, we start getting a nested behavior in the trigger activation. So again, all of these need to be both defined carefully and understood carefully when one creates triggers, so that one knows how one will behave in practice. Another issue that's really more about trigger design is exactly how to write one's triggers when conditions are involved. Sometimes it's possible to put a condition either as part of the action or as part of the when clause in a trigger. Now, certain trigger languages are limited in what they can say on the when, and then, again we wouldn't have a choice. But sometimes we do have choice, and it could effect actually the efficiency of trigger execution, depending where we put the condition. Finally, I'll mention again that implementations do vary significantly especially across these tricky issues. Now, most of these issues are actually going to be discussed further in the demo. But the demo that I'm going to give is going to use Sequel Lite, which only has row-level triggers, so let me give one more example that talks about the difference between row-level and statement-level triggers, since I won't be able to show that in the demo. This example is completely contrived to show a few issues. Let's suppose that we have a table T and it has two attributes. Attribute K is a key for the table, and attribute V is a value. And we're going to have a trigger that's going to be activated when we perform insertions on T. And let's suppose that we perform a large number of insertions or at least a few insertions and it's going to be a role level trigger. So we're going to execute the trigger once for each row that's inserted. So, as a reminder, when we have a row-level trigger, we can refer both to the specific tuple that's being processed for one activation of the trigger, and we can also refer to the entire set of changes that were made by the modification command that caused the trigger to be activated. So in this trigger we're going to use NR to refer to the current inserted row that we are processing and NT to refer to the entire set of inserted rows. Okay, so what is this trigger doing? It's going to process one inserted row at a time. And when it processes that row, it's going to check whether the current average value of 'v' in table 'T', so that's the current average value, is less than the average of the inserted rows. Now one thing I want to say is that this value is stable, so even if we modify T, NT doesn't change. NT is always set to the set of inserted tuples, ok? So we check whether T's average is less than the NT average, and if it is, then we're going to modify the update that modified the tuple that was inserted. So we're going to update T and we're going to set the value to be V+10 for the tuple that we're currently processing, in other words, the tuple whose key is in new row. Ok, this is really, really tricky. I'm trying to demonstrate a bunch of things here. So just let's back off and think again for a minute about what happened. We inserted a whole bunch of tuples. After we inserted those tuples, we first determined what the average value of the inserted tuples. That's this average value here. Let's say it's 25. Then we're going to for each inserted tuple, check, is the current average and T, less than 25. If it is, we're going to update that tuple that was inserted to be 10 greater, and that's going to be end of that activation. So there's a couple of things that I wanted to point out specifically with this trigger. One is that there is no statement-level equivalent of this trigger. If we try to write this without the four each row, we can never get the same behavior because what we're doing is looking at each row one at a time and deciding whether to increase its value. So we might increase the value for some subset of the rows, but not all of them, and it would not be possible to do that with a statement level trigger. The second thing that this triggers shows is the potential to have a non-deterministic final state. Because we're gonna again increase the value of the inserted tuples until the average exceeds a certain threshold and then we'll stop in updating those values or increasing those values. So the subset of tuples whose values are increased, is determined by the order in which the trigger processes the set of inserted couples. Ok, so this trigger is really really complicated. Now of course nobody is going to ever write a trigger that looks exactly like this. Part of the point was to show a bunch of different features, and show a bunch of different subtleties. Specifically in the context of row level versus statement level, because we aren't able to make that contrast with the demonstration that we're going to give. So, to conclude, triggers are event condition action rules that are used used to move monitoring logic from the application to the database, and to enforce complex constraints, potentially with automatic repair, and the implementations vary significantly.