Top 5 Advanced MySQL Features, Part 2

Triggers

Triggers are special types of stored procedures that are automatically executed in response to specific events, such as a table update or insertion. They can be used for tasks such as enforcing data integrity or maintaining a history of changes

If you’re a beginner in this field, you may have heard about the mysterious concept of “triggers”. No, we’re not talking about the type you pull to shoot someone, but rather triggers in the database world. Triggers are like having a little helper who keeps watch over your database, and can perform tasks for you automatically. We’re going to dive into the basics of MySQL triggers, and show you how to use them to make your database management life easier and more efficient.

Some example use cases:

  • Audit Trail: Keep track of changes made to a database by automatically recording the time, user, and action taken in a separate table using triggers.
  • Data Validation: Use triggers to enforce business rules, check for data integrity and reject any invalid data before it is stored in the database.
  • Auto-Updating Fields: Automatically update fields such as “last updated” or “total sales” whenever a change is made to the database, without having to manually update it.
  • Sending Notifications: Automatically send email notifications to specific users or groups when a particular action takes place, such as a new order being placed.
  • Data Synchronization: Automatically synchronize data between multiple databases or tables, ensuring that all changes made in one place are reflected in another.
  • Enforcing Constraints: Use triggers to enforce constraints, such as ensuring that only one active record is allowed for a particular user.
  • Logging: Log all database transactions, such as inserts, updates, and deletes, for auditing and troubleshooting purposes.
  • Preventing Data Loss: Automatically prevent data loss by using triggers to backup data in real-time.
  • Dynamic Calculations: Automatically calculate fields based on other fields, such as calculating the total cost of an order based on the individual item prices.

Audit Trail

  1. Create a table to store the audit trail information, such as the date and time of the change, the user who made the change, and the type of change (insert, update, or delete).
  2. Create the trigger that will automatically insert a new record into the audit trail whenever a change is made to the data in the main table. In this example, we’ll create a trigger for an “orders” table.
CREATE TABLE audit_trail (
  id INT AUTO_INCREMENT PRIMARY KEY,
  date_time DATETIME NOT NULL,
  user VARCHAR(255) NOT NULL,
  action ENUM('insert', 'update', 'delete') NOT NULL,
  record_id INT NOT NULL
);

CREATE TRIGGER audittrail_orders
AFTER INSERT OR UPDATE OR DELETE ON orders
FOR EACH ROW
BEGIN
  DECLARE @user VARCHAR(255);
  DECLARE @action VARCHAR(20);
  DECLARE @record_id INT;

  SET @user = USER();

  IF (INSERTING()) THEN
    SET @action = 'insert';
    SET @record_id = NEW.id;
  ELSEIF (UPDATING()) THEN
    SET @action = 'update';
    SET @record_id = NEW.id;
  ELSE
    SET @action = 'delete';
    SET @record_id = OLD.id;
  END IF;

  INSERT INTO audit_trail (date_time, user, action, record_id)
  VALUES (NOW(), @user, @action, @record_id);
END;

Data Validation

  1. Create the table you want to validate data for. In this example, let’s use a “customers” table.
  2. Create the trigger that will enforce the business rule that the customer’s age must be over 18.
  3. Now, whenever you attempt to insert a new customer record into the “customers” table, the trigger will be executed first, and will automatically reject any records where the customer’s age is less than 18. If a record is rejected, an error message will be displayed, letting you know why the record was not inserted.
CREATE TABLE customers (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(255) NOT NULL,
  email VARCHAR(255) NOT NULL,
  age INT NOT NULL
);

CREATE TRIGGER validate_age
BEFORE INSERT ON customers
FOR EACH ROW
BEGIN
  IF (NEW.age < 18) THEN
    SIGNAL SQLSTATE '45000'
    SET MESSAGE_TEXT = 'Customers must be over 18 years old';
  END IF;
END;

mysql> INSERT INTO customers (name, email, age) VALUES ('John Doe', 'johndoe@example.com', 17);
ERROR 1644 (45000): Customers must be over 18 years old

Auto-Updating Fields

  • Create the table you want to auto-update fields for. In this example, let’s use an “orders” table.
  • Create the trigger that will automatically update the “date_updated” field whenever a change is made to the “orders” table.
  • Now, whenever you insert a new order or update an existing order, the “date_updated” field will automatically be updated to the current date and time.
