This error occurs when you try to create or update a view with the WITH CHECK OPTION clause, but the view definition makes it non-updatable. Views with aggregate functions, UNION, GROUP BY, or non-mergeable joins cannot use CHECK OPTION.
MySQL views can be updatable, allowing INSERT, UPDATE, and DELETE operations through them. The WITH CHECK OPTION clause adds an additional constraintโit ensures that rows modified through the view remain visible after the modification (they still match the view's WHERE clause). However, ERROR 1368 occurs when you attempt to add a CHECK OPTION to a view that MySQL cannot update. This happens because the view's SELECT statement contains operations that break the one-to-one relationship between view rows and base table rows. The error message format is: "ERROR 1368 (HY000): CHECK OPTION on non-updatable view 'database.view_name'" This is a view definition error, not a query error. It happens at CREATE or ALTER VIEW time, not when you query the view.
First, identify what's preventing the view from being updatable. Use INFORMATION_SCHEMA to check:
SELECT TABLE_NAME, IS_UPDATABLE
FROM INFORMATION_SCHEMA.VIEWS
WHERE TABLE_SCHEMA = 'your_database_name'
AND TABLE_NAME = 'your_view_name';If the view shows IS_UPDATABLE = NO, it cannot use CHECK OPTION.
Common non-updatable constructs in the view definition:
-- Non-updatable: has aggregate function
CREATE VIEW non_updatable_view AS
SELECT department, COUNT(*) as emp_count, SUM(salary) as total_salary
FROM employees
GROUP BY department;
-- Non-updatable: has DISTINCT
CREATE VIEW distinct_view AS
SELECT DISTINCT city
FROM employees;
-- Non-updatable: has UNION
CREATE VIEW union_view AS
SELECT id, name FROM employees
UNION
SELECT id, name FROM contractors;
-- Non-updatable: has subquery in SELECT
CREATE VIEW subquery_view AS
SELECT id, name, (SELECT COUNT(*) FROM orders WHERE orders.emp_id = employees.id) as order_count
FROM employees;The simplest solution: if you don't actually need to update through the view, remove the CHECK OPTION clause.
Before (fails):
CREATE VIEW sales_summary AS
SELECT
department,
COUNT(*) as total_sales,
SUM(amount) as total_revenue
FROM orders
GROUP BY department
WITH CHECK OPTION; -- ERROR 1368!After (works):
CREATE VIEW sales_summary AS
SELECT
department,
COUNT(*) as total_sales,
SUM(amount) as total_revenue
FROM orders
GROUP BY department;
-- No WITH CHECK OPTION needed since this is for reporting onlyThis approach is best for reporting views or complex analytical queries that users shouldn't modify through the view anyway.
If you need the view to be updatable, remove the problematic constructs from the SELECT statement.
Remove GROUP BY and aggregate functions:
-- Non-updatable (has GROUP BY and COUNT)
CREATE VIEW order_summary AS
SELECT product_id, COUNT(*) as num_orders
FROM orders
GROUP BY product_id
WITH CHECK OPTION; -- ERROR 1368
-- Solution: Create an updatable view instead
CREATE VIEW active_orders AS
SELECT id, product_id, amount, status
FROM orders
WHERE status = 'active'
WITH CHECK OPTION; -- Now works!Remove DISTINCT:
-- Non-updatable
CREATE VIEW unique_cities AS
SELECT DISTINCT city
FROM employees
WITH CHECK OPTION; -- ERROR 1368
-- Solution: Select all columns (may need additional column for identity)
CREATE VIEW employee_locations AS
SELECT id, city
FROM employees
WITH CHECK OPTION; -- WorksAvoid subqueries in SELECT:
-- Non-updatable (subquery in SELECT)
CREATE VIEW emp_with_count AS
SELECT id, name, (SELECT COUNT(*) FROM orders WHERE orders.emp_id = employees.id) as order_count
FROM employees
WITH CHECK OPTION; -- ERROR 1368
-- Solution: If you need this data, query the tables separately
-- Or create a trigger to maintain a denormalized columnOuter joins can prevent views from being updatable when updating certain columns. Use INNER JOIN when possible.
Non-updatable (outer join):
CREATE VIEW employee_departments AS
SELECT e.id, e.name, d.dept_name
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.id
WITH CHECK OPTION; -- May fail depending on structureUpdatable (inner join):
CREATE VIEW employee_departments AS
SELECT e.id, e.name, d.dept_name
FROM employees e
INNER JOIN departments d ON e.dept_id = d.id
WITH CHECK OPTION; -- More likely to workImportant: You can only update columns from the main table in the FROM clause when using joins. You cannot update columns from joined tables through the view.
MySQL views can use different algorithms: MERGE or TEMPTABLE. The TEMPTABLE algorithm creates temporary non-updatable views.
Non-updatable (TEMPTABLE algorithm):
CREATE ALGORITHM = TEMPTABLE VIEW my_view AS
SELECT * FROM base_table
WITH CHECK OPTION; -- ERROR 1368Updatable (MERGE algorithm):
CREATE ALGORITHM = MERGE VIEW my_view AS
SELECT * FROM base_table
WHERE active = 1
WITH CHECK OPTION; -- WorksOr let MySQL decide (recommended):
CREATE VIEW my_view AS
SELECT * FROM base_table
WHERE active = 1
WITH CHECK OPTION; -- MySQL automatically uses MERGE if possibleCheck which algorithm your view is using:
SELECT TABLE_NAME, DEFINER, SECURITY_TYPE, VIEW_DEFINITION
FROM INFORMATION_SCHEMA.VIEWS
WHERE TABLE_SCHEMA = 'your_database'
AND TABLE_NAME = 'your_view_name';If you need row-level validation but cannot make the view updatable, use INSTEAD OF triggers instead.
Trigger approach (supports complex logic):
-- Create the base view (without CHECK OPTION)
CREATE VIEW order_details_view AS
SELECT id, product_id, quantity, price, created_at
FROM order_items;
-- Create INSTEAD OF INSERT trigger
DELIMITER //
CREATE TRIGGER order_items_insert
INSTEAD OF INSERT ON order_details_view
FOR EACH ROW
BEGIN
-- Validation logic
IF NEW.quantity <= 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Quantity must be greater than 0';
END IF;
IF NEW.price < 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Price cannot be negative';
END IF;
-- Insert the validated data
INSERT INTO order_items (product_id, quantity, price, created_at)
VALUES (NEW.product_id, NEW.quantity, NEW.price, NEW.created_at);
END //
DELIMITER ;
-- Create INSTEAD OF UPDATE trigger
DELIMITER //
CREATE TRIGGER order_items_update
INSTEAD OF UPDATE ON order_details_view
FOR EACH ROW
BEGIN
IF NEW.quantity <= 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Quantity must be greater than 0';
END IF;
UPDATE order_items
SET quantity = NEW.quantity, price = NEW.price
WHERE id = NEW.id;
END //
DELIMITER ;Now you can INSERT and UPDATE through the view, with custom validation logic.
After making changes, test that the view works correctly and can be updated.
Test the view creation:
-- Drop the old view if it exists
DROP VIEW IF EXISTS your_view_name;
-- Create the corrected view
CREATE VIEW your_view_name AS
SELECT id, name, status
FROM base_table
WHERE status != 'deleted'
WITH CHECK OPTION;
-- Verify updatability
SELECT IS_UPDATABLE
FROM INFORMATION_SCHEMA.VIEWS
WHERE TABLE_NAME = 'your_view_name';
-- Should return 'YES'Test INSERT through the view:
INSERT INTO your_view_name (id, name, status)
VALUES (1, 'Test Record', 'active');Test UPDATE through the view:
UPDATE your_view_name
SET name = 'Updated Name'
WHERE id = 1;Test that CHECK OPTION is enforced (should fail):
-- This should fail because it violates the WHERE clause
UPDATE your_view_name
SET status = 'deleted'
WHERE id = 1;
-- With CHECK OPTION, this fails because the row would become invisibleIf all tests pass, the view is now properly configured.
### Understanding View Updatability
A MySQL view is updatable only if there's a one-to-one relationship between rows in the view and rows in the underlying table(s). This is a fundamental requirement because UPDATE and DELETE operations need to identify exactly which base table rows to modify.
Non-updatable constructs and why:
1. Aggregate functions (COUNT, SUM, etc.) - Multiple rows collapse into one result row. Can't update a collapsed row back to multiple rows.
2. GROUP BY clause - Multiple base rows map to one group. The reverse mapping is ambiguous.
3. HAVING clause - Further filters groups. Violates the one-to-one mapping.
4. DISTINCT - Removes duplicate rows. The distinct result might represent multiple base rows.
5. UNION - Combines results from multiple queries. Can't determine which source table to update.
6. Subqueries in SELECT - Adds computed columns that don't map to base table columns.
7. TEMPTABLE algorithm - Creates temporary results that aren't directly connected to base tables.
### LOCAL vs CASCADED CHECK OPTION
When a view is based on another view, CHECK OPTION behavior differs:
-- View 1: Basic table view
CREATE VIEW v1 AS
SELECT * FROM employees
WHERE department = 'Sales'
WITH CHECK OPTION;
-- View 2: View based on view
CREATE VIEW v2 AS
SELECT * FROM v1
WHERE salary > 50000
WITH LOCAL CHECK OPTION; -- Only checks v2's WHERE, not v1's- WITH LOCAL CHECK OPTION: Only enforces the current view's WHERE clause
- WITH CASCADED CHECK OPTION (default): Enforces all parent view conditions too
CREATE VIEW v2 AS
SELECT * FROM v1
WHERE salary > 50000
WITH CASCADED CHECK OPTION; -- Checks both v2 and v1's conditions### Performance Considerations
- Updatable views with CHECK OPTION are slightly slower than direct table updates because MySQL must re-check the WHERE clause after modifications
- Non-updatable reporting views have no performance penalty
- For high-frequency updates, consider application-level validation instead of CHECK OPTION
### Migration from ERROR 1368
If you have existing code that relies on updatable views with CHECK OPTION:
1. Identify which columns actually need updates - Don't include unnecessary columns in the view
2. Consider if aggregate views need updates - Usually they're read-only; use base table updates instead
3. Split into separate views - Keep read-only aggregate views separate from updatable detail views
4. Use stored procedures - For complex update logic that involves multiple tables
Example migration:
-- Before: Trying to update aggregated data
CREATE VIEW monthly_sales AS
SELECT product_id, COUNT(*) as num_sales, SUM(amount) as total
FROM sales
GROUP BY product_id
WITH CHECK OPTION; -- ERROR 1368
-- After: Separate read-only reporting view and updatable transaction view
CREATE VIEW sales_transactions AS
SELECT id, product_id, amount, sale_date, created_at
FROM sales
WHERE created_at >= CURRENT_DATE - INTERVAL 30 DAY
WITH CHECK OPTION;
-- Reporting view (read-only)
CREATE VIEW monthly_sales_report AS
SELECT product_id, COUNT(*) as num_sales, SUM(amount) as total
FROM sales
WHERE created_at >= CURRENT_DATE - INTERVAL 30 DAY
GROUP BY product_id;
-- Updates go through the transaction view or directly to the tableERROR 1064: You have an error in your SQL syntax
How to fix "ERROR 1064: You have an error in your SQL syntax" in MySQL
ERROR 1054: Unknown column in field list
Unknown column in field list
ER_WINDOW_RANGE_FRAME_NUMERIC_TYPE (3589): RANGE frame requires numeric ORDER BY expression
RANGE frame requires numeric ORDER BY expression in MySQL window functions
CR_ALREADY_CONNECTED (2058): Handle already connected
How to fix "CR_ALREADY_CONNECTED (2058): Handle already connected" in MySQL
ER_WINDOW_DUPLICATE_NAME (3591): Duplicate window name
How to fix ER_WINDOW_DUPLICATE_NAME (3591) in MySQL window functions