Skip to content

MySQL - Create Trigger

In modern database systems, triggers are powerful tools that automate actions in response to specific events. Think of them as ‘event listeners’ for your database tables. When an event like an INSERT, UPDATE, or DELETE operation occurs on a table, a trigger can automatically execute a predefined set of SQL statements. This is invaluable for maintaining data integrity, creating audit trails, or enforcing complex business rules that cannot be handled by simple constraints.

MySQL triggers are stored programs associated with a table. They are invoked automatically, meaning you don’t call them directly in your application code. They operate in response to Data Manipulation Language (DML) events:

  • INSERT: A new row is added to the table.
  • UPDATE: An existing row is modified.
  • DELETE: A row is removed from the table.

To create a trigger, you use the CREATE TRIGGER statement. Let’s break down its modern syntax and components.

CREATE TRIGGER trigger_name
[BEFORE | AFTER] [INSERT | UPDATE | DELETE]
ON table_name FOR EACH ROW
BEGIN
-- Trigger logic goes here
END;

Key Components:

  • trigger_name: A unique name for your trigger within the schema.
  • BEFORE | AFTER: The trigger timing. BEFORE triggers fire before the event’s action is performed, allowing you to modify the incoming data. AFTER triggers fire after the action is completed, useful for logging or cascading actions.
  • INSERT | UPDATE | DELETE: The DML event that activates the trigger.
  • table_name: The name of the table the trigger is associated with.
  • FOR EACH ROW: This clause specifies that the trigger body will execute for each individual row affected by the DML statement.
  • BEGIN…END: This block contains the SQL statements that make up the trigger’s logic. For simple, single-statement triggers, this block is optional.

Inside the trigger body, you can use the special aliases NEW and OLD to access column values of the rows affected. NEW refers to the new row data (in INSERT and UPDATE), while OLD refers to the existing row data before the change (in UPDATE and DELETE).

Let’s create a practical trigger. We’ll have a products table and want to ensure that no product can be inserted or updated with a negative price.

First, define a modern products table:

CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
price DECIMAL(10, 2) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Now, let’s create a BEFORE INSERT trigger to validate the price. We use a BEFORE trigger so we can correct the data before it’s saved.

-- Note: The DELIMITER command is used in clients like the MySQL command line
-- to change the statement delimiter from ; to //, allowing the ; within the
-- trigger body to be interpreted correctly.
DELIMITER //
CREATE TRIGGER before_product_insert
BEFORE INSERT ON products FOR EACH ROW
BEGIN
IF NEW.price < 0 THEN
-- Instead of failing, we'll set the price to 0 as a business rule.
-- In other scenarios, you might want to signal an error.
SET NEW.price = 0;
END IF;
END//
DELIMITER ;

Let’s test our trigger by attempting to insert a product with a negative price.

INSERT INTO products (name, price) VALUES
('Laptop', 1200.00),
('Mouse', -25.00), -- This value should be corrected by the trigger
('Keyboard', 75.00);

Now, query the products table to see the result:

SELECT * FROM products;

The output will show that the trigger intercepted the negative value and corrected it:

idnamepricecreated_at
1Laptop1200.00YYYY-MM-DD HH:MM:SS
2Mouse0.00YYYY-MM-DD HH:MM:SS
3Keyboard75.00YYYY-MM-DD HH:MM:SS

While triggers are often managed by DBAs via migration scripts, you might need to create them programmatically. Here’s how to do it using modern, secure practices in various languages. A key principle is using environment variables for credentials and robust error handling.

