All A Level 13 65 min read 10 question quiz 20 exam marks

Relational Databases, Normalisation and SQL

Why a flat file goes wrong, normalisation to third normal form worked through on real data, then joins, grouping and constraints in SQL that runs, and transactions with ACID, with a failed transfer rolled back in front of you.

Flat files, keys and relationships

A flat file keeps everything in one table. It is easy to start with and goes wrong in a predictable way: facts get repeated.

OrderIDCustomerCityProductPriceQuantity
1Asha KhanLeedsKeyboard25.001
1Asha KhanLeedsMouse12.502
2Ben AliYorkKeyboard25.003
3Asha KhanLeedsMonitor120.001

Asha's city is stored three times and the price of a keyboard twice. Every repeat is a chance for the copies to disagree:

Python
import sqlite3

db = sqlite3.connect(":memory:")
db.execute("CREATE TABLE Orders (OrderID INTEGER, Customer TEXT, City TEXT, Product TEXT, Price REAL, Quantity INTEGER)")
db.executemany("INSERT INTO Orders VALUES (?, ?, ?, ?, ?, ?)", [
    (1, "Asha Khan", "Leeds", "Keyboard", 25.0, 1),
    (1, "Asha Khan", "Leeds", "Mouse", 12.5, 2),
    (2, "Ben Ali", "York", "Keyboard", 25.0, 3),
    (3, "Asha Khan", "Leeds", "Monitor", 120.0, 1),
])

# Asha moves to Bradford, but the update only reaches one of her three rows
db.execute("UPDATE Orders SET City = 'Bradford' WHERE OrderID = 1 AND Product = 'Keyboard'")

for row in db.execute("SELECT DISTINCT Customer, City FROM Orders ORDER BY Customer, City"):
    print(row)

The database now says that Asha lives in Bradford and in Leeds. Three kinds of fault come from the repetition:

AnomalyWhat goes wrongExample
UpdateChanging a fact in some places and not othersAsha's city
InsertionYou cannot record a fact without an unrelated oneA new product cannot be added until somebody orders it
DeletionDeleting one fact destroys anotherDeleting order 3 removes the only record of the monitor and its price

The relational answer

A relational database splits the data into several tables, each about one thing, and links them with keys.

TermMeaning
EntityA thing the database holds facts about, such as a customer
AttributeOne fact about an entity, which is a column
Record (tuple)One entity's row
Primary keyAttribute(s) whose value is unique for each record
Composite keyA primary key made from two or more attributes together
Foreign keyAn attribute holding the primary key of another table, which makes the link
Secondary keyAn attribute indexed for searching, though not unique
Referential integrityEvery foreign key value matches a primary key that really exists

Relationships between entities

An entity relationship diagramAn entity relationship diagram with four tables. Customer has the primary key CustomerID, a name and a city. CustomerOrder has the primary key OrderID, the foreign key CustomerID, and an order date. OrderLine has a primary key made of the two foreign keys OrderID and ProductID, and a quantity. Product has the primary key ProductID, a name and a price. One customer places many orders. One order contains many order lines. One product appears on many order lines. OrderLine is the link table that resolves the many to many relationship between orders and products.Entity relationship diagram for the shop databaseCustomerPKCustomerIDNameCityCustomerOrderPKOrderIDFKCustomerIDOrderDateOrderLinePK FKOrderIDPK FKProductIDQuantityProductPKProductIDNamePriceOne customer places many orders. One order contains many order lines. One product appears on many order lines.OrderLine is the link table that resolves the many to many relationship between orders and products.exactly onemanyPK primary key, FK foreign key
The crow's foot goes on the many side, which is where the foreign key lives.

A one to many relationship, one customer has many orders, is made with a foreign key on the many side. A many to many relationship, an order contains many products and a product is on many orders, cannot be held directly. It is split by a link table that has a foreign key to each side, OrderLine, whose primary key is the pair together.

Normalisation

Normalisation is the process of organising the tables to remove the repetition, and with it the three anomalies. It goes in stages, each one a normal form, and each includes the one before.

