Views, Procedures & Server-Side Logic
I learn the database objects that live beyond plain tables — views, materialized views, stored procedures, triggers, generated columns, sequences, and temp tables — and when pushing logic into the engine helps or hurts. Interviewers use this area to test whether I understand what the engine can do server-side and can argue the business-logic-in-the-database trade-off.
part ofRelational database conceptsoverview, primer and where to startread it →on this pageshowhide
explore
- Views & Their Use Cases5 questions
- Updatable Views5 questions
- Materialized Views & Refresh Strategies4 questions
- Stored Procedures & Functions6 questions
- Business Logic in the Database: Trade-Offs4 questions
- Trigger Mechanics5 questions
- Trigger Pitfalls5 questions
- Generated & Computed Columns4 questions
- Sequences & Identity Columns4 questions
- Temporary Tables4 questions
- BI Analystroleanchors this topic
- PostgreSQL DBAroleanchors this topic
- SQLskillanchors this topic
- AI & Data Scientistrole
- AI Engineerrole
- Backend Developerrole
- Computer Scienceskill
- Cyber Security Expertrole
- Data Analystrole
- Data Engineerrole
- Forward Deployed Engineerrole
- Full Stack Developerrole
- Java Backend Developerrole
- Kotlin Backend Developerrole
- MLOps Engineerrole
- Machine Learning Engineerrole
- Server-Side Game Developerrole
- Software Architectrole
questions
page 2 of 2How can a view act as an access-control boundary — for example exposing an employees table without its salary and national-ID columns — and what are the limits of relying on that?
basics
~20 sGrant SELECT on a view that projects only the allowed columns and rows, and grant nothing on the base table. Because the view typically executes with its owner's privileges, callers can read exactly what it projects and no more. Limits: it does not stop anyone holding direct base-table rights, and clever predicates can leak information.
For keeping a derived value in a table up to date, when would you use a generated column and when would you fall back to a trigger? What does each guarantee?
basics
~20 sUse a generated column whenever the value is a deterministic function of the same row - the engine guarantees it can never be wrong or bypassed, and there is no procedural code to maintain. Use a trigger only when the derivation needs other rows, other tables, non-deterministic input, or must freeze at insert time.
Your team keeps meaningful business logic in database stored procedures. How do you version, test, review, and roll back that code with the same discipline you apply to application code?
basics
~20 sKeep every procedure body in version control as the source of truth, deploy it through the same ordered migration pipeline as schema changes, test against a real database in CI, keep signatures backward compatible across rolling deploys, and treat rollback as a forward migration re-applying the old body.
While a materialized view is being refreshed, what happens to queries that read it, and how does a concurrent refresh mode (REFRESH MATERIALIZED VIEW CONCURRENTLY and its equivalents) change that?
basics
~20 sA plain refresh typically takes an exclusive lock and rebuilds the view, so readers block for the whole refresh. A concurrent refresh builds the new result separately and merges the differences row by row, letting readers keep querying — at the cost of a slower refresh and a required unique index.
A sequence can be created with a CACHE setting (for example CACHE 1 versus CACHE 1000). Explain what that setting does, and how you would choose it for a high-throughput insert workload — including what happens on a crash and across multiple database instances.
basics
~20 sCACHE n preallocates n values into memory so most allocations avoid touching and persisting shared sequence state. Bigger cache means less contention and fewer writes, but larger gaps after a crash and, across instances, values issued out of global order.
When a stored routine's body is executed repeatedly with different parameter values, how does the database handle execution plans for the statements inside it, and what problem can that cause?
basics
~20 sEngines cache plans for statements inside a routine and reuse them across calls, planning them against the first parameter values seen. If the data is skewed, a plan optimal for one value is terrible for another — parameter sniffing. Remedies: force a per-call replan, use a generic plan, split the routine, or restructure the query.
A query that joins against a set-returning (table-valued) function performs far worse than the equivalent query written against base tables. What is going on inside the optimizer, and what are your options?
basics
~20 sThe optimizer usually cannot see inside the function, so it uses a fixed default row estimate instead of statistics. A wrong cardinality picks the wrong join order and join method — typically nested loops over millions of rows. Fixes: get the function inlined, declare a row estimate, or materialize its output into a temp table with statistics.
A stored procedure that creates a temporary table runs thousands of times a minute, and the database starts showing contention and bloat unrelated to the business tables. What is happening, and how would you fix it?
basics
~20 sEach creation writes system-catalog rows and allocates storage, so at high rates the catalog itself becomes the hot spot: latch or lock contention on metadata, catalog bloat, and constant plan recompilation. Fixes: create the table once per session and reuse it, drop the staging step entirely, or move the intermediate inline.
Inside a trigger body, how do you reference the row's values before and after the change, and how does that differ between row-level and statement-level triggers? Explain what transition tables (REFERENCING OLD TABLE / NEW TABLE) give you.
basics
~20 sRow-level triggers get single-row images, conventionally OLD and NEW: INSERT has only NEW, DELETE only OLD, UPDATE both. Statement-level triggers have no per-row image; transition tables expose the whole set of changed rows as two readable relations you can join and aggregate in one set-based statement.
A view aggregates and joins several tables, so the engine will not accept writes against it. How does an INSTEAD OF trigger let you support writes anyway, and what are the risks of that approach?
basics
~20 sAn INSTEAD OF trigger replaces the engine's automatic rewrite: instead of executing the INSERT, UPDATE or DELETE, the engine runs your code, which issues explicit statements against the base tables. Risks: you own correctness, multi-row semantics, ordering, error handling, and returned row counts.
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?
basics
~20 sA 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.
A reporting query reads from a view that itself reads from three other views, and it is far slower than expected. What problems are typical of deep view-on-view chains, and how would you attack this?
basics
~20 sStacked views accumulate joins and columns nobody downstream needs, and inner aggregations or DISTINCTs block predicate pushdown so the bottom of the stack scans everything. Read the expanded plan, find the fence and the joins contributing nothing, then flatten the query for that consumer.
A proposal moves a CPU-heavy pricing calculation from the application tier into database procedures so it runs next to the data. How would you evaluate that proposal from a capacity and scaling perspective?
basics
~20 sCompare where the work is cheapest to add capacity. Application servers are stateless and scale horizontally for money; the primary database is one writable node, shared by every query, and often licensed per core. Move work down only when it removes far more data movement than the CPU it adds.
How would you choose between refreshing a materialized view on commit of every base-table transaction versus on demand on a schedule, and how do you decide the acceptable staleness window?
basics
~20 sOn-commit refresh keeps the view nearly current but charges every writing transaction and serialises writers on shared aggregate rows. On-demand refresh batches the cost but leaves a staleness window. Choose by the consumer's tolerance for stale data against the write throughput you can afford to lose.
You need a durable audit trail of every change to a handful of core tables. Compare building it with audit triggers, with log-based change data capture that reads the database transaction log, and with a transactional outbox table written by the application — and say when you would choose each.
basics
~20 sTriggers capture every writer atomically with the change but tax every write and roughly double log volume. Log-based capture reads the transaction log the engine already writes, so the write path pays nothing, but it is asynchronous and loses application context. An outbox gives typed business events in the same transaction, but only for writers that cooperate.
You need to split a wide table into two and rename several of its columns, but dozens of applications read it and you cannot redeploy them together. How would you use views as a facade to stage that change, and where does the approach run out?
basics
~20 sIntroduce the new tables alongside the old shape, then expose a view carrying the old name and column names over the new structure so existing readers keep working unchanged. Migrate consumers to new views over time, then retire the compatibility view. It runs out on writes, on plan quality, and on the fact that a shim nobody is forced to leave lives forever.
showing 31–46 of 46