NodeJS
Python
Java
PHP
This example uses `mysql2/promise` with `async/await` for clean, asynchronous code. Use a `.env` file for credentials (e.g., `DB_HOST=localhost`).
// main.js
const mysql = require('mysql2/promise');
require('dotenv').config(); // Use 'npm install dotenv'
const triggerSQL = `
CREATE TRIGGER before_product_insert
BEFORE INSERT ON products FOR EACH ROW
BEGIN
IF NEW.price < 0 THEN
SET NEW.price = 0;
END IF;
END
`;
async function main() {
let connection;
try {
connection = await mysql.createConnection({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME
});
console.log('Dropping trigger if it exists...');
await connection.execute('DROP TRIGGER IF EXISTS before_product_insert');
console.log('Creating new trigger...');
await connection.execute(triggerSQL);
console.log('Trigger created successfully!');
} catch (error) {
console.error('An error occurred:', error.message);
} finally {
if (connection) {
await connection.end();
console.log('Connection closed.');
}
}
}
main();
This Python example uses `mysql-connector-python` with context managers (`with`) for automatic connection and cursor management, which is a best practice.
# main.py
import mysql.connector
import os # For environment variables
# Best practice: Load credentials from environment variables
config = {
'user': os.getenv('DB_USER', 'root'),
'password': os.getenv('DB_PASSWORD', 'password'),
'host': os.getenv('DB_HOST', '127.0.0.1'),
'database': os.getenv('DB_NAME', 'test_db'),
}
trigger_sql = """
CREATE TRIGGER before_product_insert
BEFORE INSERT ON products FOR EACH ROW
BEGIN
IF NEW.price < 0 THEN
SET NEW.price = 0;
END IF;
END
"""
try:
with mysql.connector.connect(**config) as connection:
print("Connected to MySQL database...")
with connection.cursor() as cursor:
print("Dropping trigger if it exists...")
cursor.execute("DROP TRIGGER IF EXISTS before_product_insert")
print("Creating new trigger...")
cursor.execute(trigger_sql)
connection.commit()
print("Trigger 'before_product_insert' created successfully.")
except mysql.connector.Error as err:
print(f"Error: {err}")
finally:
print("Script finished.")
This Java example uses modern try-with-resources for automatic resource management and loads credentials from a properties file or environment variables for security.
// TriggerManager.java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.sql.SQLException;
public class TriggerManager {
// Load credentials securely, e.g., from environment variables or a config file
private static final String DB_URL = "jdbc:mysql://localhost:3306/test_db";
private static final String DB_USER = "root";
private static final String DB_PASSWORD = "password";
public static void main(String[] args) {
String triggerSQL = "CREATE TRIGGER before_product_insert " +
"BEFORE INSERT ON products FOR EACH ROW " +
"BEGIN " +
" IF NEW.price < 0 THEN " +
" SET NEW.price = 0; " +
" END IF; " +
"END";
// try-with-resources ensures the connection and statement are closed automatically
try (Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
Statement stmt = conn.createStatement()) {
System.out.println("Connected to the database.");
System.out.println("Dropping trigger if it exists...");
stmt.execute("DROP TRIGGER IF EXISTS before_product_insert");
System.out.println("Creating new trigger...");
stmt.execute(triggerSQL);
System.out.println("Trigger created successfully!");
} catch (SQLException e) {
System.err.println("SQL Error: " + e.getMessage());
e.printStackTrace();
}
}
}
This PHP example uses the modern `mysqli` object-oriented interface with proper error checking.
<?php
// config.php
// Best practice: store credentials outside of your web root using environment variables.
define('DB_HOST', getenv('DB_HOST') ?: 'localhost');
define('DB_USER', getenv('DB_USER') ?: 'root');
define('DB_PASS', getenv('DB_PASSWORD') ?: 'password');
define('DB_NAME', getenv('DB_NAME') ?: 'test_db');
// main.php
require_once 'config.php';
// Use object-oriented style with error reporting
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
try {
$mysqli = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
echo "Connected successfully!\n";
$dropTriggerSQL = "DROP TRIGGER IF EXISTS before_product_insert";
$createTriggerSQL = "
CREATE TRIGGER before_product_insert
BEFORE INSERT ON products FOR EACH ROW
BEGIN
IF NEW.price < 0 THEN
SET NEW.price = 0;
END IF;
END;
";
echo "Dropping existing trigger (if any)...\n";
$mysqli->query($dropTriggerSQL);
echo "Creating new trigger...\n";
$mysqli->query($createTriggerSQL);
echo "Trigger created successfully!\n";
} catch (mysqli_sql_exception $e) {
// Catch connection or query errors
error_log("MySQL Error: " . $e->getMessage());
die("Database operation failed. Check server logs.");
} finally {
if (isset($mysqli)) {
$mysqli->close();
echo "Connection closed.\n";
}
}