CREATE TABLE orders (
  id INT AUTO_INCREMENT PRIMARY KEY,
  date_placed DATETIME NOT NULL,
  total_amount DECIMAL(10, 2) NOT NULL,
  date_updated DATETIME NOT NULL
);

CREATE TRIGGER auto_update_date
AFTER INSERT OR UPDATE ON orders
FOR EACH ROW
BEGIN
  SET NEW.date_updated = NOW();
END;

mysql> INSERT INTO orders (date_placed, total_amount) VALUES (NOW(), 100.00);
Query OK, 1 row affected (0.00 sec)

mysql> SELECT * FROM orders;
+----+---------------------+-------------+---------------------+
| id | date_placed         | total_amount | date_updated       |
+----+---------------------+-------------+---------------------+
|  1 | 2021-05-17 13:00:00 |      100.00 | 2021-05-17 13:00:00 |
+----+---------------------+-------------+---------------------+

Notifications

  1. Create the table you want to send notifications for. In this example, let’s use a “orders” table.
  2. Create the “mail_queues” table to store the email notifications.
  3. Create the trigger that will insert a record into the “mail_queues” table whenever the status of an order is changed to “Completed”.
CREATE TABLE orders (
  id INT AUTO_INCREMENT PRIMARY KEY,
  date_placed DATETIME NOT NULL,
  total_amount DECIMAL(10, 2) NOT NULL,
  status ENUM('Pending', 'Processing', 'Completed') NOT NULL
);

CREATE TABLE mail_queues (
  id INT AUTO_INCREMENT PRIMARY KEY,
  order_id INT NOT NULL,
  to_email VARCHAR(255) NOT NULL,
  subject VARCHAR(255) NOT NULL,
  message TEXT NOT NULL,
  status ENUM('Pending', 'Sent') NOT NULL DEFAULT 'Pending',
  date_created DATETIME NOT NULL
);

DELIMITER $$
CREATE TRIGGER send_notification
AFTER UPDATE ON orders
FOR EACH ROW
BEGIN
  IF (OLD.status != 'Completed' AND NEW.status = 'Completed') THEN
    -- insert a record into the mail_queues table
    INSERT INTO mail_queues (order_id, to_email, subject, message, date_created)
    VALUES (NEW.id, 'customer@example.com', 'Order Completed',
            'Your order #' || NEW.id || ' has been completed. Total amount: ' || NEW.total_amount || '.', NOW());
  END IF;
END $$
DELIMITER ;

Data Synchronization

  1. Create the “customers” table in both databases.
  2. Create the trigger in “DB1” to insert the same customer into “DB2” when a new customer is inserted into “DB1”.
-- database DB1
CREATE TABLE customers (
  id INT AUTO_INCREMENT PRIMARY KEY,
  first_name VARCHAR(255) NOT NULL,
  last_name VARCHAR(255) NOT NULL,
  email VARCHAR(255) NOT NULL,
  created_at DATETIME NOT NULL
);

-- database DB2
CREATE TABLE customers (
  id INT AUTO_INCREMENT PRIMARY KEY,
  first_name VARCHAR(255) NOT NULL,
  last_name VARCHAR(255) NOT NULL,
  email VARCHAR(255) NOT NULL,
  created_at DATETIME NOT NULL
);

-- the trigger
DELIMITER $$
CREATE TRIGGER sync_customer
AFTER INSERT ON customers
FOR EACH ROW
BEGIN
  -- insert the same customer into the "customers" table in "DB2"
  INSERT INTO DB2.customers (first_name, last_name, email, created_at)
  VALUES (NEW.first_name, NEW.last_name, NEW.email, NEW.created_at);
END $$
DELIMITER ;

Enforcing Constraints

  1. Create the “orders” table.
  2. Create the trigger to enforce the constraint on the “total” field.
DELIMITER $$
CREATE TRIGGER enforce_total
BEFORE INSERT ON orders
FOR EACH ROW
BEGIN
  -- check if the total value is within the specified range
  IF NEW.total < 10 OR NEW.total > 100 THEN
    -- if the total is outside of the range, raise an error
    SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Total must be between $10 and $100.';
  END IF;
END $$
DELIMITER ;

