- Learn
- Computer Science & IT
- Programming
- SQL Basics
- SQL CREATE TABLE
SQL CREATE TABLE: Creating Tables and Changing Data
CREATE TABLE statements with sensible data types, protect the data with NOT NULL, UNIQUE, CHECK, primary keys and foreign keys, and add, change and remove rows with INSERT, UPDATE and DELETE. Practice targeting exactly the right rows, group related changes in transactions you can commit or roll back, and change or remove tables with ALTER TABLE and DROP TABLE. Examples use standard SQL that runs on SQLite and PostgreSQL, with differences in MySQL, SQL Server and Oracle pointed out where they matter.BeginnerLesson 6 of 6 in SQL Basics Course
What you will learn
- Write
CREATE TABLEstatements that define a table and its columns, and choose a data type for each column that fits the values it will hold.- Write a CREATE TABLE statementDefine a new table with named columns, check its structure, and avoid the syntax errors beginners make most often.
- Choose a data type for each columnPick integer, exact decimal, floating-point, text, date and true/false types for the values a column holds, and know how SQLite's flexible typing differs from stricter databases.
- Write a CREATE TABLE statement
- Use constraints so that the database itself rejects missing, duplicate, out-of-range and unmatched values, and give every row a reliable key.
- Require, default and limit values with NOT NULL, DEFAULT, UNIQUE and CHECKDeclare the column and table rules that keep values present, distinct and within range, and predict which inserts a rule will reject.
- Identify rows with primary keys and generated IDsDeclare single-column and composite primary keys, let the database number new rows, and know what generated IDs do and do not promise in each database system.
- Link tables with foreign keysDeclare foreign keys that keep references between tables valid, choose what happens when a referenced row is deleted, and switch on enforcement where it is off.
- Require, default and limit values with NOT NULL, DEFAULT, UNIQUE and CHECK
- Add new rows with
INSERT, one at a time, several at once or copied from a query, and handle generated keys and duplicates.- Insert rows with VALUESWrite
INSERT ... VALUESstatements with a column list and correctly written text, numbers, dates andNULL, and read the errors that stop an insert. - Insert from queries, return new keys and handle duplicatesCopy rows with
INSERT ... SELECT, read back generated values withRETURNING, and decide what happens when an inserted row collides with an existing one.
- Insert rows with VALUES
- Change values with
UPDATEand remove rows withDELETE, and make sure each statement affects exactly the rows intended.- Change values with UPDATEWrite
UPDATEstatements that set one or more columns, calculate new values from old ones, and stay within the table's constraints. - Remove rows with DELETERemove chosen rows with
DELETE, tellDELETE,TRUNCATEandDROP TABLEapart, and decide when marking a row as deleted is better than removing it. - Target exactly the rows you meanBuild
WHEREconditions for changes that match the intended rows and no others, and check the result before keeping it.
- Change values with UPDATE
- Use transactions so that a group of related changes is kept as a whole or undone as a whole, and recover cleanly when a statement fails.
- Commit or roll back a group of changesStart a transaction, keep its changes with
COMMITor undo them withROLLBACK, and understand autocommit. - Handle errors, savepoints and structure changes in a transactionRespond correctly when a statement fails inside a transaction, undo part of a transaction with a savepoint, and know which databases can roll back
CREATE,ALTERandDROP.
- Commit or roll back a group of changes
- Change the structure of existing tables without losing their rows, and remove tables safely when they are no longer needed.
- Add, rename and remove columns with ALTER TABLEUse
ALTER TABLEto add, rename and drop columns and to change types and constraints, and work around the limits of SQLite. - Remove tables with DROP TABLERemove tables with
DROP TABLE, respect the dependencies between tables, and choose between dropping, emptying and deleting.
- Add, rename and remove columns with ALTER TABLE
SQL CREATE TABLE study guide
1Create tables with suitable column types
A table needs a clear structure before it can hold useful data. Start with the facts one row records, give each fact a name, and choose how the database will store it. You will build an empty table, inspect what was actually created, and distinguish a number used for calculation from text that merely looks numeric. SQLite and PostgreSQL share much of the syntax, but their type rules can produce very different results.
1.1 Write a CREATE TABLE statement
A table definition describes what each row can hold. It does not add any rows. In an online shop, each product needs separate fields for its identifier, name, price and stock count.
Build the definition
CREATE TABLE products (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
price NUMERIC(8, 2) NOT NULL,
in_stock INTEGER DEFAULT 0
);
Read from the outside in. CREATE TABLE products names the new table. Parentheses contain its column definitions. Each line gives a column name, a type and any rules for that column. Commas separate definitions; the last definition has no comma. The semicolon ends the statement. After it succeeds, SELECT * FROM products; returns zero rows.
This key declaration generates IDs in SQLite. In PostgreSQL, use id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY to generate them. The decimal declaration also behaves differently across these systems; choosing exact storage is part of choosing a type.
Give each fact its own column
A customer row could contain id, first_name, last_name, email and signup_date. Packing "Ana Ruiz, ana@example.com" into one field makes the name and email difficult to search or check separately. Name the fields for what they contain. Short lowercase names joined with underscores, such as unit_price, are easy to reuse.
Avoid keywords as names: orders works, while unquoted order conflicts with SQL's ORDER keyword. A name containing spaces needs double quotes, as in "order date", each time you use it. PostgreSQL folds unquoted names to lowercase, so unquoted Products and products refer to the same name.
Single quotes have another job: they enclose text values such as 'Desk lamp'. Double quotes identify names. PostgreSQL reads "Desk lamp" as a column name, not as product text.
Check what was created
A trailing comma before ) causes a syntax error. A missing comma often does too, but SQLite can read several words as one type name. CREATE TABLE t (a INTEGER b TEXT); creates one column called a, not two columns. Inspect the definition:
- SQLite shell:
.schema productsprints the creation statement. - SQLite SQL:
PRAGMA table_info(products);lists columns and types. - PostgreSQL psql:
\d productsshows columns, types and constraints.
CREATE TABLE IF NOT EXISTS products (...) avoids an error when the name is already present. It does not compare or update the old definition. A table missing a needed column stays that way.
Create a one-time copy
CREATE TABLE cheap_products AS
SELECT name, price FROM products WHERE price < 10;
This creates columns and copies the query's rows. Later product changes do not refresh the copy. In SQLite and PostgreSQL, the new table does not inherit the source's primary key, NOT NULL or CHECK rules.
CREATE, ALTER and DROP belong to data definition language, or DDL: they change structure. INSERT, UPDATE and DELETE are data manipulation language, or DML: they change rows. SELECT reads data; books differ on whether they include it in DML or give queries their own family.
Key Takeaways
- Column definitions contain a name, type and optional rules.
- Use commas between definitions, with no trailing comma.
- Single quotes enclose text; double quotes enclose identifiers.
- Inspect the resulting structure, including after an IF NOT EXISTS statement.
- CREATE TABLE AS copies query data without copying the source constraints.
1.2 Choose a data type for each column
Choose a type from the job the value must do. A quantity must support arithmetic. A ZIP code must preserve its characters, including leading zeros. Both can contain digits, but they need different storage.
Choose number and text types
| Values | Useful type | Reason |
|---|---|---|
| Whole counts | INTEGER | Counts have no fractional part. |
| Exact money in PostgreSQL | NUMERIC(p, s) | Decimal arithmetic preserves exact amounts. |
| Exact money in SQLite | INTEGER cents | Whole cents avoid floating-point storage. |
| Measurements | REAL or DOUBLE PRECISION | Approximate values suit measured quantities. |
| Names and codes | TEXT or VARCHAR(n) | Characters and leading zeros are preserved. |
In PostgreSQL, NUMERIC(8, 2) has eight total digits, two after the decimal point. Its largest positive value is 999999.99. Inserting 12.345 stores 12.35; 1234567.00 overflows because it needs seven digits before the point. DECIMAL is another name for this type family.
SQLite accepts NUMERIC(8, 2) but does not enforce that precision or scale. It stores 19.99 as floating point and keeps 12.345 unrounded. Store 1999 whole cents for an exact amount of 19.99. Floating point approximates binary fractions: SQLite's SELECT 0.1 + 0.2 = 0.3; returns 0. PostgreSQL treats those bare decimal literals as exact numeric values; cast them to DOUBLE PRECISION to see the approximate result.
Text numbers sort by characters: '100', '25', '9' appear in that order. Numeric quantities need numeric columns. ZIP code '02139' belongs in text; integer storage loses its initial zero.
PostgreSQL's VARCHAR(20) rejects text longer than 20 characters. Its CHAR(n) pads shorter text with spaces to a fixed length. TEXT has no declared character limit. SQLite treats both VARCHAR(n) and CHAR(n) as text, without enforcing the length or adding padding.
Store dates and yes/no values
PostgreSQL has DATE, TIME, TIMESTAMP and BOOLEAN. It rejects an impossible DATE such as '2026-02-30'. SQLite has no dedicated date storage type. Use ISO text such as '2026-03-15', whose character order matches date order. US-style text misorders dates across years: '12/01/2025' sorts after '03/15/2026'.
A timestamp adds a time, such as '2026-03-15 14:30:00'. PostgreSQL uses TRUE and FALSE for Booleans; SQLite stores 1 and 0 and accepts those keywords. MySQL treats BOOLEAN as a small integer; SQL Server uses BIT.
Know what the system enforces
Ordinary SQLite tables use type affinity, a storage preference. An INTEGER column converts '5' to 5 but accepts 'abc' as text. PostgreSQL rejects text that cannot become an integer. SQLite 3.37.0 and later support STRICT tables, whose permitted types are INT, INTEGER, REAL, TEXT, BLOB and ANY.
For orders, choose an integer id, text email, a PostgreSQL DATE or SQLite ISO text date, PostgreSQL NUMERIC(10, 2) or SQLite integer cents for the total, and a Boolean or 0/1 flag for payment. NULL means missing or unknown; it is neither a type nor the same as zero or empty text. A column accepts it unless a rule forbids it.
Key Takeaways
- Choose types by use: arithmetic, text, time or yes/no.
- NUMERIC precision counts all digits; scale counts decimal places.
- Use PostgreSQL NUMERIC or SQLite integer cents for exact money.
- ISO date text sorts by date when written consistently.
- SQLite affinity permits mixed storage; STRICT strengthens type checks.
- NULL marks a missing or unknown value.
2Protect data with constraints
A database can check rules at the moment data changes, whichever program sends the change. Column rules control missing values, duplicates and ranges; keys identify rows and keep links between tables valid. You will follow accepted and rejected inserts, choose generated IDs, and predict what happens to orders when a customer is removed. These rules work together: a check on price does a different job from a requirement that price be present.
2.1 Require, default and limit values with NOT NULL, DEFAULT, UNIQUE and CHECK
A constraint makes the database reject data that breaks a declared rule. The check runs when rows are inserted or updated, no matter which application sends the statement. Different rules protect different parts of a product record.
Combine rules in a table
This SQLite definition generates the key and checks the other fields. For PostgreSQL, replace the key line with an identity primary key.
CREATE TABLE products (
id INTEGER PRIMARY KEY,
sku TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
price NUMERIC(8, 2) NOT NULL CHECK (price > 0),
in_stock INTEGER NOT NULL DEFAULT 0 CHECK (in_stock >= 0)
);
NOT NULL requires a value. UNIQUE prevents a repeated SKU. CHECK (price > 0) rejects zero or negative prices. The stock count has a default of 0 and must stay nonnegative. SQLite's numeric declaration does not give it exact decimal arithmetic, so use integer cents when exactness matters.
Distinguish omission from NULL
Insert a desk lamp with SKU 'LAMP-01', name 'Desk lamp' and price 24.99, leaving stock out of the column list. It succeeds and gets stock 0. A supplied NULL stock does not request the default: it fails NOT NULL.
A coffee mug priced at -3.50 fails the price check. A second product with SKU 'LAMP-01' fails UNIQUE, even if its name and price differ. A pen with no name fails NOT NULL. Read the named rule or column in the error and correct the input.
Empty text is a real value. name TEXT NOT NULL accepts '' in SQLite and PostgreSQL. Add CHECK (name <> '') to reject it. Oracle treats empty text as NULL, so the same input meets a different rule there.
Check combinations and missing values
A column rule follows its type. A table rule is a separate item inside the table definition and can compare columns:
CHECK (end_date >= start_date),
UNIQUE (order_id, product_id)
The first rejects an end date before a start date. The second rejects a repeated pair: a product can occur in different orders, but cannot appear twice in the same order. A table may have several unique constraints.
A check rejects a row when its condition is false. If price is NULL, price > 0 is unknown, so the check passes. That is why the product price also needs NOT NULL. PostgreSQL, SQLite and MySQL normally allow multiple NULLs under UNIQUE; SQL Server allows one in a single-column unique constraint. PostgreSQL 15 and later offer UNIQUE NULLS NOT DISTINCT when missing values must conflict too. MySQL versions before 8.0.16 ignored CHECK rules.
Give a rule a useful name with CONSTRAINT price_positive CHECK (price > 0). The name helps identify it in an error and remove it later. A constraint checks exactly its expression: CHECK (email LIKE '%@%') catches a missing at sign but cannot catch a typo in an otherwise matching address.
Key Takeaways
- NOT NULL rejects missing values but does not reject empty text in SQLite or PostgreSQL.
- DEFAULT fills omitted columns; an explicit NULL does not request it.
- UNIQUE can protect a column or a combination of columns.
- CHECK rejects false conditions; add NOT NULL when unknown must also be rejected.
- Constraint errors identify data that needs correction.
2.2 Identify rows with primary keys and generated IDs
A primary key identifies one row reliably. It can be one column or a combination, but a table has at most one primary key. The key must be unique and present, so other statements can name a row without depending on its description.
Choose a stable identifier
A natural key is an existing real-world identifier, such as a book's ISBN. A surrogate key is an artificial identifier, often a generated integer. A customer's email can change; a generated customer ID can stay the same while that happens.
A composite key identifies a combination:
PRIMARY KEY (order_id, product_id)
An order can contain several products, and a product can occur in several orders. The pair identifies one order item. Neither column needs to be unique by itself.
PostgreSQL, MySQL and SQL Server enforce nonmissing primary keys. Ordinary SQLite tables retain a historical exception: a primary key other than INTEGER PRIMARY KEY can contain NULL. Use code TEXT PRIMARY KEY NOT NULL for a text key. SQLite STRICT tables close this gap too.
Let the database generate the number
| Database | Generated primary key declaration |
|---|---|
| SQLite | id INTEGER PRIMARY KEY |
| PostgreSQL | id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY |
| MySQL | id INT AUTO_INCREMENT PRIMARY KEY |
| SQL Server | id INT IDENTITY(1, 1) PRIMARY KEY |
Leave id out of the insert's column list to request a generated value. A plain PostgreSQL INTEGER PRIMARY KEY does not generate one. Older PostgreSQL code often uses SERIAL.
In SQLite, INTEGER PRIMARY KEY aliases the internal row number. Start with generated IDs 1, 2 and 3, then delete 3. The next normal generated ID is 3 again, one above the current maximum. With INTEGER PRIMARY KEY AUTOINCREMENT, it is 4: a number held by a committed row is not reused. A number from a rolled-back insert can still be reused. This stronger guarantee costs extra work, so choose it when the no-reuse requirement matters.
Read IDs without treating them as counts
Gaps are normal. Deletions leave them, and PostgreSQL does not return sequence numbers after failed or rolled-back inserts. Use COUNT(*) for a row count and a timestamp for event time. The largest ID answers neither question reliably.
PostgreSQL's GENERATED ALWAYS form normally rejects an explicit ID such as 50. GENERATED BY DEFAULT and SERIAL accept explicit values, but supplying 50 does not move the generator past 50. A later generated value can collide with it.
To use a new row's key in a related row, read the key back:
INSERT INTO customers (first_name, last_name, email)
VALUES ('Dev', 'Patel', 'dev@example.com')
RETURNING id;
RETURNING works in PostgreSQL and SQLite 3.35.0 and later. SQLite also offers last_insert_rowid(), MySQL offers LAST_INSERT_ID(), and SQL Server uses OUTPUT. Read the result of the insert instead of guessing the next ID.
Key Takeaways
- A primary key identifies a row; a composite key identifies a combination.
- Generated keys use different declarations in different databases.
- SQLite AUTOINCREMENT prevents reuse of committed IDs, with extra cost.
- ID gaps are normal; use COUNT(*) to count rows.
- RETURNING reads the actual generated key.
2.3 Link tables with foreign keys
An order's customer_id should point to a customer who exists. A foreign key makes the database check that link. The table holding the reference is the child; the table it points to is the parent.
Declare and enforce the link
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL REFERENCES customers(id)
);
Here customers must exist first, and its id must be a primary key or unique key. Match the types on both sides. The table form, FOREIGN KEY (customer_id) REFERENCES customers(id), declares the same link separately from the column.
With enforcement on, an order for customer 99 fails when no customer 99 exists. Updating an order to that missing customer also fails. A nullable foreign key can hold NULL, meaning no linked row; NOT NULL makes the link required.
In SQLite, run PRAGMA foreign_keys = ON; on each new connection before starting a transaction. A setting change inside a transaction has no effect. PostgreSQL and SQL Server enforce foreign keys normally, and MySQL's default InnoDB engine does too.
Predict a parent deletion
Use customers 1, Ana, and 2, Ben. Orders 101 and 102 belong to Ana; 103 belongs to Ben. Consider deleting customer 1 under different declarations:
| ON DELETE action | Result |
|---|---|
NO ACTION or RESTRICT | The delete fails while 101 and 102 refer to Ana. |
CASCADE | Ana, 101 and 102 are removed; Ben and 103 remain. |
SET NULL | Ana is removed; all orders remain, with 101 and 102 unlinked. |
SET DEFAULT | The referencing values become their declared default. |
SET NULL needs a nullable column. SET DEFAULT still has to satisfy the foreign key: a nonmissing default must identify an existing parent. A NULL default works when missing links are allowed.
Choose by the meaning of the data. Order items often belong entirely to their order, so cascading can fit. Orders that must be retained call for a blocking action when someone tries to remove their customer. An optional supplier link can be set to NULL. Applying cascades everywhere lets one delete remove more records than its table name suggests.
Work in dependency order
Create parents before children and insert parent rows before rows that refer to them. When removing related data or tables, work from children to parents. An ON UPDATE action offers the same choices when the parent's key changes; ON UPDATE CASCADE copies the new key to the children.
RESTRICT checks immediately. NO ACTION, the default, can wait until the statement ends, or until transaction end for a deferrable constraint. Both block an ordinary delete that would leave unmatched references. Keep generated IDs stable so that changing a customer's details does not require changing every reference to that customer.
Key Takeaways
- A foreign key links child values to an existing primary or unique parent key.
- NULL permits an optional link unless NOT NULL forbids it.
- CASCADE deletes children; SET NULL keeps them without their link.
- Enable SQLite foreign keys before BEGIN on each connection.
- Create and insert parents first; delete and drop children first.
- Choose actions from the records that must be kept.
3Add rows with INSERT
Adding rows means matching each value to the right column and letting the database check the result. A product can be entered directly, several customers can be added together, and older orders can be copied from a query. You will also read back a generated key and choose how to handle an existing SKU. The same input can mean a replacement stock count or an extra delivery, so an upsert must express the intended change precisely.
3.1 Insert rows with VALUES
INSERT adds rows to an existing table. Its column list is the map between the values you supply and the fields that receive them. Write the map explicitly so that a later table change does not silently change your input's meaning.
Match values by position
INSERT INTO products (sku, name, price, in_stock)
VALUES ('LAMP-01', 'Desk lamp', 24.99, 12);
Read the pairs in order: SKU gets 'LAMP-01', name gets 'Desk lamp', price gets 24.99 and stock gets 12. There must be four values for these four columns. A mismatch such as two values for three columns fails.
Without the list, values follow the table's declared column order. SQLite requires all columns to be supplied; PostgreSQL allows fewer and defaults the trailing columns. An explicit list avoids relying on either behavior. It also lets you put columns in an order that is clear for the input.
A swapped pair can be worse than a syntax error. Supplying 9.99 as the name and 'Desk lamp' as price makes PostgreSQL reject the nonnumeric price. Ordinary SQLite affinity can store name text "9.99" and price text "Desk lamp" without an error. Read the inserted row back with SELECT to check the mapping.
Write literal values correctly
| Kind | SQL example |
|---|---|
| Text | 'Desk lamp' |
| Text containing an apostrophe | 'O''Brien' |
| Number | 1250.50 |
| ISO date | '2026-03-15' |
| Missing value | NULL |
The doubled apostrophe stores one apostrophe. Numbers have no currency sign or thousands separator; a comma separates values, so a comma inside an unquoted amount changes the value count. 'NULL' is the four-letter text "NULL", not a missing value.
Let omitted fields fill themselves
A column left out receives its default, or NULL if it has none. A generated key receives a generated value. That last behavior depends on the declaration: SQLite's INTEGER PRIMARY KEY generates IDs; PostgreSQL requires an identity declaration for this purpose. A plain PostgreSQL integer primary key left out of an insert fails because its value would be NULL.
PostgreSQL, MySQL and SQL Server allow DEFAULT as a value placeholder. SQLite does not accept it inside a VALUES list; leave that column out instead. An explicit NULL never requests the default.
Add several rows together
INSERT INTO customers (first_name, last_name, email) VALUES
('Ana', 'Ruiz', 'ana@example.com'),
('Ben', 'Okafor', 'ben@example.com'),
('Chloe', 'Nguyen', 'chloe@example.com');
In an empty table with its generated key starting at 1, these receive IDs 1, 2 and 3. Under ordinary constraint handling, the multi-row statement succeeds as a whole or fails as a whole. If one row violates a rule, none of this statement's rows are inserted.
A UNIQUE error points to a duplicate key or value; NOT NULL points to missing data; CHECK points to a failed condition; and a foreign key error points to an invalid link. Fix the value or its intended mapping before trying again.
Key Takeaways
- List columns and match each value to them by position.
- Omitted fields receive defaults or NULL; generated IDs require a generator.
- Use single quotes for text and double an embedded apostrophe.
- NULL and the text NULL are different.
- A normal multi-row insert fails as a whole if a row violates a constraint.
3.2 Insert from queries, return new keys and handle duplicates
Rows do not have to be typed one at a time. A query can supply the rows for an insert, and a conflict rule can say what should happen when a unique value already exists. Both features depend on matching the intended data precisely.
Copy a query result
INSERT INTO archived_orders (id, customer_id, order_date, total)
SELECT id, customer_id, order_date, total
FROM orders
WHERE order_date < '2026-01-01';
There is no VALUES clause: the query supplies the values. Match its selected columns to the target list by position. The target table must already exist, and its rules still apply.
Preview the SELECT alone. Orders 201, 202 and 204 have dates 2025-11-03, 2025-12-19 and 2025-10-30 and totals 45.00, 120.50 and 60.00. Orders 203 and 205 are dated in 2026. The query returns three rows totaling 225.50, and the insert copies those three. It can also copy zero rows when the filter finds nothing.
A second run tries to copy them again. A target primary key makes that fail instead of silently storing duplicates. INSERT ... SELECT is a copy operation; it does not remove the source rows or keep the two tables synchronized.
File imports often go through a staging table first. SQLite's shell uses .import --csv file.csv staging; psql uses \copy. In a later query, TRIM(email) removes surrounding spaces and NULLIF(TRIM(email), '') turns a blank email into NULL. The destination constraints then check the cleaned rows.
Read the result of a write
INSERT INTO customers (first_name, last_name, email)
VALUES ('Dev', 'Patel', 'dev@example.com')
RETURNING id;
The result gives the generated customer ID to use in a related row. PostgreSQL and SQLite 3.35.0 and later support RETURNING on inserts, updates and deletes. MySQL uses LAST_INSERT_ID() for a generated ID; SQL Server uses OUTPUT.
Decide how duplicates should behave
With stock (sku TEXT PRIMARY KEY, qty INTEGER NOT NULL), suppose 'LAMP-01' has quantity 12. A plain insert proposing the same SKU and quantity 20 fails. In SQLite and PostgreSQL:
INSERT INTO stock (sku, qty) VALUES ('LAMP-01', 20)
ON CONFLICT (sku) DO UPDATE
SET qty = stock.qty + excluded.qty;
stock.qty is the existing 12. excluded.qty is the proposed 20. This form stores 32, which fits an extra delivery. SET qty = excluded.qty stores 20, which fits a replacement count. DO NOTHING keeps 12 and skips the conflicting row. Each variation starts from the same original quantity 12.
An upsert combines inserting a new row with updating on a conflict. The conflict target must have suitable uniqueness protection. MySQL's equivalent family is ON DUPLICATE KEY UPDATE. A plain INSERT does not correct an existing customer: it either creates another row or hits a uniqueness error. Use UPDATE for a known existing row, and an upsert when the row might already exist.
Key Takeaways
- INSERT ... SELECT copies the query results into an existing table.
- Re-running a copy needs a deliberate duplicate policy.
- RETURNING reports values from the rows actually written.
- excluded names the proposed row in an upsert.
- Replace stock with excluded.qty; add a delivery with stock.qty + excluded.qty.
- DO NOTHING skips a unique conflict without updating the existing row.
4Change and remove rows with UPDATE and DELETE
A valid data-changing statement can still affect the wrong rows. Updating a price, removing an order and selecting the records to change are separate decisions. You will calculate new values from stored ones, distinguish clearing a field from deleting a row, and build conditions with dates, missing values and grouped alternatives. A preview and a checked result make the effect visible before a transaction makes the change permanent.
4.1 Change values with UPDATE
UPDATE changes values in rows that already exist. It names a table, assigns new values with SET, then chooses the target rows with WHERE. The table's constraints check the resulting values.
Set one or several fields
UPDATE products
SET price = 22.50, in_stock = 30
WHERE id = 3;
The product with key 3 receives both values; other rows keep theirs. Assignments use commas. Writing SET price = 22.50 AND in_stock = 30 does not mean two assignments. PostgreSQL rejects it; SQLite treats the right side as a Boolean expression and can store 0 or 1 in price.
A missing WHERE is valid SQL and applies to every row. UPDATE products SET price = 0; sets every product's price to zero if the table's rules allow it. A preview with the intended condition shows whether the target is one row, several rows or none.
Calculate from the stored value
UPDATE products
SET price = price * 1.10;
A price of 20.00 becomes 22.00 after a 10% increase. A price of 19.99 gives an unrounded result of 21.989. PostgreSQL stores 21.99 in a NUMERIC(8, 2) column. SQLite does not enforce the scale, so SET price = ROUND(price * 1.10, 2) requests two decimal places. For exact SQLite money storage, keep whole cents and make the cents rounding decision explicit.
The expression must include the old value when the change is relative. SET in_stock = in_stock - 1 removes one unit; SET in_stock = 1 replaces the count with one.
In SQLite and PostgreSQL, all right-hand expressions read the row before the update. UPDATE t SET a = b, b = a; swaps a row's values from 1 and 2 to 2 and 1. MySQL's single-table update evaluates assignments left to right, so the same form makes both 2. Do not transfer this assumption between systems.
Keep rules and missing values in view
Use SET phone = NULL to clear a phone field. To find missing phone values, use WHERE phone IS NULL. Equality with NULL does not find them.
Consider prices 12.00, 3.00 and 8.50 protected by CHECK (price > 0). Subtracting 5 from every price would make the second -2.00. With ordinary constraint handling, that failure undoes the whole statement, so even the otherwise valid prices stay unchanged.
A primary key update also has to satisfy related foreign keys. A blocked key change fails while children point to the old key; ON UPDATE CASCADE transfers the new value to them. Keeping generated IDs stable avoids this extra work when descriptive details change.
Interpret the affected-row count
psql reports a result such as UPDATE 3; SQLite's changes() reports the count for the last change statement. Both include rows matched and updated even when the assigned value equals the old value. A count of three does not promise three visibly different values. Compare the count with the preview and inspect the relevant fields afterward, or use RETURNING to see affected rows directly.
Key Takeaways
- SET assigns values; WHERE chooses rows.
- Separate assignments with commas, not AND.
- Use the old column value in a relative calculation.
- An ordinary constraint failure undoes the whole UPDATE statement.
- Affected-row counts can include rows whose values stayed equal.
4.2 Remove rows with DELETE
DELETE removes whole rows. It leaves the table ready to hold rows again. Clearing one field uses UPDATE ... SET column = NULL; DELETE email FROM customers is not valid SQL.
Follow a filtered delete
The shop has these orders:
| ID | Status | Order date |
|---|---|---|
| 201 | cancelled | 2025-11-03 |
| 202 | shipped | 2025-12-19 |
| 203 | cancelled | 2026-01-08 |
| 204 | cancelled | 2025-10-30 |
| 205 | expired | 2026-02-14 |
DELETE FROM orders
WHERE status = 'cancelled'
AND order_date < '2026-01-01';
Order 201 meets both tests, as does 204. Order 203 is cancelled but too recent; 202 is old enough but shipped. The statement removes two rows and leaves 202, 203 and 205. DELETE FROM orders; without the condition would remove all five.
Use related data to choose rows
DELETE FROM order_items
WHERE order_id IN (
SELECT id FROM orders WHERE status = 'cancelled'
);
The inner query produces cancelled order IDs. IN checks each item's order_id against that list. Run the inner query alone to inspect the keys, then preview the item rows with the same condition.
Foreign keys also affect deletion. A parent with referring children can be protected by NO ACTION or RESTRICT; a cascade removes children; SET NULL keeps children but clears the link. Check those actions when judging the effect of deleting a parent, because the directly named table may not be the only table changed.
Choose what should remain
| Statement | Rows removed | What remains |
|---|---|---|
DELETE ... WHERE ... | Matching rows | Table and other rows |
DELETE FROM orders; | All rows | Empty table |
TRUNCATE TABLE orders; | All rows, with no WHERE | Empty table |
DROP TABLE orders; | All rows with the table | No orders table |
PostgreSQL, MySQL, SQL Server and Oracle support TRUNCATE; SQLite does not. Its transaction and dependency rules vary by database. A statement that sounds like a quick emptying operation must still fit those rules.
Keep history with a soft delete
UPDATE customers
SET deleted_at = '2026-03-15'
WHERE id = 7;
A soft delete marks the row without removing it. It can preserve history and allow restoration by clearing the mark. Queries for active customers must include WHERE deleted_at IS NULL; otherwise the marked customers still appear. Marking data does not erase it.
A committed delete cannot be undone by a later ROLLBACK. Restoration needs a saved copy or backup. Within an open transaction, rollback can still restore deleted rows. Check the selected IDs and affected count before making the result permanent.
Key Takeaways
- DELETE removes rows; UPDATE can clear a single field.
- A missing WHERE empties the table while retaining its structure.
- IN can select rows using keys from another table.
- Foreign key actions can extend or block a delete.
- A soft delete requires active-row filters and does not erase the stored data.
- ROLLBACK restores a delete only while its transaction remains uncommitted.
4.3 Target exactly the rows you mean
Choosing rows is often the hardest part of a change. The same filter that is harmless in a query can delete or update too much when reused in a write. Turn the intended rule into a condition, then inspect what it actually matches.
Preview the exact condition
SELECT id, status, order_date FROM orders
WHERE status = 'cancelled'
AND order_date < '2026-01-01';
This preview returns orders 201 and 204 in the five-order shop example. Reuse exactly that condition in the delete. Rewriting it while changing the command creates a new, unchecked selection.
For one specific row, use its primary key: WHERE id = 42 matches at most one row. A descriptive name such as 'Desk lamp' may be shared by several products.
Group alternatives explicitly
AND binds more tightly than OR. Compare:
WHERE status = 'cancelled'
OR status = 'expired' AND order_date < '2026-01-01'
with:
WHERE (status = 'cancelled' OR status = 'expired')
AND order_date < '2026-01-01'
The first accepts every cancelled order, regardless of date, plus expired orders before 2026. It matches 201, 203 and 204. The second applies the date test to both statuses and matches 201 and 204. Order 203 exposes the difference because it is cancelled in 2026.
Include missing values deliberately
WHERE customer_id = NULL matches no rows. Missing values need IS NULL or IS NOT NULL. A negative test also has a gap: status <> 'shipped' does not include a missing status, because its comparison is unknown.
If missing status belongs in the intended group, write:
WHERE status <> 'shipped' OR status IS NULL
When adding another condition, group this whole alternative with parentheses so that both branches meet that condition.
Pattern matching can reach beyond the intended description. LIKE '%lamp%' matches "Desk lamp" and "Clamp". SQLite's default LIKE also ignores case for English letters, so it finds "Lamp shade". A pattern is useful for finding possible matches, but inspect those matches before changing them.
Choose a list comparison for a list
UPDATE orders SET status = 'on hold'
WHERE customer_id IN (
SELECT id FROM customers WHERE state = 'CA'
);
The subquery can supply several customer IDs; IN checks membership. Replacing IN with = asks for one scalar result. PostgreSQL rejects multiple rows there, while SQLite silently uses the first row returned and misses other customers.
After the change, compare the affected count with the preview. PostgreSQL and SQLite can also append RETURNING * to show the affected rows. For a risky write, start a transaction before changing data, inspect the result, then choose COMMIT or ROLLBACK.
Key Takeaways
- Preview exactly the WHERE condition the write will use.
- A primary key filter identifies at most one row.
- Parentheses make OR alternatives share an AND condition.
- IS NULL handles missing values; ordinary comparisons skip them.
- IN accepts a subquery list; scalar equality does not.
- Check the count and result before committing.
5Group changes into transactions
Some changes belong together: a transfer needs both a withdrawal and a deposit. Transactions give those statements one decision to keep or undo, while savepoints let you return to an intermediate point. You will track balances through successful changes and errors, then compare how SQLite and PostgreSQL handle a failed statement. The database and client settings matter, especially when a structure change can commit earlier work.
5.1 Commit or roll back a group of changes
A transaction groups related statements into one unit of work. You decide to keep its changes together with COMMIT or undo them together with ROLLBACK. The all-or-nothing property is called atomicity, the A in ACID.
Keep both sides of a transfer
Maya's account, ID 1, holds 500.00. Leo's account, ID 2, holds 120.00. Moving 75.00 requires two changes:
BEGIN;
UPDATE accounts SET balance = balance - 75.00 WHERE id = 1;
UPDATE accounts SET balance = balance + 75.00 WHERE id = 2;
SELECT id, balance FROM accounts WHERE id IN (1, 2);
COMMIT;
The first update leaves Maya with 425.00. The second leaves Leo with 195.00. The check shows both balances before the commit, and the total stays 620.00. For these exact decimal balances use PostgreSQL NUMERIC; in SQLite an exact-money version stores 50000 and 12000 cents and transfers 7500 cents.
Without an explicit transaction under autocommit, the first update could become permanent before the second runs. A crash between them would keep the withdrawal without the deposit. A transaction gives the two updates one final keep-or-undo decision. If a statement fails, handle the error before deciding that the transfer is complete.
Understand the default boundary
Under database autocommit, each successful standalone statement commits as its own transaction. This is the usual starting behavior for SQLite, PostgreSQL, MySQL and SQL Server. A programming library or client can choose different settings and start a transaction for you.
BEGIN; starts an explicit transaction in SQLite and PostgreSQL. SQLite and SQL Server also use BEGIN TRANSACTION; PostgreSQL and MySQL accept START TRANSACTION. Oracle starts a transaction with a data change and ordinarily waits for COMMIT in tools such as SQL*Plus unless autocommit is enabled.
COMMIT ends the transaction and keeps its changes. ROLLBACK ends it and undoes its uncommitted changes. A later rollback cannot cross an earlier commit.
Inspect and undo
BEGIN;
DELETE FROM orders;
SELECT * FROM orders;
ROLLBACK;
Your connection sees an empty table at the SELECT, then sees the five orders again after rollback. The empty result was real within the transaction, but was not yet permanent. Other connections normally do not see the uncommitted changes.
A transaction can protect a wrong row selection only if you inspect and choose to undo it. SQL can successfully update an unintended row: no database rule knows which account you meant to name. Compare returned IDs, values and counts with the intended result before committing.
Keep transactions short and end them. Open transactions can hold locks that make other connections wait; SQLite permits only one writer at a time. Closing a connection with an unfinished transaction rolls back its pending changes. When a library starts a transaction automatically, the program must call its commit method to keep the work.
Key Takeaways
- Atomicity lets related changes be kept or undone together.
- Autocommit makes a standalone successful statement permanent.
- Your connection can inspect its own pending changes.
- COMMIT and ROLLBACK both end the transaction.
- ROLLBACK cannot undo work from an earlier committed transaction.
5.2 Handle errors, savepoints and structure changes in a transaction
A failed statement does not have the same effect in every database. You need to know what is still pending before deciding how to recover. A savepoint can provide a smaller place to return to inside an open transaction.
Trace an error
After the transfer, Maya has 425.00 and Leo has 195.00. The table has CHECK (balance >= 0). Start a transaction and subtract 30.00 from Maya: her pending balance is 395.00. Then try to subtract 300.00 from Leo. That would make his balance -105.00, so the second statement fails.
| Database | State after the ordinary constraint error |
|---|---|
| SQLite | The failed statement is undone; Maya's earlier change is still pending. |
| PostgreSQL | The transaction is failed, and further ordinary commands are refused. |
In SQLite, committing at this point would keep Maya's deduction alone. In PostgreSQL, COMMIT on the failed transaction ends it with a rollback. A clear recovery routine is to issue ROLLBACK, correct the cause, and rerun the related statements. Both balances then return to 425.00 and 195.00 before the corrected attempt.
Undo a later part with a savepoint
BEGIN;
UPDATE accounts SET balance = balance + 10 WHERE id = 1;
SAVEPOINT after_first;
UPDATE accounts SET balance = balance + 10 WHERE id = 2;
ROLLBACK TO after_first;
COMMIT;
The savepoint comes after Maya's increase. ROLLBACK TO undoes Leo's later increase but preserves Maya's. The final balances are 435.00 and 195.00. It does not end the outer transaction; COMMIT does that.
RELEASE after_first removes the savepoint without undoing the changes after it. It is not an independent permanent commit inside the outer transaction. PostgreSQL also lets ROLLBACK TO a savepoint established before an error clear the failed state and continue from that point.
Check whether structure changes can roll back
SQLite and PostgreSQL allow CREATE TABLE, ALTER TABLE and DROP TABLE inside transactions. For example, BEGIN; DROP TABLE orders; ROLLBACK; restores the table and its rows.
Ordinary permanent-table DDL in MySQL and Oracle causes implicit commits. A CREATE TABLE, ALTER TABLE, DROP TABLE or TRUNCATE TABLE can commit earlier pending changes as well as perform its own structural operation. A later rollback cannot undo that earlier work. Do not assume that a transaction around a script makes all database dialects behave alike.
For a risky data change, begin the transaction, run the write, inspect its values and count, and then commit or roll back. For example, inspect lighting prices after UPDATE products SET price = ROUND(price * 1.10, 2) WHERE category = 'lighting';. Complete the decision promptly so the transaction does not keep other work waiting.
Key Takeaways
- A normal SQLite constraint error leaves earlier transaction changes pending.
- A PostgreSQL error leaves the transaction failed until rollback or recovery to a savepoint.
- ROLLBACK TO undoes later work without ending the transaction.
- RELEASE removes a savepoint without committing the outer transaction.
- DDL rollback support depends on the database.
6Change table structure with ALTER TABLE and DROP TABLE
Tables sometimes need a new field, a clearer name or a stronger rule after they already contain data. Changing that structure requires checking the existing rows and the objects that depend on the table. You will add and fill columns, recognize when SQLite needs a rebuild, and distinguish removing records from removing their container. Dependencies also set the order of work when related tables are retired.
6.1 Add, rename and remove columns with ALTER TABLE
ALTER TABLE changes an existing table's structure while preserving its rows. UPDATE changes the values those rows hold. Adding a discount field needs ALTER TABLE; raising the discounts already stored needs UPDATE.
Add and rename fields
ALTER TABLE customers ADD COLUMN phone TEXT;
ALTER TABLE customers RENAME COLUMN phone TO mobile;
ALTER TABLE customers RENAME TO clients;
SQLite and PostgreSQL share these forms. A new column gives existing rows its default, or NULL when there is none. ADD COLUMN active INTEGER NOT NULL DEFAULT 1 gives each current row an active value of 1. Adding email TEXT NOT NULL with no default fails on a populated table because the existing rows would lack a required value.
SQLite supports RENAME COLUMN from 3.25.0 and DROP COLUMN from 3.35.0. When renaming, update application code and saved queries that refer to the old name. SQL outside the database cannot discover the new name automatically.
Fill data before requiring it
In PostgreSQL, add a nullable column, fill the existing rows with suitable values, and only then require it:
ALTER TABLE customers ADD COLUMN email TEXT;
UPDATE customers SET email = 'unknown@example.com'
WHERE email IS NULL;
ALTER TABLE customers ALTER COLUMN email SET NOT NULL;
This illustrates filling a required field; it does not declare email unique. A real unique email column needs distinct valid values. SET NOT NULL fails if any missing values remain. PostgreSQL also supports changing a type with ALTER COLUMN price TYPE NUMERIC(10, 2), setting a default and adding a named check. Existing rows must satisfy a newly added rule.
Remove a field or rebuild a table
ALTER TABLE customers DROP COLUMN mobile; removes the field and all its stored values. Dependencies can block the operation. SQLite rejects dropping a primary-key, unique, indexed or foreign-key column, or one used by another schema object. PostgreSQL can remove dependent objects with CASCADE, including a view that uses the column.
SQLite cannot change an existing column's type or add primary-key, unique, foreign-key or check constraints to existing columns with a simple ALTER. Version 3.53.0 adds ALTER COLUMN ... SET NOT NULL and DROP NOT NULL, not general constraint editing. A newly added column can have a check or permitted reference, but cannot have PRIMARY KEY or UNIQUE; with foreign keys on, an added reference needs a NULL default.
For a broader SQLite change:
- Record the indexes, triggers and views that must be recreated. If foreign keys are on, switch them off before the transaction.
- Start a transaction and create a new table with the desired rules.
- Copy rows using explicit columns in
INSERT INTO new_products (...) SELECT ... FROM products;. - Drop the old table and rename the new one to
products. - Recreate dependent objects and run
PRAGMA foreign_key_check;. Resolve any reported problems before committing. - Commit and restore foreign key enforcement afterward if it was on before.
MySQL changes definitions with MODIFY COLUMN; SQL Server uses ALTER COLUMN. Translate a structural change into the target database's supported syntax before running it.
Key Takeaways
- ALTER TABLE changes structure; UPDATE changes stored values.
- Added columns give existing rows a default or NULL.
- Fill a required new column before adding NOT NULL.
- Dropping a column removes its values and can be blocked by dependencies.
- SQLite needs a rebuild for many type and constraint changes.
- Rebuilds must preserve dependent objects and check foreign keys.
6.2 Remove tables with DROP TABLE
DROP TABLE removes the container as well as its contents. After DROP TABLE archived_orders;, queries cannot read that table because it no longer exists. Its constraints, indexes and triggers are removed with it.
Choose the level of removal
| Intended result | Statement |
|---|---|
| Keep the table and remove selected rows | DELETE FROM orders WHERE ...; |
| Keep an empty table | DELETE FROM orders; |
| Quickly empty a table where supported | TRUNCATE TABLE orders; |
| Remove the table itself | DROP TABLE orders; |
DROP TABLE IF EXISTS archived_orders; avoids an error if the table is absent. It still removes an existing table and its data. A setup script that drops and recreates tables is destructive when run on a database containing records you want to keep.
Respect links between tables
Drop children before parents, reversing the usual creation order. In PostgreSQL, dropping customers while an order foreign key depends on it fails. DROP TABLE customers CASCADE; also removes the dependent foreign key and any dependent views. The order table and its rows remain, but the customer relationship is no longer enforced.
This PostgreSQL CASCADE is different from a foreign key's ON DELETE CASCADE. The first removes dependent database objects during a structure change. The second removes child rows when a parent row is deleted. Knowing which action is in use prevents an incorrect prediction about the remaining data.
With SQLite foreign key enforcement on, dropping a table first performs an implicit deletion of its rows. Referring children can block it. If the relationship declares ON DELETE CASCADE, those child rows can be deleted; SET NULL can clear their links. A drop therefore needs the same attention to references as a delete, as well as attention to the table structure being removed.
Preserve a recovery path
Before removing or rebuilding a table with real data, preserve a usable copy. A quick internal data copy is:
CREATE TABLE products_backup AS
SELECT * FROM products;
In SQLite and PostgreSQL, this preserves the selected data but not the original constraints. It does not serve as a full reconstruction of the table definition. An export or database backup keeps recovery data outside the table being changed.
SQLite and PostgreSQL also allow a drop to be tested inside a transaction:
BEGIN;
DROP TABLE archived_orders;
ROLLBACK;
The rollback restores the table and its rows because the drop was not committed. Once committed, the drop cannot be reversed by issuing ROLLBACK later. In databases with implicit DDL commits, wrapping the drop in transaction syntax does not supply this undo opportunity. Inspect the target database and the tables involved before running a rebuilding script.
Key Takeaways
- DROP TABLE removes the table and its rows, constraints, indexes and triggers.
- IF EXISTS suppresses an absence error, not an existing table deletion.
- Drop child tables before their parents.
- PostgreSQL DROP CASCADE removes dependent objects, while child tables can remain.
- A data-only copy does not preserve the original constraints.
SQL CREATE TABLE examples
1Build and inspect a product table
11 steps
- 1IntroductionStart with one product
A shop needs to record a product's ID, name, price and stock count. A table definition gives each fact its own field before any product rows are added.
We will build the structure, inspect it and then check whether the types preserve what the values mean. The examples use SQLite and name the PostgreSQL changes where they affect the result.
- 2ExplanationRead a column definition
In
name TEXT NOT NULL,nameis the field's identifier,TEXTdescribes its storage andNOT NULLrequires a value.The table lists these definitions inside parentheses, separated by commas. The semicolon ends the creation statement. Creating the structure is DDL; it does not add any product rows. A later
INSERTsupplies those rows. - 3ExampleCreate four fields
CREATE TABLE products ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, price NUMERIC(8, 2) NOT NULL, in_stock INTEGER DEFAULT 0 );There are four column definitions and three separating commas. SQLite generates this integer primary key. PostgreSQL needs an identity declaration if an omitted ID should be generated.
- 4TipInspect the actual definition
Use
PRAGMA table_info(products);in SQLite SQL or.schema productsin its command-line shell. In PostgreSQL psql, use\d products.Inspection catches more than syntax errors. SQLite can read a missing comma as part of a multiword type name, leaving one column where you expected two.
SELECT * FROM products;shows an empty table, but its zero rows do not verify every rule. - 5ChallengeWill this repair an old table?
An older
productstable has onlyidandname. A script runsCREATE TABLE IF NOT EXISTS products (...)with four desired columns. How many columns does the existing table have afterward?---reveal---
It still has two.
IF NOT EXISTSskips creation when the name is present. It does not compare the definitions or add the missing fields. A structural change needsALTER TABLEor a deliberate rebuild. - 6ExplanationSeparate quantities from codes
Store quantities as numbers so that arithmetic and numeric sorting work. Store codes as text when their characters carry meaning.
ZIP code
'02139'loses its leading zero as an integer. Text values'100','25'and'9'sort in that character order, which is wrong for numerical quantities. The decision follows how the field will be used, not whether its examples contain digits. - 7ExampleRead precision and scale
PostgreSQL
NUMERIC(8, 2)allows eight total digits, with two after the point. That leaves six before it, up to 999999.99.A value of 12.345 is stored as 12.35. A value of 1234567.00 fails because it needs seven digits before the point. SQLite accepts the same declaration but does not enforce this precision or scale, so that prediction does not transfer to SQLite.
- 8Key InsightKeep exact money exact
Binary floating point can introduce tiny differences into decimal arithmetic. SQLite's
SELECT 0.1 + 0.2 = 0.3;returns false.Use PostgreSQL
NUMERICfor exact decimal amounts. In SQLite, store whole cents as integers: 1999 represents 19.99. WritingNUMERIC(8, 2)in SQLite does not create fixed-scale exact decimal storage. Approximate types still suit measurements such as temperatures. - 9ChallengeKeep dates in order
A SQLite table stores dates as text. Why does changing
'12/01/2025'and'03/15/2026'to ISO dates help an ascending date sort?---reveal---
The ISO forms are
'2025-12-01'and'2026-03-15'. Text comparison then checks year, month and day in that order, putting the earlier date first. Month-first text compares the month before it reaches the year and puts these two in the wrong date order. - 10ChallengeCheck the table and the type
A SQLite script creates a missing table with
CREATE TABLE IF NOT EXISTS readings (qty INTEGER);. An insert supplies'abc'as qty and succeeds. What should you check before assuming qty always holds numbers?---reveal---
Inspect the actual definition, because
IF NOT EXISTScould have skipped an older table. Also account for ordinary SQLite affinity: an integer declaration can still store nonnumeric text. A suitableSTRICTtable strengthens type enforcement. - 11RecapMatch structure to use
A creation statement defines fields, types and rules; an insert supplies the records. Inspect the structure that exists instead of assuming a successful script created the definition you intended.
For each field, ask what must be preserved: numeric arithmetic, exact cents, identifier characters or chronological order. Apply the target database's type behavior when predicting the stored result.
2Protect products and customer links
13 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionCheck data at the database
- 2ExplanationGive each rule one job
- 3ExampleFollow one accepted row
- 4ChallengeDoes a default fix explicit NULL?
- 5Key InsightAn unknown check result passes
- 6ExplanationIdentify the whole row
- 7ExampleLet the system choose the ID
- 8ChallengeIs the next ID a row count?
- 9ExplanationMake each order find its customer
- 10ExampleTrace a cascade
- 11ChallengeKeep the orders after removal
- 12ChallengeCombine the required rules
- 13RecapCheck values, identity and links
3Insert products and copy orders
11 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionChoose where new rows come from
- 2ExampleMap one product
- 3TipKeep literal values intact
- 4ChallengeRead the explicit list
- 5ExplanationAdd a group as one statement
- 6ExamplePreview the archive query
- 7ExampleUse the query as input
- 8ExplanationName the proposed row
- 9ChallengeKeep or replace?
- 10ChallengeCopy and link without guessing
- 11RecapFollow the input through the write
4Change exactly the intended rows
14 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionSeparate what changes from which rows
- 2ExampleAssign two new values
- 3ExampleRaise a price from its old value
- 4ChallengeWill the other prices change?
- 5ExplanationRemove a row or clear a field
- 6ExampleTrace the old cancelled orders
- 7ChallengeKeep a deleted customer's history
- 8TipReuse the preview condition
- 9ExampleApply AND before OR
- 10ChallengeApply one date rule to both statuses
- 11Key InsightA negative comparison misses NULL
- 12ChallengeUse a list of customer keys
- 13ChallengeInspect an all-row update
- 14RecapCheck meaning and effect
5Keep or undo a complete change
12 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionOne transfer needs two changes
- 2ExplanationOpen a shared boundary
- 3ExampleSubtract the transfer amount
- 4ExampleAdd and inspect the deposit
- 5ChallengeUndo the whole pending transfer
- 6ExampleTrack a failed balance change
- 7ExplanationRecover according to the database
- 8ChallengeAvoid keeping an incomplete operation
- 9ExampleMark an intermediate point
- 10Key InsightReleasing is not an outer commit
- 11ChallengeCan this drop be undone?
- 12RecapEnd at a deliberate decision
6Change or remove a populated table
13 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionWork with records already present
- 2ExampleAdd a nullable field
- 3ChallengeRequire a field after filling it
- 4TipRename every external use
- 5ExplanationRecognize a limited ALTER
- 6ExamplePrepare the replacement
- 7ExampleCopy, replace and check
- 8ExplanationDecide whether the table should stay
- 9ExampleTrace PostgreSQL dependencies
- 10ChallengeSeparate the two cascade meanings
- 11TipKeep data and definition recoverable
- 12ChallengeKeep rules through a rebuild
- 13RecapFollow the structure and its dependencies
SQL CREATE TABLE practice questions
Set 1: Trace the stored results10 Practice Questions
Question 1 of 10
NUMERIC(5, 2). What happens to an inserted value of 48.726?Show answerHide answer
Question 2 of 10
UNIQUE (room_id, start_time). It contains (4, '09:00') and (5, '09:00'). What happens when (4, '10:00') is inserted?Show answerHide answer
Question 3 of 10
code TEXT PRIMARY KEY. What extra rule closes SQLite's historical missing-key exception?Show answerHide answer
NOT NULL prevents those missing keys. Another uniqueness rule does not solve missingness, and comparing a missing value with itself produces an unknown result that a check can accept.Question 4 of 10
Show answerHide answer
ON CONFLICT would be a deliberate different policy, but this statement has no such action.Question 5 of 10
qty INTEGER NOT NULL DEFAULT 6. What insert adds a brush using that default?Show answerHide answer
NULL would violate the presence rule. Although some databases allow DEFAULT in a value list, SQLite does not, so omission is the supported form here.Question 6 of 10
Show answerHide answer
NULL clears only the matched customer's phone field. Quoting it would store the four-letter text "NULL" instead of a missing value. Deleting the row loses the customer, and dropping the column removes phone values for every customer.Question 7 of 10
state = 'closed' OR state = 'expired' AND opened < '2026-01-01'?Show answerHide answer
AND groups the expired status with the date test. Every closed case qualifies regardless of date, so both 71 and 73 match, along with old expired case 74. Selecting only 71 and 74 would apply the date restriction to both statuses, which requires parentheses around the alternatives.Question 8 of 10
Show answerHide answer
Question 9 of 10
ROLLBACK TO s, and commits. What remains?Show answerHide answer
ROLLBACK TO does not cross the savepoint.Question 10 of 10
DROP TABLE clients CASCADE; succeeds. What can remain of invoices?Show answerHide answer
ON DELETE CASCADE would incorrectly predict automatic child-row deletion.Set 2: Choose a reliable sequence10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
CREATE TABLE t (a INTEGER b TEXT); succeeds. Inspection shows only column a. Why?Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
COUNT(*) return?Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
ON CONFLICT (code) DO UPDATE SET qty = stock.qty + excluded.qty. The same statement then runs again. What qty is stored?Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
CREATE TABLE saved AS SELECT * FROM original;. What should be checked before treating saved as a full replacement?Show answerHide answer
Save this lesson to see this answer.
Set 3: Apply the rules to combined changes10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
value NUMERIC(7, 2). Inserting 6.789 succeeds. What stored value should you expect from this declaration alone?Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
label TEXT NOT NULL with no other rules. What happens when the supplied value is the empty string?Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
parent_id INTEGER DEFAULT 90 REFERENCES parents(id) ON DELETE SET DEFAULT. Parent 90 does not exist. Child 12 points to parent 7. What happens when parent 7 is deleted?Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
DO UPDATE SET qty = excluded.qty. What quantity is stored after both imports, and what total would correct addition have produced?Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
customer_id = (SELECT id FROM customers WHERE eligible = TRUE). What happens, and what comparison fixes it?Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Show answerHide answer
Save this lesson to see this answer.
Set 4: Diagnose common SQL mistakes10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
labels already has only id and text columns. A script runs CREATE TABLE IF NOT EXISTS labels (id INTEGER PRIMARY KEY, text TEXT, color TEXT);. What happens to color?Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
CREATE TABLE tickets (id INTEGER PRIMARY KEY, label TEXT);. No other statements run. What exists afterward?Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
CHECK (score BETWEEN 0 AND 100). An insert supplies NULL. What happens?Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
ON DELETE SET NULL, and project_id is also NOT NULL. Foreign keys are enforced. What happens when the project is deleted?Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
CHECK (balance >= 0). Under ordinary constraint handling, what would COMMIT keep?Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
active INTEGER NOT NULL DEFAULT 1. What active value do its existing rows have?Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Show answerHide answer
Save this lesson to see this answer.
Set 5: Build tables and make first changes10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
DELETE FROM logs; succeeds. What remains?Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
BEGIN, changes a quantity from 8 to 11, then runs ROLLBACK. What quantity does that connection read afterward?Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
00481 exactly. What storage type fits?Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
NUMERIC(6, 2). What is its largest positive value?Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
qty INTEGER NOT NULL DEFAULT 4 CHECK (qty >= 0). An insert explicitly supplies NULL for qty. What happens?Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
supplier_id = 18, but supplier 18 does not exist. What must be done to insert that same non-NULL reference successfully?Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
(code, name) with values ('R7', 'Notebook'). What is stored in name?Show answerHide answer
Save this lesson to see this answer.
Stuck on your own question? Homework Help
Key Terms
A
ADD COLUMN
active INTEGER NOT NULL DEFAULT 1 gives existing customers the new value 1 without separate inserts.Affected-row count
ALTER TABLE
ALTER TABLE; filling phone values later uses UPDATE.Atomicity
Autocommit
AUTOINCREMENT
C
CHECK
CHECK (price > 0) rejects a negative price but needs NOT NULL as well when a price must be supplied.Child table
Column list
(sku, name, price) makes the first supplied value a SKU even if price was declared earlier in the table.COMMIT
Composite key
Constraint
CREATE TABLE
CREATE TABLE labels (id INTEGER PRIMARY KEY, name TEXT); prepares a table before labels are inserted.CREATE TABLE AS
cheap_products copy holds the products selected when it was created, even after source prices change.D
Data definition language
CREATE TABLE, ALTER TABLE and DROP TABLE change the database's structure at different stages.Data manipulation language
INSERT; correcting its status uses UPDATE; removing its row uses DELETE.Data type
DEFAULT
DEFAULT 0 gives zero, while explicitly supplying NULL keeps it missing.DELETE
Dependency
DROP CASCADE
DROP COLUMN
DROP TABLE
E
excluded
stock.qty + excluded.qty adds the proposed incoming amount to the quantity already stored.F
Failed transaction
Floating point
0.1 + 0.2 = 0.3 as false because its decimal literals use approximate storage.Foreign key
Foreign key enforcement
PRAGMA foreign_keys = ON; on the connection before a transaction to check its declared links.I
Identifier
"order date" needs quotes, while order_date avoids spaces and is easier to reuse.Identity column
INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY supplies new IDs without a manual number in each insert.IF EXISTS
DROP TABLE IF EXISTS archived_orders; succeeds when the table is absent and removes it when present.Implicit commit
IN
INSERT
INSERT SELECT
INTEGER
IS NULL
WHERE phone IS NULL finds customers without a phone number; WHERE phone = NULL finds no such rows.ISO date
'2026-03-15' sort by year, month and day when they all use the same format.M
Multi-row insert
N
Named constraint
CONSTRAINT price_positive CHECK (price > 0) gives the positive-price rule a name for later maintenance.Natural key
NOT NULL
NULL
NULL when no phone number is known; IS NULL finds those customers.NUMERIC precision
NUMERIC(8, 2) allows six digits before the decimal point and two after it, up to 999999.99.O
ON DELETE CASCADE
ON DELETE SET NULL
Operator precedence
P
Parent table
Primary key
R
RELEASE
RETURNING
ROLLBACK
ROLLBACK TO
after_first, set between two balance increases, removes the later increase but keeps the earlier one pending.ROUND
ROUND(price * 1.10, 2) requests a two-place result for a SQLite price calculation that would produce 21.989.S
Savepoint
Scale
NUMERIC(8, 2) column stores 12.345 as 12.35 because the declared scale is two.SET assignment
SET in_stock = in_stock - 1 decreases the stored count, while SET in_stock = 1 replaces it with one.Soft delete
deleted_at retains the row, so active customer queries add deleted_at IS NULL.Staging table
NULL through NULLIF(TRIM(email), '') during the copy query.String literal
'O''Brien' stores the name "O'Brien" with one apostrophe instead of ending the value early.Subquery
order_id IN (SELECT id FROM orders WHERE status = 'cancelled') to choose related items.Surrogate key
T
Table rebuild
Transaction
TRUNCATE
TRUNCATE TABLE, but its dependency and transaction rules still matter.Type affinity
'5' to 5 but can keep 'abc' as text without an error.U
UNIQUE
UNIQUE (order_id, product_id) allows the same product in different orders but rejects a repeated pair.UPDATE
UPDATE products SET price = 22.50 WHERE id = 3; changes one product without inserting a new product row.Upsert
ON CONFLICT (sku) DO UPDATE when it exists.W
WHERE condition
- 1Relational Database Model
- 2SQL SELECT
- 3SQL CASE and Functions
- 4SQL GROUP BY
- 5SQL Joins
- 6SQL CREATE TABLE
About this lesson
Make your own Study Materials
Turn your notes, slides and documents into Study Guides, Practice Questions, Flashcards and 6 more tools.