skip to content

A view joins a customers table to an orders table, and an application tries to UPDATE a column through it. What is a key-preserved table, and why do engines use that concept to decide whether the update is allowed?

level: seniorimportance: should knowfreq 32%

answer

  1. key-preserved = each base row appears ≤ once
  2. join on the other side's PK/unique key
  3. parent is duplicated → not key-preserved
  4. proved from constraints, not from data
  5. engine support varies; trigger is the portable route

basics

~20 s

A table is key-preserved in a join view when its primary key stays unique in the view result — each of its rows appears at most once. Only key-preserved tables can be updated through the view, because only then does a view row identify exactly one base row.

solid answer

~60 s

In a join view, one base row can appear many times in the result. If the orders side joins to customers on customer_id and customer_id is the customers primary key, then each order appears exactly once: orders is **key-preserved**, its primary key is still a key of the view. Customers is not — a customer with five orders appears five times, so its key is no longer unique in the view. Engines that permit writes through join views allow modifying columns of key-preserved tables only. Updating an orders column resolves to one row; updating a customers column through the view would resolve to one row via five different view rows, and a single statement could try to set it to five different values. Deletes have the same logic. Crucially, key preservation is a property the engine must *prove* from declared constraints — it depends on a primary or unique key existing on the join column. Drop that unique index and a previously updatable join view stops accepting writes. Support also varies by engine, so treat write-through joins as non-portable.

code

sql · 11 lines
sql
CREATE VIEW order_details AS
SELECT o.order_id, o.total, o.customer_id, c.name AS customer_name
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id;
-- customers.customer_id is the PRIMARY KEY, so each order appears once

UPDATE order_details SET total = 99 WHERE order_id = 7;
-- allowed where join-view writes are supported: targets key-preserved orders

UPDATE order_details SET customer_name = 'X' WHERE order_id = 7;
-- rejected: customers is not key-preserved in this view

go deeper

for a junior

It is enough to know that joining tables usually stops a view from accepting writes because a row can appear more than once.

for a middle

Define key preservation and identify which side of a one-to-many join qualifies, and why the other side's updates would be ambiguous.

for a senior

Stress that the property is proved from declared constraints, so schema changes can silently revoke updatability, and that engine support differs enough to make this a portability risk.

for a principal

Rule on whether the system should depend on inferred join-view writes at all; if it must, make the underlying unique constraints an explicit, tested part of the contract.

## Why joins are the hard case Filters and column projections keep row identity intact, so the engine can rewrite writes trivially. A join can multiply rows: a parent row matching N child rows appears N times in the result. Once a base row appears more than once, an UPDATE through the view is ambiguous — the same base row is reachable through several view rows, and one statement could assign it conflicting values. Engines need a decision procedure. That procedure is key preservation. ## The definition A base table is **key-preserved** in a view if every key of that table remains a key of the view result — equivalently, each of its rows can appear at most once in the view. The concept originated in Oracle's terminology but the underlying reasoning is what every engine that supports join-view writes implements, under one name or another. The usual test: join child to parent on the parent's primary or unique key. Because at most one parent row matches each child row, the child rows are not multiplied, and the child is key-preserved. The parent is not: it is duplicated once per matching child. ``` orders o JOIN customers c ON o.customer_id = c.customer_id ``` Here `customers.customer_id` is the primary key, so each order row survives exactly once — `orders` is key-preserved. `customers` is not. Note what this hinges on: the *declared* uniqueness of `customers.customer_id`. The engine reasons from catalog metadata, not from the data that happens to be present. If the join column has no declared primary key or unique constraint, the engine must assume duplicates are possible and neither side is key-preserved, even when today's data is in fact unique. ## What follows for DML On engines that support it: - **UPDATE**: you may set columns belonging to a key-preserved table. Attempting to set a column of a non-key-preserved table raises an error. - **DELETE**: allowed when exactly one table in the view is key-preserved, so the target is unambiguous; if two tables are key-preserved (a one-to-one join on both keys), the engine cannot choose and refuses. - **INSERT**: strictest. Only columns of a single key-preserved table may be supplied, and every mandatory column of that table must be reachable. Inserting into two tables through one view is never inferred. Outer joins add their own wrinkle: on the null-supplying side, rows may not exist at all, so writes to that side make no sense and are refused. ## Portability This is one of the least portable corners of SQL. Oracle implements key preservation explicitly and documents it. MySQL permits updates to multi-table views under related conditions but forbids inserts into joins. PostgreSQL's automatic updatability requires a single base relation in the FROM clause, so join views are simply not auto-updatable there and need a trigger or rule. Never carry an assumption from one engine to another; verify against the target engine, and prefer not to depend on join-view writes at all in a system that might be ported. ## The operational trap Key preservation is inferred from constraints, which makes it fragile in a way schema changes do not advertise. Dropping a unique index during a maintenance window, replacing a primary key, or adding a second matching row-set can silently move a view from updatable to non-updatable. Nothing in the view definition changed; nothing in the application changed; the writes start failing. When a system relies on join-view writes, the uniqueness constraints those views depend on are load-bearing and should be treated as part of the contract — documented, and covered by a test that attempts the write and rolls back. ## The alternative The pragmatic senior answer is usually to stop relying on inference. Either have the application write to the base tables directly — the join view stays a read surface — or attach an INSTEAD OF trigger that states explicitly which table each written column belongs to. The trigger costs code and must handle multi-row statements, but it is portable, explicit, reviewable, and immune to the engine's inference rules changing under you. ## Interview framing Define key preservation in one sentence (each row of that table appears at most once in the view), give the customers/orders example, explain the ambiguity that motivates the rule, then land the two senior points: the property is proved from declared constraints and therefore breaks with schema changes, and engine support varies enough that join-view writes are a portability liability.

  • Why does the engine decide key preservation from declared constraints rather than from the data currently in the tables?
    Because the decision must hold for every future execution of the statement, not just the current contents. Data that happens to be unique today can gain duplicates a second later, which would turn a valid rewrite into a silent multi-row corruption. Declared primary and unique keys are the only guarantees the engine can rely on, so without one it must assume duplication is possible.
  • How would you make a joined view writable in an engine that does not support join-view DML at all?
    Attach an INSTEAD OF trigger to the view and implement the mapping explicitly: decide which base table each written column belongs to, issue the corresponding statements, and handle multi-row statements and constraint failures. It is more code than relying on inference, but it is portable, explicit in review, and does not silently break when a unique constraint changes.

Think of a class roster joined to teachers. Each student line names one teacher, so students are still uniquely identified — you can correct a student's details from the roster. But a teacher's name is printed on thirty lines; correcting it thirty times in one pass, possibly to thirty different values, is not something the printer can resolve for you.

saying these in an interview costs you the question

  • Saying a join view is updatable as long as the join is an inner join
  • Claiming both sides of a one-to-many join can be updated through the view
  • Assuming key preservation is inferred from the data currently present
  • Believing the rule is identical across all relational engines
  • Ignoring that dropping a unique constraint can silently revoke a view's updatability

context