[{"data":1,"prerenderedAt":1207},["ShallowReactive",2],{"blog-\u002Fblog\u002Fsql-constraints-primary-foreign-unique":3},{"id":4,"title":5,"body":6,"description":1193,"difficulty":1194,"extension":1195,"framework":1196,"frameworkSlug":27,"meta":1197,"navigation":273,"order":81,"path":1198,"qaPath":1199,"seo":1200,"stem":1201,"subtopic":1202,"topic":1203,"topicSlug":1204,"updated":1205,"__hash__":1206},"blog\u002Fblog\u002Fsql-constraints-primary-foreign-unique.md","SQL Constraints — PRIMARY KEY, FOREIGN KEY, UNIQUE, CHECK, and NOT NULL",{"type":7,"value":8,"toc":1183},"minimark",[9,14,18,22,154,171,175,185,379,382,386,389,489,495,556,612,615,619,624,812,815,819,825,1088,1092,1099,1156,1160,1179],[10,11,13],"h2",{"id":12},"what-constraints-do","What constraints do",[15,16,17],"p",{},"A constraint is a rule the database enforces on every write. It is the last line\nof defence against bad data — it catches what the application layer misses.\nConstraints run atomically with the write: the row is either committed with\nvalid data or rejected entirely.",[10,19,21],{"id":20},"not-null-the-simplest-constraint","NOT NULL — the simplest constraint",[23,24,29],"pre",{"className":25,"code":26,"language":27,"meta":28,"style":28},"language-sql shiki shiki-themes github-light github-dark","CREATE TABLE users (\n    id         BIGINT       PRIMARY KEY GENERATED ALWAYS AS IDENTITY,\n    email      VARCHAR(254) NOT NULL,   -- must be provided\n    created_at TIMESTAMPTZ  NOT NULL DEFAULT NOW(),\n    phone      VARCHAR(20)              -- intentionally nullable\n);\n","sql","",[30,31,32,52,79,108,129,148],"code",{"__ignoreMap":28},[33,34,37,41,44,48],"span",{"class":35,"line":36},"line",1,[33,38,40],{"class":39},"szBVR","CREATE",[33,42,43],{"class":39}," TABLE",[33,45,47],{"class":46},"sScJk"," users",[33,49,51],{"class":50},"sVt8B"," (\n",[33,53,55,58,61,64,67,70,73,76],{"class":35,"line":54},2,[33,56,57],{"class":50},"    id         ",[33,59,60],{"class":39},"BIGINT",[33,62,63],{"class":39},"       PRIMARY KEY",[33,65,66],{"class":39}," GENERATED",[33,68,69],{"class":39}," ALWAYS",[33,71,72],{"class":39}," AS",[33,74,75],{"class":39}," IDENTITY",[33,77,78],{"class":50},",\n",[33,80,82,85,88,91,95,98,101,104],{"class":35,"line":81},3,[33,83,84],{"class":50},"    email      ",[33,86,87],{"class":39},"VARCHAR",[33,89,90],{"class":50},"(",[33,92,94],{"class":93},"sj4cs","254",[33,96,97],{"class":50},") ",[33,99,100],{"class":39},"NOT NULL",[33,102,103],{"class":50},",   ",[33,105,107],{"class":106},"sJ8bj","-- must be provided\n",[33,109,111,114,117,120,123,126],{"class":35,"line":110},4,[33,112,113],{"class":50},"    created_at ",[33,115,116],{"class":39},"TIMESTAMPTZ",[33,118,119],{"class":39},"  NOT NULL",[33,121,122],{"class":39}," DEFAULT",[33,124,125],{"class":39}," NOW",[33,127,128],{"class":50},"(),\n",[33,130,132,135,137,139,142,145],{"class":35,"line":131},5,[33,133,134],{"class":50},"    phone      ",[33,136,87],{"class":39},[33,138,90],{"class":50},[33,140,141],{"class":93},"20",[33,143,144],{"class":50},")              ",[33,146,147],{"class":106},"-- intentionally nullable\n",[33,149,151],{"class":35,"line":150},6,[33,152,153],{"class":50},");\n",[15,155,156,157,159,160,163,164,166,167,170],{},"Every column that should never be empty should be ",[30,158,100],{},". This prevents the\nsubtle bugs caused by ",[30,161,162],{},"NULL"," propagating through calculations and comparisons.\nDefault-worthy columns (timestamps, counters, flags) should combine ",[30,165,100],{},"\nwith a ",[30,168,169],{},"DEFAULT",".",[10,172,174],{"id":173},"primary-key-row-identity","PRIMARY KEY — row identity",[15,176,177,178,180,181,184],{},"A primary key uniquely identifies each row. It implies ",[30,179,100],{}," and ",[30,182,183],{},"UNIQUE",",\nand the database creates an index on it automatically.",[23,186,188],{"className":25,"code":187,"language":27,"meta":28,"style":28},"-- Surrogate (generated) key\nCREATE TABLE products (\n    id           BIGINT       PRIMARY KEY GENERATED ALWAYS AS IDENTITY,\n    sku          VARCHAR(50)  NOT NULL UNIQUE,\n    product_name VARCHAR(200) NOT NULL\n);\n\n-- Composite primary key (junction table)\nCREATE TABLE order_items (\n    order_id    BIGINT NOT NULL REFERENCES orders(id),\n    product_id  BIGINT NOT NULL REFERENCES products(id),\n    quantity    INT    NOT NULL,\n    unit_price  NUMERIC(10,2) NOT NULL,\n    PRIMARY KEY (order_id, product_id)\n);\n",[30,189,190,195,206,225,247,264,268,275,281,293,310,325,339,365,374],{"__ignoreMap":28},[33,191,192],{"class":35,"line":36},[33,193,194],{"class":106},"-- Surrogate (generated) key\n",[33,196,197,199,201,204],{"class":35,"line":54},[33,198,40],{"class":39},[33,200,43],{"class":39},[33,202,203],{"class":46}," products",[33,205,51],{"class":50},[33,207,208,211,213,215,217,219,221,223],{"class":35,"line":81},[33,209,210],{"class":50},"    id           ",[33,212,60],{"class":39},[33,214,63],{"class":39},[33,216,66],{"class":39},[33,218,69],{"class":39},[33,220,72],{"class":39},[33,222,75],{"class":39},[33,224,78],{"class":50},[33,226,227,230,232,234,237,240,242,245],{"class":35,"line":110},[33,228,229],{"class":50},"    sku          ",[33,231,87],{"class":39},[33,233,90],{"class":50},[33,235,236],{"class":93},"50",[33,238,239],{"class":50},")  ",[33,241,100],{"class":39},[33,243,244],{"class":39}," UNIQUE",[33,246,78],{"class":50},[33,248,249,252,254,256,259,261],{"class":35,"line":131},[33,250,251],{"class":50},"    product_name ",[33,253,87],{"class":39},[33,255,90],{"class":50},[33,257,258],{"class":93},"200",[33,260,97],{"class":50},[33,262,263],{"class":39},"NOT NULL\n",[33,265,266],{"class":35,"line":150},[33,267,153],{"class":50},[33,269,271],{"class":35,"line":270},7,[33,272,274],{"emptyLinePlaceholder":273},true,"\n",[33,276,278],{"class":35,"line":277},8,[33,279,280],{"class":106},"-- Composite primary key (junction table)\n",[33,282,284,286,288,291],{"class":35,"line":283},9,[33,285,40],{"class":39},[33,287,43],{"class":39},[33,289,290],{"class":46}," order_items",[33,292,51],{"class":50},[33,294,296,299,301,304,307],{"class":35,"line":295},10,[33,297,298],{"class":50},"    order_id    ",[33,300,60],{"class":39},[33,302,303],{"class":39}," NOT NULL",[33,305,306],{"class":39}," REFERENCES",[33,308,309],{"class":50}," orders(id),\n",[33,311,313,316,318,320,322],{"class":35,"line":312},11,[33,314,315],{"class":50},"    product_id  ",[33,317,60],{"class":39},[33,319,303],{"class":39},[33,321,306],{"class":39},[33,323,324],{"class":50}," products(id),\n",[33,326,328,331,334,337],{"class":35,"line":327},12,[33,329,330],{"class":50},"    quantity    ",[33,332,333],{"class":39},"INT",[33,335,336],{"class":39},"    NOT NULL",[33,338,78],{"class":50},[33,340,342,345,348,350,353,356,359,361,363],{"class":35,"line":341},13,[33,343,344],{"class":50},"    unit_price  ",[33,346,347],{"class":39},"NUMERIC",[33,349,90],{"class":50},[33,351,352],{"class":93},"10",[33,354,355],{"class":50},",",[33,357,358],{"class":93},"2",[33,360,97],{"class":50},[33,362,100],{"class":39},[33,364,78],{"class":50},[33,366,368,371],{"class":35,"line":367},14,[33,369,370],{"class":39},"    PRIMARY KEY",[33,372,373],{"class":50}," (order_id, product_id)\n",[33,375,377],{"class":35,"line":376},15,[33,378,153],{"class":50},[15,380,381],{},"Use surrogate keys (auto-generated integers) for most tables. Use composite\nkeys for junction\u002Fassociation tables where the combination of two foreign keys\nis the natural identity.",[10,383,385],{"id":384},"foreign-key-referential-integrity","FOREIGN KEY — referential integrity",[15,387,388],{},"A foreign key ensures every value in the referencing column exists in the\nreferenced table. It prevents orphaned rows (an order that references a\nnon-existent customer).",[23,390,392],{"className":25,"code":391,"language":27,"meta":28,"style":28},"CREATE TABLE orders (\n    id          BIGINT       PRIMARY KEY GENERATED ALWAYS AS IDENTITY,\n    customer_id BIGINT       NOT NULL REFERENCES customers(id) ON DELETE RESTRICT,\n    status      VARCHAR(20)  NOT NULL DEFAULT 'pending',\n    created_at  TIMESTAMPTZ  NOT NULL DEFAULT NOW()\n);\n",[30,393,394,405,424,445,469,485],{"__ignoreMap":28},[33,395,396,398,400,403],{"class":35,"line":36},[33,397,40],{"class":39},[33,399,43],{"class":39},[33,401,402],{"class":46}," orders",[33,404,51],{"class":50},[33,406,407,410,412,414,416,418,420,422],{"class":35,"line":54},[33,408,409],{"class":50},"    id          ",[33,411,60],{"class":39},[33,413,63],{"class":39},[33,415,66],{"class":39},[33,417,69],{"class":39},[33,419,72],{"class":39},[33,421,75],{"class":39},[33,423,78],{"class":50},[33,425,426,429,431,434,436,439,442],{"class":35,"line":81},[33,427,428],{"class":50},"    customer_id ",[33,430,60],{"class":39},[33,432,433],{"class":39},"       NOT NULL",[33,435,306],{"class":39},[33,437,438],{"class":50}," customers(id) ",[33,440,441],{"class":39},"ON DELETE",[33,443,444],{"class":50}," RESTRICT,\n",[33,446,447,450,453,455,457,459,461,463,467],{"class":35,"line":110},[33,448,449],{"class":39},"    status",[33,451,452],{"class":39},"      VARCHAR",[33,454,90],{"class":50},[33,456,141],{"class":93},[33,458,239],{"class":50},[33,460,100],{"class":39},[33,462,122],{"class":39},[33,464,466],{"class":465},"sZZnC"," 'pending'",[33,468,78],{"class":50},[33,470,471,474,476,478,480,482],{"class":35,"line":131},[33,472,473],{"class":50},"    created_at  ",[33,475,116],{"class":39},[33,477,119],{"class":39},[33,479,122],{"class":39},[33,481,125],{"class":39},[33,483,484],{"class":50},"()\n",[33,486,487],{"class":35,"line":150},[33,488,153],{"class":50},[15,490,491],{},[492,493,494],"strong",{},"ON DELETE \u002F ON UPDATE actions:",[496,497,498,511],"table",{},[499,500,501],"thead",{},[502,503,504,508],"tr",{},[505,506,507],"th",{},"Action",[505,509,510],{},"Effect when the referenced row is deleted",[512,513,514,526,536,546],"tbody",{},[502,515,516,523],{},[517,518,519,522],"td",{},[30,520,521],{},"RESTRICT"," (default)",[517,524,525],{},"Reject the delete — prevents orphans",[502,527,528,533],{},[517,529,530],{},[30,531,532],{},"CASCADE",[517,534,535],{},"Delete the referencing rows automatically",[502,537,538,543],{},[517,539,540],{},[30,541,542],{},"SET NULL",[517,544,545],{},"Set the foreign key column to NULL",[502,547,548,553],{},[517,549,550],{},[30,551,552],{},"SET DEFAULT",[517,554,555],{},"Set the column to its default value",[23,557,559],{"className":25,"code":558,"language":27,"meta":28,"style":28},"-- Order items are removed when their order is deleted\nFOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE\n\n-- Soft relationship: customer deletion nullifies the field\nFOREIGN KEY (assigned_agent_id) REFERENCES agents(id) ON DELETE SET NULL\n",[30,560,561,566,583,587,592],{"__ignoreMap":28},[33,562,563],{"class":35,"line":36},[33,564,565],{"class":106},"-- Order items are removed when their order is deleted\n",[33,567,568,571,574,577,580],{"class":35,"line":54},[33,569,570],{"class":39},"FOREIGN KEY",[33,572,573],{"class":50}," (order_id) ",[33,575,576],{"class":39},"REFERENCES",[33,578,579],{"class":50}," orders(id) ",[33,581,582],{"class":39},"ON DELETE CASCADE\n",[33,584,585],{"class":35,"line":81},[33,586,274],{"emptyLinePlaceholder":273},[33,588,589],{"class":35,"line":110},[33,590,591],{"class":106},"-- Soft relationship: customer deletion nullifies the field\n",[33,593,594,596,599,601,604,606,609],{"class":35,"line":131},[33,595,570],{"class":39},[33,597,598],{"class":50}," (assigned_agent_id) ",[33,600,576],{"class":39},[33,602,603],{"class":50}," agents(id) ",[33,605,441],{"class":39},[33,607,608],{"class":39}," SET",[33,610,611],{"class":39}," NULL\n",[15,613,614],{},"Index foreign key columns — the database creates the index on the referenced\ncolumn (primary key) automatically, but not on the referencing column. Without\nthis index, every parent delete requires a full scan of the child table.",[10,616,618],{"id":617},"unique-prevent-duplicates","UNIQUE — prevent duplicates",[15,620,621,623],{},[30,622,183],{}," ensures a column (or combination of columns) has no duplicate\nnon-NULL values.",[23,625,627],{"className":25,"code":626,"language":27,"meta":28,"style":28},"-- Single-column unique\nALTER TABLE users ADD CONSTRAINT users_email_unique UNIQUE (email);\n\n-- Composite unique: one active subscription per customer\nCREATE TABLE subscriptions (\n    id            BIGINT      PRIMARY KEY GENERATED ALWAYS AS IDENTITY,\n    customer_id   BIGINT      NOT NULL REFERENCES customers(id),\n    plan          VARCHAR(50) NOT NULL,\n    status        VARCHAR(20) NOT NULL,\n    CONSTRAINT subscriptions_active_unique UNIQUE (customer_id, status)\n    -- Note: allows multiple rows if status differs (e.g., cancelled old ones)\n);\n\n-- Partial unique index (Postgres): only one active subscription per customer\nCREATE UNIQUE INDEX ON subscriptions (customer_id) WHERE status = 'active';\n",[30,628,629,634,658,662,667,678,698,713,730,747,766,771,775,779,784],{"__ignoreMap":28},[33,630,631],{"class":35,"line":36},[33,632,633],{"class":106},"-- Single-column unique\n",[33,635,636,639,641,644,647,650,653,655],{"class":35,"line":54},[33,637,638],{"class":39},"ALTER",[33,640,43],{"class":39},[33,642,643],{"class":50}," users ",[33,645,646],{"class":39},"ADD",[33,648,649],{"class":39}," CONSTRAINT",[33,651,652],{"class":50}," users_email_unique ",[33,654,183],{"class":39},[33,656,657],{"class":50}," (email);\n",[33,659,660],{"class":35,"line":81},[33,661,274],{"emptyLinePlaceholder":273},[33,663,664],{"class":35,"line":110},[33,665,666],{"class":106},"-- Composite unique: one active subscription per customer\n",[33,668,669,671,673,676],{"class":35,"line":131},[33,670,40],{"class":39},[33,672,43],{"class":39},[33,674,675],{"class":46}," subscriptions",[33,677,51],{"class":50},[33,679,680,683,685,688,690,692,694,696],{"class":35,"line":150},[33,681,682],{"class":50},"    id            ",[33,684,60],{"class":39},[33,686,687],{"class":39},"      PRIMARY KEY",[33,689,66],{"class":39},[33,691,69],{"class":39},[33,693,72],{"class":39},[33,695,75],{"class":39},[33,697,78],{"class":50},[33,699,700,703,705,708,710],{"class":35,"line":270},[33,701,702],{"class":50},"    customer_id   ",[33,704,60],{"class":39},[33,706,707],{"class":39},"      NOT NULL",[33,709,306],{"class":39},[33,711,712],{"class":50}," customers(id),\n",[33,714,715,718,720,722,724,726,728],{"class":35,"line":277},[33,716,717],{"class":50},"    plan          ",[33,719,87],{"class":39},[33,721,90],{"class":50},[33,723,236],{"class":93},[33,725,97],{"class":50},[33,727,100],{"class":39},[33,729,78],{"class":50},[33,731,732,734,737,739,741,743,745],{"class":35,"line":283},[33,733,449],{"class":39},[33,735,736],{"class":39},"        VARCHAR",[33,738,90],{"class":50},[33,740,141],{"class":93},[33,742,97],{"class":50},[33,744,100],{"class":39},[33,746,78],{"class":50},[33,748,749,752,755,757,760,763],{"class":35,"line":295},[33,750,751],{"class":39},"    CONSTRAINT",[33,753,754],{"class":50}," subscriptions_active_unique ",[33,756,183],{"class":39},[33,758,759],{"class":50}," (customer_id, ",[33,761,762],{"class":39},"status",[33,764,765],{"class":50},")\n",[33,767,768],{"class":35,"line":312},[33,769,770],{"class":106},"    -- Note: allows multiple rows if status differs (e.g., cancelled old ones)\n",[33,772,773],{"class":35,"line":327},[33,774,153],{"class":50},[33,776,777],{"class":35,"line":341},[33,778,274],{"emptyLinePlaceholder":273},[33,780,781],{"class":35,"line":367},[33,782,783],{"class":106},"-- Partial unique index (Postgres): only one active subscription per customer\n",[33,785,786,788,791,794,797,800,803,806,809],{"class":35,"line":376},[33,787,40],{"class":39},[33,789,790],{"class":39}," UNIQUE INDEX",[33,792,793],{"class":46}," ON",[33,795,796],{"class":50}," subscriptions (customer_id) ",[33,798,799],{"class":39},"WHERE",[33,801,802],{"class":39}," status",[33,804,805],{"class":39}," =",[33,807,808],{"class":465}," 'active'",[33,810,811],{"class":50},";\n",[15,813,814],{},"A partial unique index is more precise than a full composite unique — it\nenforces uniqueness only within the subset of rows where the condition is true.",[10,816,818],{"id":817},"check-custom-domain-rules","CHECK — custom domain rules",[15,820,821,824],{},[30,822,823],{},"CHECK"," validates that a column value satisfies a boolean expression.",[23,826,828],{"className":25,"code":827,"language":27,"meta":28,"style":28},"CREATE TABLE products (\n    id         BIGINT         PRIMARY KEY GENERATED ALWAYS AS IDENTITY,\n    unit_price NUMERIC(10, 2) NOT NULL CHECK (unit_price > 0),\n    stock_qty  INT            NOT NULL CHECK (stock_qty >= 0),\n    status     VARCHAR(20)    NOT NULL\n        CHECK (status IN ('active', 'discontinued', 'draft'))\n);\n\n-- Table-level check spanning multiple columns\nCREATE TABLE discounts (\n    id           BIGINT        PRIMARY KEY GENERATED ALWAYS AS IDENTITY,\n    min_purchase NUMERIC(10,2) NOT NULL,\n    max_purchase NUMERIC(10,2),\n    CONSTRAINT discounts_range_valid CHECK (\n        max_purchase IS NULL OR max_purchase > min_purchase\n    )\n);\n",[30,829,830,840,859,894,916,932,963,967,971,976,987,1006,1027,1044,1055,1077,1083],{"__ignoreMap":28},[33,831,832,834,836,838],{"class":35,"line":36},[33,833,40],{"class":39},[33,835,43],{"class":39},[33,837,203],{"class":46},[33,839,51],{"class":50},[33,841,842,844,846,849,851,853,855,857],{"class":35,"line":54},[33,843,57],{"class":50},[33,845,60],{"class":39},[33,847,848],{"class":39},"         PRIMARY KEY",[33,850,66],{"class":39},[33,852,69],{"class":39},[33,854,72],{"class":39},[33,856,75],{"class":39},[33,858,78],{"class":50},[33,860,861,864,866,868,870,873,875,877,879,882,885,888,891],{"class":35,"line":81},[33,862,863],{"class":50},"    unit_price ",[33,865,347],{"class":39},[33,867,90],{"class":50},[33,869,352],{"class":93},[33,871,872],{"class":50},", ",[33,874,358],{"class":93},[33,876,97],{"class":50},[33,878,100],{"class":39},[33,880,881],{"class":39}," CHECK",[33,883,884],{"class":50}," (unit_price ",[33,886,887],{"class":39},">",[33,889,890],{"class":93}," 0",[33,892,893],{"class":50},"),\n",[33,895,896,899,901,904,906,909,912,914],{"class":35,"line":110},[33,897,898],{"class":50},"    stock_qty  ",[33,900,333],{"class":39},[33,902,903],{"class":39},"            NOT NULL",[33,905,881],{"class":39},[33,907,908],{"class":50}," (stock_qty ",[33,910,911],{"class":39},">=",[33,913,890],{"class":93},[33,915,893],{"class":50},[33,917,918,920,923,925,927,930],{"class":35,"line":131},[33,919,449],{"class":39},[33,921,922],{"class":39},"     VARCHAR",[33,924,90],{"class":50},[33,926,141],{"class":93},[33,928,929],{"class":50},")    ",[33,931,263],{"class":39},[33,933,934,937,940,942,945,947,950,952,955,957,960],{"class":35,"line":150},[33,935,936],{"class":39},"        CHECK",[33,938,939],{"class":50}," (",[33,941,762],{"class":39},[33,943,944],{"class":39}," IN",[33,946,939],{"class":50},[33,948,949],{"class":465},"'active'",[33,951,872],{"class":50},[33,953,954],{"class":465},"'discontinued'",[33,956,872],{"class":50},[33,958,959],{"class":465},"'draft'",[33,961,962],{"class":50},"))\n",[33,964,965],{"class":35,"line":270},[33,966,153],{"class":50},[33,968,969],{"class":35,"line":277},[33,970,274],{"emptyLinePlaceholder":273},[33,972,973],{"class":35,"line":283},[33,974,975],{"class":106},"-- Table-level check spanning multiple columns\n",[33,977,978,980,982,985],{"class":35,"line":295},[33,979,40],{"class":39},[33,981,43],{"class":39},[33,983,984],{"class":46}," discounts",[33,986,51],{"class":50},[33,988,989,991,993,996,998,1000,1002,1004],{"class":35,"line":312},[33,990,210],{"class":50},[33,992,60],{"class":39},[33,994,995],{"class":39},"        PRIMARY KEY",[33,997,66],{"class":39},[33,999,69],{"class":39},[33,1001,72],{"class":39},[33,1003,75],{"class":39},[33,1005,78],{"class":50},[33,1007,1008,1011,1013,1015,1017,1019,1021,1023,1025],{"class":35,"line":327},[33,1009,1010],{"class":50},"    min_purchase ",[33,1012,347],{"class":39},[33,1014,90],{"class":50},[33,1016,352],{"class":93},[33,1018,355],{"class":50},[33,1020,358],{"class":93},[33,1022,97],{"class":50},[33,1024,100],{"class":39},[33,1026,78],{"class":50},[33,1028,1029,1032,1034,1036,1038,1040,1042],{"class":35,"line":341},[33,1030,1031],{"class":50},"    max_purchase ",[33,1033,347],{"class":39},[33,1035,90],{"class":50},[33,1037,352],{"class":93},[33,1039,355],{"class":50},[33,1041,358],{"class":93},[33,1043,893],{"class":50},[33,1045,1046,1048,1051,1053],{"class":35,"line":367},[33,1047,751],{"class":39},[33,1049,1050],{"class":50}," discounts_range_valid ",[33,1052,823],{"class":39},[33,1054,51],{"class":50},[33,1056,1057,1060,1063,1066,1069,1072,1074],{"class":35,"line":376},[33,1058,1059],{"class":50},"        max_purchase ",[33,1061,1062],{"class":39},"IS",[33,1064,1065],{"class":39}," NULL",[33,1067,1068],{"class":39}," OR",[33,1070,1071],{"class":50}," max_purchase ",[33,1073,887],{"class":39},[33,1075,1076],{"class":50}," min_purchase\n",[33,1078,1080],{"class":35,"line":1079},16,[33,1081,1082],{"class":50},"    )\n",[33,1084,1086],{"class":35,"line":1085},17,[33,1087,153],{"class":50},[10,1089,1091],{"id":1090},"deferrable-constraints-postgres","Deferrable constraints (Postgres)",[15,1093,1094,1095,1098],{},"By default, constraint checks run immediately on each row write. ",[30,1096,1097],{},"DEFERRABLE INITIALLY DEFERRED"," moves the check to transaction commit time — useful when\nyou need to insert rows in an order that temporarily violates a constraint.",[23,1100,1102],{"className":25,"code":1101,"language":27,"meta":28,"style":28},"-- Useful for circular references: departments and their managers\nALTER TABLE departments\n    ADD CONSTRAINT departments_manager_fk\n    FOREIGN KEY (manager_id) REFERENCES employees(id)\n    DEFERRABLE INITIALLY DEFERRED;\n-- Now you can INSERT a department and its manager in any order\n-- within the same transaction; the FK is only checked at COMMIT.\n",[30,1103,1104,1109,1118,1128,1141,1146,1151],{"__ignoreMap":28},[33,1105,1106],{"class":35,"line":36},[33,1107,1108],{"class":106},"-- Useful for circular references: departments and their managers\n",[33,1110,1111,1113,1115],{"class":35,"line":54},[33,1112,638],{"class":39},[33,1114,43],{"class":39},[33,1116,1117],{"class":50}," departments\n",[33,1119,1120,1123,1125],{"class":35,"line":81},[33,1121,1122],{"class":39},"    ADD",[33,1124,649],{"class":39},[33,1126,1127],{"class":50}," departments_manager_fk\n",[33,1129,1130,1133,1136,1138],{"class":35,"line":110},[33,1131,1132],{"class":39},"    FOREIGN KEY",[33,1134,1135],{"class":50}," (manager_id) ",[33,1137,576],{"class":39},[33,1139,1140],{"class":50}," employees(id)\n",[33,1142,1143],{"class":35,"line":131},[33,1144,1145],{"class":50},"    DEFERRABLE INITIALLY DEFERRED;\n",[33,1147,1148],{"class":35,"line":150},[33,1149,1150],{"class":106},"-- Now you can INSERT a department and its manager in any order\n",[33,1152,1153],{"class":35,"line":270},[33,1154,1155],{"class":106},"-- within the same transaction; the FK is only checked at COMMIT.\n",[10,1157,1159],{"id":1158},"recap","Recap",[15,1161,1162,1163,1165,1166,1169,1170,1172,1173,1175,1176,1178],{},"Constraints are cheap insurance: ",[30,1164,100],{}," eliminates NULL-propagation bugs;\n",[30,1167,1168],{},"PRIMARY KEY"," guarantees row identity; ",[30,1171,570],{}," keeps references valid;\n",[30,1174,183],{}," prevents duplicate business keys; ",[30,1177,823],{}," enforces domain rules that\ndo not belong in application code. Index every foreign key column on the\nreferencing side. Use partial unique indexes for \"at most one active record\"\npatterns. Define constraints in the schema, not just in application code —\nthe database is the only layer that guarantees enforcement on every write path.",[1180,1181,1182],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":28,"searchDepth":54,"depth":54,"links":1184},[1185,1186,1187,1188,1189,1190,1191,1192],{"id":12,"depth":54,"text":13},{"id":20,"depth":54,"text":21},{"id":173,"depth":54,"text":174},{"id":384,"depth":54,"text":385},{"id":617,"depth":54,"text":618},{"id":817,"depth":54,"text":818},{"id":1090,"depth":54,"text":1091},{"id":1158,"depth":54,"text":1159},"SQL constraints explained — how PRIMARY KEY, FOREIGN KEY, UNIQUE, CHECK, and NOT NULL protect data integrity, with cascades and deferrable constraints.","medium","md","SQL",{},"\u002Fblog\u002Fsql-constraints-primary-foreign-unique","\u002Fsql\u002Fschema\u002Fconstraints",{"title":5,"description":1193},"blog\u002Fsql-constraints-primary-foreign-unique","Constraints","Schema & Data Types","schema","2026-06-20","A8rj9OWeI-WvTXMEZdqJx9wgiTarpc0kZ9tSXJylin8",1782244088737]