Normalisation to third normal formNormalising a shop's order data in three stages. In first normal form there is a single table, Orders, with the primary key OrderID and ProductID and the attributes OrderDate, CustomerID, CustomerName, Address, ProductName, Price and Quantity. For second normal form, the facts that depend on only part of the key are moved out: Product holds ProductID, ProductName and Price, Order holds OrderID, OrderDate and the customer's details, and OrderLine holds OrderID, ProductID and Quantity. For third normal form, the customer's name and address, which depend on CustomerID and not on the order, are moved into a Customer table, and Order keeps CustomerID as a foreign key. The result is four tables, Customer, Order, Product and OrderLine.Normalisation, one form at a timeOrders (1NF: every cell holds one value, and there is a primary key)PK OrderID PK ProductID OrderDate CustomerID CustomerName Address ProductName Price Quantity2NF: Price and ProductName depend only on ProductID;OrderDate and the customer's details only on OrderID.OrderPK OrderIDOrderDateCustomerIDCustomerNameAddressProductPK ProductIDProductNamePriceOrderLinePK FK OrderIDPK FK ProductIDQuantity3NF: CustomerName and Address depend onCustomerID, which is not the key of Order.CustomerPK CustomerIDCustomerNameAddressOrderPK OrderIDOrderDateFK CustomerIDProductPK ProductIDProductNamePriceOrderLinePK FK OrderIDPK FK ProductIDQuantityEvery fact is now stored once: the key, the whole key, and nothing but the key.
Each stage removes one kind of repeated data.
FormThe ruleWhat is removed
1NFEvery cell holds one value, there are no repeating groups, and there is a primary keyLists inside a cell, repeated columns such as Product1, Product2
2NFIn 1NF, and every non key attribute depends on the whole keyPartial dependencies: facts about only part of a composite key
3NFIn 2NF, and no non key attribute depends on another non key attributeTransitive dependencies: a fact about a fact

A phrase that holds all three: every attribute depends on the key, the whole key, and nothing but the key.

Worked example

The flat table above has no repeating groups, once the primary key is the pair OrderID and Product, so it is in 1NF. Now work out what each attribute really depends on.

  • Quantity depends on both parts of the key: how many of this product on this order.
  • Price depends only on Product. That is a partial dependency.
  • Customer and City depend only on OrderID. That is another.

To reach 2NF, move those out into tables of their own. Now the order's facts and the product's facts are each held once.

  • City depends on Customer, and Customer depends on OrderID. A fact about a fact is a transitive dependency.

To reach 3NF, give the customer a table of their own, and let the order refer to it with a foreign key. The finished design is four tables:

TableColumnsKey
CustomerCustomerID, Name, CityCustomerID
ProductProductID, Name, PriceProductID
CustomerOrderOrderID, CustomerIDOrderID, with CustomerID as a foreign key
OrderLineOrderID, ProductID, QuantityOrderID and ProductID together, both foreign keys

Asha's city is stored once. Changing it is one update, and every order sees it.

Building it in SQL

Python
import sqlite3

db = sqlite3.connect(":memory:")
db.execute("PRAGMA foreign_keys = ON")
db.executescript('''
CREATE TABLE Customer (CustomerID INTEGER PRIMARY KEY, Name TEXT NOT NULL, City TEXT);
CREATE TABLE Product (ProductID INTEGER PRIMARY KEY, Name TEXT NOT NULL, Price REAL CHECK (Price > 0));
CREATE TABLE CustomerOrder (
    OrderID INTEGER PRIMARY KEY,
    CustomerID INTEGER NOT NULL REFERENCES Customer(CustomerID));
CREATE TABLE OrderLine (
    OrderID INTEGER REFERENCES CustomerOrder(OrderID),
    ProductID INTEGER REFERENCES Product(ProductID),
    Quantity INTEGER NOT NULL,
    PRIMARY KEY (OrderID, ProductID));
''')

db.executemany("INSERT INTO Customer VALUES (?, ?, ?)", [(1, "Asha Khan", "Leeds"), (2, "Ben Ali", "York")])
db.executemany("INSERT INTO Product VALUES (?, ?, ?)", [(1, "Keyboard", 25.0), (2, "Mouse", 12.5), (3, "Monitor", 120.0)])
db.executemany("INSERT INTO CustomerOrder VALUES (?, ?)", [(1, 1), (2, 2), (3, 1)])
db.executemany("INSERT INTO OrderLine VALUES (?, ?, ?)", [(1, 1, 1), (1, 2, 2), (2, 1, 3), (3, 3, 1)])

