Which views can you write to, and which cannot?
The rule is about the shape of the view. A view that maps rows one-to-one onto rows of a single base table is a candidate for writing: a simple projection of some columns of one table, with no aggregation, no join, no DISTINCT, no GROUP BY, no set operation and no window function. A view whose rows do not correspond one-to-one to base rows cannot be written to.
CREATE TABLE customers (
id INTEGER PRIMARY KEY,
name VARCHAR(120) NOT NULL,
email VARCHAR(200) NOT NULL,
internal_note TEXT
);
INSERT INTO customers (id, name, email) VALUES
(1, 'Ada Lovelace', '[email protected]'),
(2, 'Grace Hopper', '[email protected]'),
(3, 'Alan Turing', '[email protected]');
-- A simple projection of one table: a candidate for writing.
CREATE VIEW customer_directory AS
SELECT id, name, email FROM customers;
-- An aggregate: one group row stands for many base rows, so it cannot be written.
CREATE VIEW customer_name_counts AS
SELECT name, COUNT(*) AS rows_with_name
FROM customers
GROUP BY name;
SELECT name FROM sqlite_master WHERE type = 'view' ORDER BY name;Two rows come back: customer_directory and customer_name_counts. Nothing in the catalogue flags which one is writable — you have to read the definition and see the shape.
The reason an aggregate blocks a write is fundamental rather than a limitation. A group row represents many base rows, so there is no single row for the engine to write to, and choosing which of the hundred rows in a group to update would be arbitrary. The same reasoning applies to a join, where the output row is a combination of rows from two tables.
There is an important dialect difference here. PostgreSQL, MySQL and SQL Server treat a simple projection of one table as automatically updatable — you write to the view and the base table changes. SQLite does not: its views are always read-only, and to make one writable you attach an INSTEAD OF trigger that performs the write against the base table instead. The next answer shows that pattern, and it is worth knowing because the same trigger also works on PostgreSQL and MySQL when you want to add validation logic to the write.