Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Create a foreign key on the child table, pointing to a primary or unique key in the parent table. A portable pattern is:

CREATE TABLE orders (
    order_id   INTEGER PRIMARY KEY,
    customer_id INTEGER,
    CONSTRAINT fk_orders_customer
        FOREIGN KEY (customer_id)
        REFERENCES customers (customer_id)
);

Use the exact syntax and migration method for your database engine. The examples below cover PostgreSQL 17, MySQL 8.4, SQL Server, and SQLite, including referential actions, indexes, existing data, and common failures.

What a foreign key does

A foreign key (FK) is a constraint on a referencing, or child, table. Every non-NULL child value must match a value in the referenced parent key. In the example, each orders.customer_id must identify an existing customers.customer_id. If every order must have a customer, declare the child column NOT NULL; otherwise, NULL can represent an intentionally absent relationship.

The referenced columns should be a primary key or a unique key. Composite keys must list columns in the same order and use compatible data types. Create the parent key before creating the child constraint on engines that require it.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Create the constraint in a new table

Single-column relationship

CREATE TABLE customers (
    customer_id INTEGER PRIMARY KEY,
    name        VARCHAR(200) NOT NULL
);

CREATE TABLE orders (
    order_id    INTEGER PRIMARY KEY,
    customer_id INTEGER NOT NULL,
    order_date  DATE NOT NULL,
    CONSTRAINT fk_orders_customer
        FOREIGN KEY (customer_id)
        REFERENCES customers (customer_id)
);

Naming the constraint makes future migrations and error messages easier to understand. Without an explicit name, the server generates one.

Composite relationship

CREATE TABLE products (
    tenant_id  INTEGER NOT NULL,
    product_id INTEGER NOT NULL,
    name       VARCHAR(200) NOT NULL,
    CONSTRAINT pk_products PRIMARY KEY (tenant_id, product_id)
);

CREATE TABLE order_lines (
    order_id   INTEGER NOT NULL,
    tenant_id  INTEGER NOT NULL,
    product_id INTEGER NOT NULL,
    quantity   INTEGER NOT NULL,
    CONSTRAINT pk_order_lines PRIMARY KEY (order_id, product_id),
    CONSTRAINT fk_lines_product
        FOREIGN KEY (tenant_id, product_id)
        REFERENCES products (tenant_id, product_id)
);

Both sides must have matching column count, order, and compatible types. A composite parent key usually needs a primary-key or unique constraint covering the exact sequence.

Add a foreign key to an existing table

For engines that support this form, the common migration is:

ALTER TABLE orders
    ADD CONSTRAINT fk_orders_customer
    FOREIGN KEY (customer_id)
    REFERENCES customers (customer_id);

Before running it, find orphaned child values and repair them:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
SELECT o.customer_id, COUNT(*) AS orphan_count
FROM orders AS o
LEFT JOIN customers AS c ON c.customer_id = o.customer_id
WHERE o.customer_id IS NOT NULL
  AND c.customer_id IS NULL
GROUP BY o.customer_id;

Insert the missing parent rows, update or delete invalid child rows, or allow NULL where the relationship is optional. A validated constraint cannot be added while existing rows violate it. Run the migration in your normal transaction, locking, and deployment process; large tables may require an online or staged approach specific to your engine.

SQLite is different

SQLite does not provide a general ALTER TABLE ... ADD CONSTRAINT operation. To add an FK to an existing table, create a replacement table with the desired schema, copy validated data, drop the old table, rename the replacement, and recreate indexes, triggers, and other dependent objects. Follow SQLite’s documented table-rebuild procedure. Adding a column with a REFERENCES clause while enforcement is enabled also requires a NULL default.

Choose update and delete behavior

Add actions to the FK declaration according to the lifecycle you want:

Action Effect on child rows Important condition
NO ACTION / RESTRICT Rejects a parent update or delete that would leave an invalid reference. Timing differs by engine; PostgreSQL can defer NO ACTION.
CASCADE Updates or deletes matching child rows automatically. Use only when deleting dependents is intentional.
SET NULL Sets child FK columns to NULL. Every affected child column must be nullable.
SET DEFAULT Sets child columns to their defaults. Requires suitable defaults and is not supported in every engine.

Example with actions

CREATE TABLE order_items (
    order_item_id INTEGER PRIMARY KEY,
    order_id      INTEGER NOT NULL,
    product_id    INTEGER,
    CONSTRAINT fk_item_order
        FOREIGN KEY (order_id)
        REFERENCES orders (order_id)
        ON DELETE CASCADE
        ON UPDATE NO ACTION,
    CONSTRAINT fk_item_product
        FOREIGN KEY (product_id)
        REFERENCES products (product_id)
        ON DELETE SET NULL
);

Do not assume action names have identical timing or support across products. PostgreSQL supports deferrable constraints; actions other than NO ACTION cannot themselves be deferred. MySQL does not support deferred checking, and InnoDB treats NO ACTION as RESTRICT. MySQL 8.4 parses SET DEFAULT but rejects it for InnoDB.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Engine-specific requirements

PostgreSQL 17