db.execute("UPDATE Customer SET City = 'Bradford' WHERE CustomerID = 1")      # one change
for row in db.execute("SELECT CustomerID, Name, City FROM Customer"):
    print(row)

# referential integrity: an order for a customer that does not exist is refused
try:
    db.execute("INSERT INTO CustomerOrder VALUES (4, 99)")
except sqlite3.IntegrityError as error:
    print("refused:", error)

# a CHECK constraint refuses a price that makes no sense
try:
    db.execute("INSERT INTO Product VALUES (4, 'Faulty', -5)")
except sqlite3.IntegrityError as error:
    print("refused a negative price:", type(error).__name__)

The constraints do the work that the programmer would otherwise have to remember: PRIMARY KEY and NOT NULL for entity integrity, REFERENCES for referential integrity, and CHECK for domain rules.

SQL at A Level: joins, grouping and aggregates

With the data in separate tables, a query puts it back together with a JOIN. JOIN ... ON pairs rows of two tables where the key matches.

The queries below all run against the four table design. The setup is repeated so that the program runs on its own.

Python
import sqlite3

db = sqlite3.connect(":memory:")
db.executescript('''
CREATE TABLE Customer (CustomerID INTEGER PRIMARY KEY, Name TEXT, City TEXT);
CREATE TABLE Product (ProductID INTEGER PRIMARY KEY, Name TEXT, Price REAL);
CREATE TABLE CustomerOrder (OrderID INTEGER PRIMARY KEY, CustomerID INTEGER);
CREATE TABLE OrderLine (OrderID INTEGER, ProductID INTEGER, Quantity INTEGER);
INSERT INTO Customer VALUES (1, 'Asha Khan', 'Leeds'), (2, 'Ben Ali', 'York');
INSERT INTO Product VALUES (1, 'Keyboard', 25.0), (2, 'Mouse', 12.5), (3, 'Monitor', 120.0);
INSERT INTO CustomerOrder VALUES (1, 1), (2, 2), (3, 1);
INSERT INTO OrderLine VALUES (1, 1, 1), (1, 2, 2), (2, 1, 3), (3, 3, 1);
''')

def run(sql):
    print(" ".join(sql.split()))
    for row in db.execute(sql):
        print("    ", row)

run('''SELECT Customer.Name, Product.Name, OrderLine.Quantity
       FROM OrderLine
       JOIN CustomerOrder ON OrderLine.OrderID = CustomerOrder.OrderID
       JOIN Customer ON CustomerOrder.CustomerID = Customer.CustomerID
       JOIN Product ON OrderLine.ProductID = Product.ProductID
       WHERE Customer.City = 'Leeds'
       ORDER BY Product.Name''')

run('''SELECT CustomerOrder.OrderID, SUM(Product.Price * OrderLine.Quantity) AS Total
       FROM CustomerOrder
       JOIN OrderLine ON CustomerOrder.OrderID = OrderLine.OrderID
       JOIN Product ON OrderLine.ProductID = Product.ProductID
       GROUP BY CustomerOrder.OrderID''')

run('''SELECT Customer.Name, SUM(Product.Price * OrderLine.Quantity)
       FROM Customer
       JOIN CustomerOrder ON Customer.CustomerID = CustomerOrder.CustomerID
       JOIN OrderLine ON CustomerOrder.OrderID = OrderLine.OrderID
       JOIN Product ON OrderLine.ProductID = Product.ProductID
       GROUP BY Customer.CustomerID
       HAVING SUM(Product.Price * OrderLine.Quantity) > 100''')

run("SELECT COUNT(*), AVG(Price), MIN(Price), MAX(Price) FROM Product")
run("SELECT Name FROM Product WHERE Price BETWEEN 10 AND 30 AND Name LIKE '%o%'")
run("SELECT Name FROM Customer WHERE CustomerID IN (SELECT CustomerID FROM CustomerOrder WHERE OrderID = 2)")
ClauseDoes
JOIN ... ON a = bCombines rows from two tables where the keys match
GROUP BYCollapses rows that share a value into one row per group
COUNT, SUM, AVG, MIN, MAXAggregate functions: one answer per group
HAVINGFilters groups, after the aggregates are worked out
WHEREFilters rows, before grouping
BETWEEN, IN, LIKERange, a list of values, and a pattern
A SELECT inside anotherA subquery
WHERE before the groups, HAVING after them

