Formatting MySQL with SQLinForm
MySQL is one of the most widely used databases in the world, and it has been part of SQLinForm since day one. Formatting MySQL correctly is more challenging than it might appear — here is what makes it complex.
Challenges Formatting MySQL SQL
The DELIMITER keyword
The DELIMITER keyword is unique to MySQL client tools and is not standard SQL. SQLinForm ignores DELIMITER declarations — it is not parsed or tracked. This means scripts that use a custom delimiter like // should have the DELIMITER lines removed before formatting, leaving only the standard SQL and stored procedure bodies. In practice, most developers format individual procedures rather than full client scripts, so this is rarely an issue.
-- Format this (without the DELIMITER lines):
CREATE PROCEDURE update_stock(IN product_id INT,
IN qty INT)
BEGIN
UPDATE products
SET stock = stock - qty
WHERE id = product_id;
END
BEGIN…END blocks without semicolons at the procedure level
In MySQL stored procedures, the outermost BEGIN…END that wraps the procedure body is not terminated with a semicolon. Only the inner statements are. This asymmetry means the formatter cannot treat all BEGIN…END pairs the same way — it requires careful context awareness to indent correctly.
CREATE PROCEDURE example()
BEGIN -- no semicolon here
SET @x = 1; -- semicolon required
SELECT @x; -- semicolon required
END -- no semicolon here either
DECLARE HANDLER and nested BEGIN blocks
Exception handling in MySQL uses DECLARE CONTINUE HANDLER and DECLARE EXIT HANDLER, which can appear inside BEGIN blocks alongside regular statements. Determining the correct indentation level for these constructs — especially when they contain their own BEGIN…END blocks — requires understanding the nesting hierarchy rather than just matching keywords.
CREATE PROCEDURE safe_insert(IN val INT)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
SELECT 'Error occurred' AS message;
END;
START TRANSACTION;
INSERT INTO log_table VALUES (val, NOW());
COMMIT;
END
CASE in procedural vs. query context
MySQL’s CASE statement appears in two very different contexts: as a flow control statement inside stored procedures (which ends with END CASE) and as a CASE expression inside queries (which ends with END). The formatter must determine from context which form is being used to apply the correct indentation and line break logic.
-- CASE as flow control (stored procedure) -- ends with END CASE
CASE status
WHEN 1 THEN SET label = 'Active';
WHEN 2 THEN SET label = 'Inactive';
ELSE SET label = 'Unknown';
END CASE;
-- CASE as expression (query) -- ends with END
SELECT CASE status
WHEN 1 THEN 'Active'
WHEN 2 THEN 'Inactive'
ELSE 'Unknown'
END AS label
FROM customers;
SIGNAL and RESIGNAL
The SIGNAL and RESIGNAL statements for error handling have a multi-part syntax with SET clauses. Formatting them consistently — especially across complex exception handler blocks — requires careful handling to avoid misaligned continuation lines.
CREATE PROCEDURE check_age(IN age INT)
BEGIN
IF age < 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Age cannot be negative',
MYSQL_ERRNO = 1001;
END IF;
END
Mixed DDL and DML in migration scripts
Real-world MySQL scripts often mix DDL (CREATE TABLE, ALTER TABLE, DROP TABLE) with DML and procedural code. Recognizing statement boundaries without semicolons between DDL blocks, especially when those blocks appear inside IF NOT EXISTS guards, is one of the harder challenges in MySQL formatting.
DROP TABLE IF EXISTS orders_archive;
CREATE TABLE orders_archive (
id INT NOT NULL,
order_date DATE NOT NULL,
amount DECIMAL(10,2)
);
INSERT INTO orders_archive
SELECT id,
order_date,
amount
FROM orders
WHERE order_date < '2023-01-01';
About SQLinForm: SQLinForm is a SQL formatter supporting 25+ database dialects with 200+ formatting options — available for VS Code, SSMS, JetBrains, Notepad++, DBeaver, and as an online formatter. Your SQL never leaves your machine.