PostgreSQL supports table-level FK declarations, all common actions, and DEFERRABLE or NOT DEFERRABLE (the default). Use DEFERRABLE INITIALLY DEFERRED when a transaction must temporarily contain an out-of-order relationship and be valid at commit. PostgreSQL does not automatically create an index on child columns.

CREATE TABLE payments (
    payment_id BIGINT PRIMARY KEY,
    order_id   BIGINT NOT NULL,
    CONSTRAINT fk_payment_order
        FOREIGN KEY (order_id)
        REFERENCES orders(order_id)
        DEFERRABLE INITIALLY DEFERRED
);

MySQL 8.4

Use InnoDB (or another engine with FK support), and provide indexes on both foreign and referenced keys. MySQL checks constraints immediately; there is no deferred mode. Verify storage-engine and version assumptions before deployment.

SQL Server

SQL Server supports inline single-column and table-level single- or multi-column FKs referencing primary or unique keys. Supported actions include NO ACTION, CASCADE, SET NULL, and SET DEFAULT. SET NULL requires nullable child columns, while SET DEFAULT requires defaults. SQL Server does not automatically create the child-side index.

SQLite

SQLite’s documentation states: “Foreign key constraints are disabled by default (for backwards compatibility), so must be enabled separately for each database connection.” Enable and verify them outside a transaction:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
PRAGMA foreign_keys = ON;
PRAGMA foreign_keys;

The second statement should return 1. Set this on every connection, including pooled connections and administrative tools. Changing the setting during an active transaction has no effect. SQLite recommends an index on child-key columns for efficient parent updates and deletes; it need not be unique.

Indexes, types, and performance

Index the child key

An FK enforces correctness, not query speed. An index beginning with the child FK columns helps joins and lets the database find dependent rows quickly during parent updates or deletes. MySQL requires the relevant indexes; PostgreSQL, SQL Server, and SQLite differ in whether they create or merely recommend one. Check the execution plan for your workload.

Match definitions exactly

  • Use compatible numeric, character, and binary types; avoid silent conversion.
  • For composite keys, match order and every column.
  • Match collation and length rules where your engine requires them.
  • Ensure the parent key is actually primary or unique according to that engine’s rules.

Plan cascading operations

Cascades can touch many rows, acquire locks, fire triggers, and generate substantial logs. Index child columns, test worst-case fan-out, and set transaction and statement timeouts appropriate to production. Prefer explicit archival workflows when a business record must never disappear automatically.

Safe migration checklist

  1. Identify parent and child tables and decide whether the relationship is mandatory.
  2. Confirm the parent columns have a valid primary or unique key.
  3. Compare data types, order, collation, and nullability.
  4. Inventory existing orphan values with a LEFT JOIN.
  5. Choose NO ACTION, CASCADE, SET NULL, or a supported default deliberately.
  6. Create or verify a child-side index.
  7. Apply the engine-specific DDL in a migration, with a rollback plan.
  8. Insert an invalid child value in a disposable test transaction and confirm rejection.
  9. Test parent update and delete behavior, including high-volume cases.
  10. For SQLite, enable and verify PRAGMA foreign_keys on every connection.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshooting common errors

“Cannot add or update a child row”

The child value has no matching parent, or the parent row was not committed yet. Insert the parent first, correct orphan data, and check transaction ordering.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

“Referenced table/key not found”

The parent table may not exist, the key may not be primary or unique, or composite columns may differ. Create the parent key first and compare definitions column by column.

SET NULL fails

The child column is NOT NULL. Make it nullable only if an absent relationship is valid, then rerun the migration.

Deletes are unexpectedly slow

Missing child indexes force scans. Add an index beginning with the FK columns and inspect the plan; also check cascades, triggers, and lock contention.

SQLite accepts invalid references

Foreign-key enforcement is probably off for that connection. Execute PRAGMA foreign_keys = ON before the transaction and verify it returns 1.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

SQLite rejects ALTER TABLE ... ADD CONSTRAINT

That syntax is not a general SQLite capability. Use the documented table-rebuild migration and recreate dependent objects.

Or skip the browser setup

If you need screenshots of SQL documentation, migration dashboards, or generated schema pages rather than a browser automation stack, ScreenshotNeo returns a clean image or PDF from one request. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, with the result identified by response headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.

cURL: See the full option list in the ScreenshotNeo documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frequently Asked Questions

Can a foreign key reference a non-unique column?

Generally no. Reference a primary key or a key covered by a qualifying UNIQUE constraint; exact rules vary by engine, especially for composite keys.

Does creating a foreign key automatically create an index?

Do not assume it. MySQL requires foreign-key indexes, while PostgreSQL and SQL Server do not automatically create the child-side index; SQLite recommends one for performance.

Can I disable a foreign key temporarily?

The mechanism is engine-specific and can create integrity gaps. Prefer loading parent data first or using a controlled migration; SQLite’s enforcement switch is per connection.

The Bottom Line

Create the FK on the child table, reference a primary or unique parent key, validate existing rows, index the child columns, and choose delete/update actions that match your data lifecycle. Then verify behavior using your database engine’s rules.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.