Logging

  1. Create the “employees” table.
  2. Create a “logs” table to store the changes made to the “employees” table.
  3. Create the trigger to log the changes made to the “employees” table.
CREATE TABLE employees (
  id INT AUTO_INCREMENT PRIMARY KEY,
  first_name VARCHAR(255) NOT NULL,
  last_name VARCHAR(255) NOT NULL,
  email VARCHAR(255) NOT NULL,
  updated_at DATETIME NOT NULL
);

CREATE TABLE logs (
  id INT AUTO_INCREMENT PRIMARY KEY,
  employee_id INT NOT NULL,
  field VARCHAR(255) NOT NULL,
  old_value VARCHAR(255),
  new_value VARCHAR(255),
  created_at DATETIME NOT NULL
);

-- The trigger
DELIMITER $$
CREATE TRIGGER log_employee_changes
AFTER UPDATE ON employees
FOR EACH ROW
BEGIN
  -- check if any of the fields were changed
  IF OLD.first_name != NEW.first_name THEN
    -- insert a log entry for the first name field
    INSERT INTO logs (employee_id, field, old_value, new_value, created_at)
    VALUES (OLD.id, 'first_name', OLD.first_name, NEW.first_name, NOW());
  END IF;

  IF OLD.last_name != NEW.last_name THEN
    -- insert a log entry for the last name field
    INSERT INTO logs (employee_id, field, old_value, new_value, created_at)
    VALUES (OLD.id, 'last_name', OLD.last_name, NEW.last_name, NOW());
  END IF;

  IF OLD.email != NEW.email THEN
    -- insert a log entry for the email field
    INSERT INTO logs (employee_id, field, old_value, new_value, created_at)
    VALUES (OLD.id, 'email', OLD.email, NEW.email, NOW());
  END IF;
END $$
DELIMITER ;

Preventing Data Loss

An example of preventing data loss using a trigger could be when you want to ensure that data is never deleted from a critical table. For example, let’s say you have a “customers” table and you want to prevent any data from being deleted from the table. Here’s how you could set up the trigger.

  1. Create the “customers” table.
  2. Create the trigger to prevent data from being deleted from the “customers” table.
CREATE TABLE customers (
  id INT AUTO_INCREMENT PRIMARY KEY,
  first_name VARCHAR(255) NOT NULL,
  last_name VARCHAR(255) NOT NULL,
  email VARCHAR(255) NOT NULL,
  deleted_at DATETIME
);

-- the trigger
DELIMITER $$
CREATE TRIGGER prevent_customer_deletion
BEFORE DELETE ON customers
FOR EACH ROW
BEGIN
  -- instead of deleting the row, update the deleted_at field
  SET NEW.deleted_at = NOW();
  UPDATE customers SET deleted_at = NOW() WHERE id = OLD.id;
  -- stop the DELETE statement
  SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Deleting customers is not allowed';
END $$
DELIMITER ;

Now, whenever you try to delete a customer from the “customers” table, the trigger will be executed and the DELETE statement will be stopped. Instead of deleting the customer, the trigger will update the “deleted_at” field, marking the customer as deleted. This way, you can ensure that critical data is never lost, even if it has been marked as deleted.

Dynamic Calculations

  1. Create the “orders” and “order_items” tables.
  2. Create the trigger to dynamically calculate the total cost of an order.
CREATE TABLE orders (
  id INT AUTO_INCREMENT PRIMARY KEY,
  customer_id INT NOT NULL,
  total_cost DECIMAL(10,2) NOT NULL,
  created_at DATETIME NOT NULL,
  updated_at DATETIME NOT NULL
);

-- the trigger
DELIMITER $$
CREATE TRIGGER update_order_total_cost
AFTER INSERT ON order_items
FOR EACH ROW
BEGIN
  -- calculate the total cost of the order
  DECLARE total_cost DECIMAL(10,2);
  SET total_cost = (SELECT SUM(cost) FROM order_items WHERE order_id = NEW.order_id);
  -- update the total cost of the order in the orders table
  UPDATE orders SET total_cost = total_cost WHERE id = NEW.order_id;
END $$
DELIMITER ;

Now, whenever a new item is inserted into the “order_items” table, the trigger will be executed and the total cost of the order will be calculated and updated in the “orders” table. This way, you can ensure that the total cost of each order is always up-to-date and accurate.

Related Posts