{"post":{"slug":"check-constraints-explained-2026","title":"Check Constraints: Rules the Database Enforces for You","excerpt":"Validation in application code holds until something else writes to the table. A check constraint holds for every writer, including the console session during an incident.","category":"code","date":"January 9, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">Y</span>our application validates that a quantity is positive before inserting. That holds for every write going through the application, which is most of them.\n\nIt does not hold for the data migration that ran last month, the maintenance script someone wrote, the bulk import from a partner, or the manual update executed during an incident at two in the morning. Each of those wrote directly, and none of them knew about your validation.\n\nA check constraint holds for all of them, because the database enforces it.\n\n## What changed in 2026\n\n- **Non-blocking constraint addition became standard.** Adding a constraint as not-valid and validating separately became the default approach on large tables.\n- **Constraints returned to favour.** After a period of pushing validation entirely into applications, database-level integrity regained ground.\n- **Multi-writer reality became the argument.** As more systems wrote to shared databases, application-only validation looked visibly insufficient.\n- **Generated column pairing spread.** Combining computed columns with constraints on them enabled rules that were previously awkward.\n\n## What they can and cannot express\n\nA check constraint is a boolean expression over columns of the same row.\n\n| Rule | Expressible? |\n| --- | --- |\n| Value is positive | Yes |\n| End date after start date | Yes |\n| Status is one of a set | Yes |\n| Email contains an @ | Yes, crudely |\n| Discount not more than the price | Yes, same row |\n| Total matches sum of line items in another table | No |\n| Only one active row per user | No — use a partial unique index |\n| Value is less than the current date | No — not deterministic |\n\nTwo limitations do most of the work.\n\n**Same row only.** No subqueries, no references to other tables. Cross-table rules need foreign keys, triggers, or application logic.\n\n**Deterministic only.** The expression must produce the same result for the same row every time. Referencing the current time is prohibited, because a row valid at insert would become invalid later, and the database cannot maintain that.\n\nFor rules a check constraint cannot express, other tools apply: [exclusion constraints](/blog/exclusion-constraints-explained-2026) for overlap rules, partial unique indexes for conditional uniqueness per [partial indexes](/blog/partial-indexes-explained-2026), and foreign keys for referential rules.\n\n## Adding one without locking\n\nThe operational concern. Adding a constraint normally requires the database to verify every existing row, which takes a lock for the duration — on a large table, an outage.\n\nThe two-step approach avoids it:\n\n**Add it as not-valid.** The constraint is created and enforced for new and modified rows, without checking existing ones. This takes a brief lock rather than a long one.\n\n**Validate it separately.** A subsequent validation step scans existing rows with a much weaker lock that does not block normal writes.\n\nIf validation finds violations, you have discovered a data quality problem — fix the rows, then validate again. That is a better outcome than the constraint failing to apply at all.\n\nOrder matters when adding a constraint to a table with existing bad data: clean first, or the constraint cannot be validated.\n\n## Naming and maintenance\n\nGive constraints explicit names. Generated names are unreadable, appear verbatim in error messages users may see, and make migrations harder to write and review.\n\nA name stating the rule — rather than the columns — makes a violation self-explanatory: an error naming a positive-quantity constraint tells you what happened without opening the schema.\n\nBe deliberate about scope. Constraints encoding rules that change frequently are a poor fit, because changing a constraint is a schema migration while changing application logic is a deploy. Encode invariants that are genuinely stable properties of the data; leave changeable business policy in code.\n\n## Common mistakes\n\n- **Relying on application validation alone.** Every other writer bypasses it.\n- **Adding constraints to large tables without the not-valid step.** Long lock.\n- **Non-deterministic expressions.** Rejected, and the reason is not always obvious.\n- **Generated names.** Unreadable errors, awkward migrations.\n- **Encoding volatile business rules.** Schema changes are slower than code changes.\n- **Trying to reference other tables.** Not possible; use a different mechanism.\n- **Not cleaning data first.** Validation fails and the constraint stays unvalidated.\n\n## FAQ\n\n**Do constraints slow down writes?**\nMarginally — an expression evaluated per write. Negligible compared with the cost of the write itself, and vastly cheaper than discovering bad data later.\n\n**Should I duplicate validation in the application?**\nYes, for the user experience. The application should catch violations early with a helpful message; the constraint is the backstop that catches everything else.\n\n**How do I handle a constraint that must change?**\nAdd the new one as not-valid, migrate the data, validate, then drop the old one — the same shape as any breaking change, per [expand and contract migrations](/blog/expand-contract-migrations-2026).\n\n**What about performance on bulk loads?**\nConstraints are checked per row. For very large loads, some workflows drop and re-add constraints, which requires confidence that the loaded data is clean — see [COPY bulk loading](/blog/copy-bulk-loading-2026).\n\n## Where to go next\n\nFor rules constraints cannot express, read [exclusion constraints](/blog/exclusion-constraints-explained-2026) and [partial indexes](/blog/partial-indexes-explained-2026). For deferring constraint checks within a transaction, [deferrable constraints](/blog/deferrable-constraints-explained-2026).","hue":307,"metaTitle":"Check Constraints: Rules the Database Enforces","metaDescription":"Why database check constraints catch what application validation misses, how to add them without locking a large table, and what they cannot express.","keywords":["check constraints","database validation","data integrity rules","not valid constraint","constraint locking","schema level validation","data quality","database design 2026"],"tldr":["**Application validation is bypassed by every other writer** — migrations, scripts, and manual fixes.","**Adding one to a large table locks unless validated separately** — add as not-valid, then validate.","**Constraints must be deterministic** — no current time, no subqueries, no other tables.","**Name them explicitly** — generated names are unreadable in error messages and migrations.","**Skip:** constraints encoding business rules that change often. Schema changes are slower than code changes."],"readTime":"4 min read","wordCount":862},"related":[{"slug":"enum-vs-lookup-table-2026","title":"Enum vs Lookup Table: Where Should Your Fixed Values Live?","excerpt":"A status column with five possible values can be a database enum, a lookup table, or a check constraint. The right answer depends almost entirely on who needs to change the list.","category":"code","date":"January 9, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":315},{"slug":"deferrable-constraints-explained-2026","title":"Deferrable Constraints: Checking at Commit Instead of Immediately","excerpt":"Swapping two unique values fails because the intermediate state violates the constraint. Deferring the check to commit time lets the transaction pass through invalid states legally.","category":"code","date":"January 2, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":320},{"slug":"how-to-design-a-database-schema-2026","title":"How to design a database schema in 2026: structure, constraints, and tradeoffs","excerpt":"A schema designed under pressure becomes a schema you refactor for years. This guide covers relational design principles, naming conventions, and 2026 tradeoffs.","category":"code","date":"March 30, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":312}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"bluetooth-codecs-explained-2026","title":"Bluetooth Codecs Explained: SBC, AAC, aptX and LDAC","excerpt":"Codec names dominate headphone marketing and explain less of the sound than the drivers, the fit, and the tuning. Here is what each one actually does and when it matters.","category":"tech","date":"January 9, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":230},"older":{"slug":"context-extension-explained-2026","title":"Context Extension: What a Bigger Window Actually Buys","excerpt":"Context windows grew by orders of magnitude, and the practical benefit is smaller than the numbers suggest. Cost, attention quality, and concurrency all move against you.","category":"ai","date":"January 9, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":213}}