WHERE Price > 20 removes products before anything is totalled. HAVING SUM(...) > 100 removes customers after their totals are worked out, which a WHERE cannot do because the totals do not exist yet.

Changing data safely

OCR reference language
UPDATE Product SET Price = Price * 1.1 WHERE Name = 'Keyboard'
DELETE FROM OrderLine WHERE OrderID = 3
INSERT INTO Product (ProductID, Name, Price) VALUES (4, 'Webcam', 39.99)

Always use parameters, never joined text, when a value comes from a user. You saw this at GCSE as a defence against SQL injection, and it is still the rule: db.execute("SELECT ... WHERE Name = ?", (name,)).

Transactions and ACID

Moving £30 from one account to another takes two updates: take it from one, add it to the other. If the program stops between the two, £30 has disappeared. A transaction is a group of operations treated as one unit, that either all happen or none do.

The four properties a database guarantees are ACID:

PropertyMeansIn the transfer
AtomicityAll of the transaction, or none of itBoth updates happen, or neither does
ConsistencyThe database goes from one valid state to anotherThe money in the system is the same before and after
IsolationTransactions running at once do not interfereAnother transfer cannot see the half done one
DurabilityOnce committed, the change survives a crashA power cut after the commit does not lose it

commit() makes the transaction permanent. rollback() undoes everything since it began.

Python
import sqlite3

db = sqlite3.connect(":memory:")
db.execute("CREATE TABLE Account (ID INTEGER PRIMARY KEY, Owner TEXT, Balance REAL CHECK (Balance >= 0))")
db.executemany("INSERT INTO Account VALUES (?, ?, ?)", [(1, "Asha", 100.0), (2, "Ben", 50.0)])
db.commit()

def balances():
    return [row[0] for row in db.execute("SELECT Balance FROM Account ORDER BY ID")]

def transfer(source, target, amount):
    try:
        db.execute("UPDATE Account SET Balance = Balance + ? WHERE ID = ?", (amount, target))
        db.execute("UPDATE Account SET Balance = Balance - ? WHERE ID = ?", (amount, source))
        db.commit()
        return "committed"
    except sqlite3.IntegrityError:
        db.rollback()
        return "rolled back"

print(transfer(1, 2, 30), balances())
print(transfer(1, 2, 500), balances())

In the second transfer, the first statement, crediting Ben with £500, succeeded. The second statement, taking £500 from Asha, was refused by the CHECK, because she only has £70. Without a transaction Ben would now hold £550 that came from nowhere. The rollback undid the credit, and the balances are as they were.

Concurrency and locking

When two users change the same record at once, one change can overwrite the other. The database prevents it with record locking: the first transaction to touch a record locks it, and others wait. Two transactions each waiting for a lock that the other holds are in deadlock, and the database resolves it by rolling one back.

Indexes

An index is a separate sorted structure on a column, usually a tree, which lets the database find rows by that column without reading the whole table. It makes reads faster and writes slower, because every insert must update the index, and it costs storage. Primary keys are indexed automatically, and you add an index to a column you search often.

Python
import sqlite3

db = sqlite3.connect(":memory:")
db.execute("CREATE TABLE Person (ID INTEGER PRIMARY KEY, Surname TEXT)")
db.executemany("INSERT INTO Person (Surname) VALUES (?)", [("Khan",), ("Ali",), ("Smith",), ("Khan",)])
db.execute("CREATE INDEX idx_surname ON Person (Surname)")
print(db.execute("SELECT COUNT(*) FROM Person WHERE Surname = 'Khan'").fetchone()[0])
The four ACID properties of a database transaction: atomicity, consistency, isolation and durabilityThe four properties that make a database transaction reliable. Atomicity means all or nothing: moving 50 pounds from account A to account B either completes both steps, which is committed, or if one step fails both are undone. Consistency means the rules always hold: before the move A and B have 100 pounds each, after it they have 50 and 150, the total is still 200 pounds, and no account goes below zero. Isolation means transactions cannot see each other's half finished work: transaction 2 waits while transaction 1 has the balance locked, then runs after it commits. Durability means that once a transaction is committed it stays, even if the power is cut.Atomicity1all or nothingAccount A£100Account B£0move £50both steps done: commitone step fails: undo bothConsistency2the rules always holdbeforeA£100B£100total £200afterA£50B£150total £200no account may go below £0Isolation3no peeking at half-finished workTransaction 1works on balanceTransaction 2waits, then runscommitDurability4once committed, it stayspower cutstill there
  1. Atomicity: a transaction is all or nothing.
  2. Consistency: the database goes from one valid state to another, and the rules always hold.
  3. Isolation: concurrent transactions do not see each other's half finished work.
  4. Durability: committed data survives a crash.
Learn the four words and give the bank transfer as the example for each.

Key terms you must be able to define

Flat file
A database held as a single table, which tends to repeat data.
Relational database
A database of several tables, each about one entity, linked by keys.
Primary key
An attribute, or set of attributes, whose value is unique for every record in a table.
Foreign key
An attribute in one table that holds the primary key of a record in another table.
Referential integrity
The rule that every foreign key value must match an existing primary key value.
Normalisation
Organising the tables of a database to remove repeated data and the anomalies it causes.
Partial dependency
An attribute that depends on only part of a composite primary key.
Transitive dependency
A non key attribute that depends on another non key attribute rather than directly on the key.
Transaction
A group of database operations that is treated as a single unit.
ACID
The four properties of a reliable transaction: atomicity, consistency, isolation and durability.
Index
A data structure on a column that lets the database find records quickly without reading the whole table.

Flashcards

Say the definition out loud before you flip the card. Retrieving it from memory is what builds recall, reading it again is not.

DefineFlat file
AnswerA database held as a single table, which tends to repeat data.
DefineRelational database
AnswerA database of several tables, each about one entity, linked by keys.
DefinePrimary key
AnswerAn attribute, or set of attributes, whose value is unique for every record in a table.
DefineForeign key
AnswerAn attribute in one table that holds the primary key of a record in another table.
DefineReferential integrity
AnswerThe rule that every foreign key value must match an existing primary key value.
DefineNormalisation
AnswerOrganising the tables of a database to remove repeated data and the anomalies it causes.
DefinePartial dependency
AnswerAn attribute that depends on only part of a composite primary key.
DefineTransitive dependency
AnswerA non key attribute that depends on another non key attribute rather than directly on the key.

Reach the top grade on this topic

Database questions are about structure first and syntax second, so get the design right before writing a query.

  • Name the key of every table, and mark each foreign key and what it refers to
  • For normalisation, say which rule each change satisfies, and which dependency was removed
  • Join tables on matching keys, and put the aggregate condition in HAVING, not in WHERE
  • Define each part of ACID in a sentence, and tie it to the example in the question
  • Describe an index as a trade: faster searching, slower writing and more storage

Mistakes that cost marks here

Avoid these
  • Putting a condition on an aggregate, such as SUM(...) > 100, in a WHERE clause.
  • Describing normalisation as making a database smaller, instead of removing repeated data and anomalies.
  • Forgetting the foreign key that links two tables, so the design cannot be joined.
  • Mixing up a partial dependency, on part of a composite key, with a transitive one, through another attribute.
  • Saying atomicity means a transaction is fast, when it means all or nothing.
Fun fact

The relational model was published in 1970 by Edgar Codd, an IBM researcher, in a paper of just thirteen pages. IBM was slow to build it, because it already sold a profitable older database, and the first commercial relational database came from a small start up that became Oracle.

Knowledge check

0 of 10 answered
1What is the main problem with storing all the data in a single flat file?
2What is a foreign key?
3What does first normal form require?
4What does second normal form remove?
5What does third normal form remove?
6Which clause filters the groups produced by GROUP BY, after aggregates are calculated?
7What is the purpose of a JOIN in SQL?
8What does atomicity mean for a database transaction?
9What does referential integrity ensure?
10What is the effect of adding an index to a column?
Score 0 / 10

Exam-style questions

Five written questions in the style of the real paper. Write a full answer, then let the marker check it against the mark scheme.

20 marks
1State three problems that can occur when data is stored in a flat file database.3 marks
State

Mark scheme0 / 3
  • LData is duplicated, which wastes storage space
  • LThe same data may be stored differently in different places, so it is inconsistent
  • LA change to a repeated item must be made in many places, which is slow or may be missed
  • LIt is difficult to add or remove data without affecting other data
Model answer

Data is repeated in many records, which wastes storage space. The repeated data can become inconsistent, if an item is changed in one record but not in the others. A change to repeated data has to be made in every place it appears, which is slow and easy to get wrong. Deleting a record can also lose other facts that were held only in that record.

The marker looks for key phrases. If you said it another way and it is still right, give yourself the mark.
2The table shows the data held by a small online shop in a single table. The primary key is the pair OrderID and ProductID. Normalise the data to third normal form. State the name, attributes and primary key of each table that you create.6 marks
Normalise
OrderIDProductIDOrderDateCustomerIDCustomerNameAddressProductNamePriceQuantity
101P103/05C7A. Khan4 High StKeyboard25.001
101P203/05C7A. Khan4 High StMouse12.502
102P104/05C9B. Ali9 Mill RdKeyboard25.003

Mark scheme0 / 6
  • LA Customer table with CustomerID as the primary key, holding the name and address
  • LA Product table with ProductID as the primary key, holding the name and price
  • LAn Order table with OrderID as the primary key, holding the date
  • LThe Order table has CustomerID as a foreign key to the Customer table
  • LAn OrderLine table, which links orders and products, with Quantity
  • LOrderLine has a composite primary key of OrderID and ProductID, which are both foreign keys
Model answer

Customer details depend only on the customer, so they go in a Customer table: CustomerID (primary key), CustomerName, Address. Product details depend only on the product: Product table with ProductID (primary key), ProductName, Price. Order details depend only on the order: Order table with OrderID (primary key), OrderDate and CustomerID, which is a foreign key to the Customer table. The quantity depends on both the order and the product, so an OrderLine table holds OrderID, ProductID and Quantity, and its primary key is the pair OrderID and ProductID together, each of which is also a foreign key.

The marker looks for key phrases. If you said it another way and it is still right, give yourself the mark.
3A database has the tables Customer(CustomerID, Name, City) and CustomerOrder(OrderID, CustomerID, Total). Write an SQL query that displays the Name of each customer and the sum of the Total of all their orders, but only for customers whose orders add up to more than 100.4 marks
Write

Mark scheme0 / 4
  • LSELECT the name and SUM of the Total
  • LFROM the two tables, joined ON the matching CustomerID
  • LGROUP BY the customer
  • LHAVING SUM(Total) > 100, not WHERE
Model answer
SELECT Customer.Name, SUM(CustomerOrder.Total)
FROM Customer
JOIN CustomerOrder ON Customer.CustomerID = CustomerOrder.CustomerID
GROUP BY Customer.CustomerID
HAVING SUM(CustomerOrder.Total) > 100

The two tables are joined on the matching CustomerID, the orders are grouped by customer so that SUM gives one total for each, and HAVING keeps only those groups whose total is over 100. WHERE could not be used because the total does not exist until after the grouping.

The marker looks for key phrases. If you said it another way and it is still right, give yourself the mark.
4Explain what is meant by referential integrity, and give an example of how it protects a database.3 marks
Explain

Mark scheme0 / 3
  • LEvery foreign key value must match the primary key of an existing record in the table it refers to
  • LThe database refuses an action that would break this rule
  • LFor example, an order cannot be added for a customer who does not exist, or a customer cannot be deleted while they still have orders
Model answer

Referential integrity means that every foreign key value must match the primary key of a record that really exists in the table it refers to. The database refuses any action that would break the rule. For example, it will not allow an order to be recorded for a customer number that is not in the Customer table, and it will not allow a customer to be deleted while orders that refer to them still exist, so there are never orders left pointing at nothing.

The marker looks for key phrases. If you said it another way and it is still right, give yourself the mark.
5A bank transfer takes money from one account and adds it to another using two SQL updates. Explain how using a transaction protects the data if the system fails after the first update, with reference to atomicity and durability.4 marks
Explain

Mark scheme0 / 4
  • LA transaction treats both updates as a single unit
  • LAtomicity means all of the updates happen or none do
  • LAfter a failure the first update is rolled back, so no money is lost
  • LDurability means that once the transaction is committed, the changes are permanent even if the system then fails
Model answer

A transaction treats the two updates as a single unit. Atomicity means that either both updates are carried out or neither is, so if the system fails after the first update the transaction is rolled back and the first update is undone, which means no money is lost or created. Durability means that once the transaction has been committed the changes are permanent, and they will still be there even if the system fails immediately afterwards.

The marker looks for key phrases. If you said it another way and it is still right, give yourself the mark.
Machine mark 0 / 20