PL/SQL & T-SQL (Oracle and SQL Server) Module 8 – Production T-SQL: concurrency, security, new features and tests
T-SQL code review and interview drills
Read a T-SQL procedure in the order its failures cost, check it against one list on eight planted problems, then answer original interview drills.
What you will learn
- Review a T-SQL procedure against one checklist of errors, transactions, sets, concurrency, security and performance
- Fix each finding of a review and name the test that proves the fix
- Explain a design choice by the data size, the concurrency and the failure mode that decide it
- Practise interview drills that show the failure first, then the fix, then the test
- Recognise interview-question sources that are leaked, unverified or out of date
Before you start
What you’ll learn · Before you start
What you will learn
- Review a T-SQL procedure against one checklist of errors, transactions, sets, concurrency, security and performance
- Fix each finding of a review and name the test that proves the fix
- Explain a design choice by the data size, the concurrency and the failure mode that decide it
- Practise interview drills that show the failure first, then the fix, then the test
- Recognise interview-question sources that are leaked, unverified or out of date
Before you start
On this page
A 50-line stored procedure: A named unit of procedural code kept in the database and run with one call, with parameters and the permissions of the database, instead of being sent as text each time. More in “PL/SQL and T-SQL: what they are and where they run” that settles a customer’s orders passes its one happy-path call, and a reading of its text still finds eight problems. Two can commit half a payment, one can spend money that another session is spending, and two let a caller run its own code or step around the procedure’s rules.
A T-SQL code review: Reading a change to code before it is merged, to find defects, risks and unclear parts. For database code it covers errors and transactions, set-based logic, concurrency, permissions, performance and tests. is how those problems are found before release: a second reader takes the procedure apart in the order its failures would cost. You will read one in that order with a checklist, run a checker that points at the eight problems, and then work through interview drills that argue each choice the way a review does.
One checklist covers what a passing test does not
The procedure’s own test settles one order for customer 2 and checks the balance: 760.25 becomes 340.25, and the test passes. One call with good data cannot show what a failure halfway through leaves behind, what two callers at once do to the same account, or who can reach the tables without the procedure. A T-SQL code review asks exactly those questions, in the order of what their failures cost. Step down the order:
The drawing shows the whole scene; its 7 steps are listed below.
Every step as text
- Start with the contract. Read the parameters and the result before any statement. dbo.settle_orders takes a customer, a year and a sort column, but not the account to pay from, so the body has to guess one.
- Then what a failure leaves. Find the transaction and the CATCH block, and ask what is committed when the third of five orders fails. Money and orders must move together or not at all.
- Then loops and row order. A cursor or a WHILE loop that touches one row at a time is a candidate for one statement. A TOP or a cursor without ORDER BY works on rows in an order nobody chose.
- Then two callers at once. Imagine a second session settling the same account at the same moment. A balance read with NOLOCK, or read and then updated in two steps, can let both sessions spend the same money.
- Then who can run what. Look for text from a parameter pasted into a statement, and for grants. A role that holds table rights can skip the procedure and its rules altogether.
- Then the predicates. Check that each condition on a column can use an index. A function wrapped around the column, or a parameter of another type, turns a seek into a scan.
- Last, the tests. Ask for a test of each rule, the failure paths included. A finding that changes the contract, such as the account the caller must now name, sends the review back to the top.
Each stage of the flow is a row of the checklist, and each row links to the lesson of this track that teaches it:
| Area | What to check | Taught in |
|---|---|---|
| Errors and transactions | SET XACT_ABORT ON; a CATCH that rolls back and re-raises; nothing half committed |
XACT_ABORT and XACT_STATE |
| Sets and order | one statement instead of a loop; ORDER BY beside every TOP |
cursors and set-based rewrites |
| Concurrency | no NOLOCK on values the code acts on; a lock held from read to write |
locking and RCSI |
| Security | values passed as parameters; EXECUTE on procedures, not table rights |
dynamic SQL, security |
| Performance | predicates that can seek; parameter types that match their columns | execution plans, sargable conditions |
| Tests | a test for each rule, failure paths included | tSQLt |
Eight problems in one procedure, found by reading in that order
Here is the procedure as submitted, with the grants its deployment script adds and the happy-path call:
-- Engine: SQL Server 2025 CU9 (17.0.5005.3); database lab with the practice schema. Client: sqlcmd.
-- The procedure as submitted settles a customer's new orders of one year from one of their accounts, then returns
-- the customer's orders sorted by a column the caller names. It passes its happy-path call below and still holds eight
-- planted problems; review_check.py lists them and review_after.sql is the revised version. The call runs inside a
-- transaction that is rolled back, and the last batch removes the procedure and the role.
SET NOCOUNT ON;
IF DATABASE_PRINCIPAL_ID(N'clerk_role') IS NULL CREATE ROLE clerk_role;
GO
CREATE OR ALTER PROCEDURE dbo.settle_orders
@customer_id int,
@year int,
@sort_col nvarchar(30) = N'order_id'
AS
BEGIN
SET NOCOUNT ON;
DECLARE @account_id int, @order_id int, @amount decimal(14,2), @balance decimal(14,2);
DECLARE @sql nvarchar(max);
SELECT TOP (1) @account_id = account_id
FROM dbo.accounts
WHERE customer_id = @customer_id;
BEGIN TRY
BEGIN TRANSACTION;
DECLARE due CURSOR FOR
SELECT o.order_id, o.qty * s.unit_price
FROM dbo.orders AS o
JOIN dbo.stock AS s ON s.sku = o.sku
WHERE o.customer_id = @customer_id
AND o.status = N'new'
AND YEAR(o.ordered_on) = @year;
OPEN due;
FETCH NEXT FROM due INTO @order_id, @amount;
WHILE @@FETCH_STATUS = 0
BEGIN
SELECT @balance = balance FROM dbo.accounts WITH (NOLOCK) WHERE account_id = @account_id;
IF @balance >= @amount
BEGIN
UPDATE dbo.accounts SET balance = balance - @amount WHERE account_id = @account_id;
INSERT INTO dbo.ledger (account_id, amount, posted_at, memo)
VALUES (@account_id, -@amount, SYSDATETIME(), CONCAT(N'order ', @order_id));
UPDATE dbo.orders SET status = N'paid' WHERE order_id = @order_id;
END;
FETCH NEXT FROM due INTO @order_id, @amount;
END;
CLOSE due;
DEALLOCATE due;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0 COMMIT TRANSACTION;
PRINT ERROR_MESSAGE();
END CATCH;
SET @sql = N'SELECT order_id, sku, qty, status FROM dbo.orders WHERE customer_id = '
+ CAST(@customer_id AS nvarchar(12)) + N' ORDER BY ' + @sort_col;
EXEC (@sql);
END;
GO
GRANT SELECT, INSERT, UPDATE ON dbo.accounts TO clerk_role;
GRANT SELECT, INSERT ON dbo.ledger TO clerk_role;
GRANT SELECT, UPDATE ON dbo.orders TO clerk_role;
GRANT SELECT ON dbo.stock TO clerk_role;
GRANT EXECUTE ON dbo.settle_orders TO clerk_role;
GO
-- The happy path: customer 2 has one new order of 2026 (RICE-5K x 1, 420.00) and account 3 holds 760.25.
BEGIN TRANSACTION;
EXEC dbo.settle_orders @customer_id = 2, @year = 2026;
SELECT account_id, balance FROM dbo.accounts WHERE account_id = 3;
ROLLBACK TRANSACTION;
GO
DROP PROCEDURE IF EXISTS dbo.settle_orders;
DROP ROLE IF EXISTS clerk_role;
GO """review_before.sql in the lab: sqlcmd runs it as sa in the database lab, on a freshly loaded practice schema."""
from mssql_session import Lab
with Lab() as lab:
lab.sqlcmd("review/review_before.sql") Output
$ sqlcmd -S localhost -U sa -C -d lab -i review/review_before.sql
order_id sku qty status
-------- ------- --- ------
2 RICE-5K 1 paid
account_id balance
---------- -------
3 340.25
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 review_before_session.py
The checker below reads both versions of the script and applies eight rules of the checklist, one per planted problem. It is built on patterns rather than a T-SQL parser, so every line it names is a place to look:
"""Eight rules of the T-SQL review checklist, run over review_before.sql and review_after.sql.
The checker reads each script beside it and prints, line by line, the places that the checklist flags. It is a
teaching aid built on regular expressions, not a T-SQL parser: it does not understand comments that span lines, code
inside strings, or patterns it was not written for. A finding is a place to look, and a script with no findings has
only passed these eight rules, which is not the same as being correct.
"""
import re
from pathlib import Path
FILES = ["review_before.sql", "review_after.sql"]
TABLE_RIGHTS = re.compile(r"^\s*GRANT\s+(SELECT|INSERT|UPDATE|DELETE)\b.*\bON\s+(OBJECT::)?dbo\.\w+\s+TO\b", re.I)
FUNCTION_ON_COLUMN = re.compile(
r"\b(YEAR|MONTH|DAY|DATEPART|CONVERT|CAST|ISNULL|UPPER|LOWER|LTRIM|RTRIM|LEFT|SUBSTRING)\s*\(\s*\w+\.\w+", re.I
)
def procedure_lines(lines):
"""Line numbers (1-based) from CREATE ... PROCEDURE to the next GO line."""
inside, out = False, []
for n, line in enumerate(lines, 1):
if re.match(r"\s*CREATE\s+(OR\s+ALTER\s+)?PROC", line, re.I):
inside = True
elif inside and re.match(r"\s*GO\s*$", line, re.I):
break
if inside:
out.append(n)
return out
def check(lines):
findings = []
proc = procedure_lines(lines)
body = "\n".join(lines[n - 1] for n in proc)
if proc and not re.search(r"SET\s+XACT_ABORT\s+ON", body, re.I):
findings.append((proc[0], "errors", "no SET XACT_ABORT ON: some run-time errors leave the transaction open"))
for n in proc:
line = lines[n - 1]
if re.search(r"BEGIN\s+CATCH", line, re.I):
block = []
for m in range(n, len(lines) + 1):
block.append(lines[m - 1])
if re.search(r"END\s+CATCH", lines[m - 1], re.I):
break
text = "\n".join(block)
if re.search(r"\bCOMMIT\b", text, re.I) or not re.search(r"\b(THROW|RAISERROR)\b", text, re.I):
findings.append((n, "errors", "CATCH commits or hides the error: partial work survives, the caller sees none"))
if re.search(r"\bTOP\s*\(", line, re.I):
statement = "\n".join(lines[m - 1] for m in range(n, len(lines) + 1)).split(";")[0]
if not re.search(r"ORDER\s+BY", statement, re.I):
findings.append((n, "sets", "TOP without ORDER BY: any matching row may be the one picked"))
if re.search(r"\b(NOLOCK|READUNCOMMITTED)\b", line, re.I):
findings.append((n, "concurrency", "NOLOCK on a value the code acts on: it may read uncommitted data"))
if re.search(r"\bDECLARE\s+\w+\s+CURSOR\b", line, re.I):
findings.append((n, "sets", "a cursor loop: row-by-row work that one set-based statement can do"))
if re.search(r"\b(WHERE|AND|OR)\b", line, re.I) and FUNCTION_ON_COLUMN.search(line):
findings.append((n, "performance", "a function around a column in a predicate: an index on it cannot seek"))
if re.search(r"\bEXEC(UTE)?\s*\(\s*@\w+\s*\)", line, re.I):
findings.append((n, "security", "EXEC of a string built by concatenation: caller text becomes code"))
grants = [n for n, line in enumerate(lines, 1) if TABLE_RIGHTS.search(line)]
if grants:
findings.append((grants[0], "security", f"{len(grants)} GRANTs of table rights: the role can bypass the procedure"))
return sorted(findings)
print("Review checklist: 8 rules (patterns, not a parser: every finding is a place to look)")
for name in FILES:
lines = Path(name).read_text(encoding="utf-8").splitlines()
found = check(lines)
print()
print(f"{name}: {len(lines)} lines, {len(found)} finding{'s' if len(found) != 1 else ''}")
for n, area, text in found:
print(f" line {n:>2} {area:<12} {text}") Output
Review checklist: 8 rules (patterns, not a parser: every finding is a place to look) review_before.sql: 74 lines, 8 findings line 9 errors no SET XACT_ABORT ON: some run-time errors leave the transaction open line 19 sets TOP without ORDER BY: any matching row may be the one picked line 25 sets a cursor loop: row-by-row work that one set-based statement can do line 31 performance a function around a column in a predicate: an index on it cannot seek line 36 concurrency NOLOCK on a value the code acts on: it may read uncommitted data line 50 errors CATCH commits or hides the error: partial work survives, the caller sees none line 57 security EXEC of a string built by concatenation: caller text becomes code line 60 security 4 GRANTs of table rights: the role can bypass the procedure review_after.sql: 97 lines, 0 findings
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 review_check.py
It names eight lines in the submitted version and none in the revised one. Read in the flow’s order, they are: no
XACT_ABORT, a CATCH that commits and only prints, a TOP (1) that takes whichever of the customer’s accounts comes first,
a cursor, a NOLOCK read of the balance it spends, a sort column pasted into
dynamic SQL: A statement that code builds as text at run time and then runs, with EXEC or with sp_executesql, in a batch of its own. More in “Dynamic SQL with sp_executesql, safely”, table rights granted to the clerks’ role, and YEAR() wrapped around a
column. Most of them show only when
something unusual happens: a statement fails, a second session runs, a customer holds two accounts or a caller sends
text the author did not expect. The happy path does none of these.
A clean run of a checker is not a review
Wrong: treat “0 findings” as the end of the review. The checker knows eight patterns. Right: read every stage of the flow yourself and use the checker to make sure the known patterns are never missed.
Checkpoint
Check: The review order and its checklist · The eight planted problems
Interview drills start from a failure you can show
The interview drills of this track are written for it, from behaviour its lessons demonstrate and Microsoft documents: none carries a company’s name, and none is a question someone remembered from a real interview. A strong answer to any of them has the shape of a good review comment: the failure first, reproduced, then the fix, then the test that proves it.
Where your preparation comes from decides whether you can trust it:
| Source | Trust it? |
|---|---|
| Microsoft’s documentation, then a run on your own lab | Yes: the rule and its proof |
| An answer written for an old release | Only after checking the current page |
| A list of “questions asked at” a company | No: unverified, often leaked or copied |
Interview drills reward the habit the review taught: name the data size, the number of sessions at once and what a failure must leave behind before you name a feature.
Checkpoint
Check: Original drills and sources to trust
The eight findings fixed, each with the test that proves it Pro: opens with a Pro, Premium or Ultimate pass
The revised procedure keeps the original’s contract where it can and changes it where a guess was the bug. Each fix below comes with a test that fails on the submitted version and passes on the revised one.
In this part:
- One setting and a CATCH block that roll back and re-raise
- The cursor becomes one statement, and the rule it hid becomes a decision
- A held account row, and an account the caller names
- A static sort, EXECUTE rights only, and a year range that can seek
3 runnable examples (Python, SQL) · about 5 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Interview drills with model answers and a rubric for each level Pro: opens with a Pro, Premium or Ultimate pass
Each drill is a question written for this track, a model answer built from behaviour its lessons show, and what each level of answer covers. Work out your own answer before you read the model one.
In this part:
- Explaining a choice by size, concurrency and failure
- Drill 1: a temp table or a table variable for a nightly step
- Drill 2: MERGE or an UPDATE and an INSERT for a price feed
- Drill 3: a review comment the author disputes
- A rubric for scoring your own answers
about 5 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Practise: sort each finding before you fix it
Each question shows a line or a claim from a review. Decide which checklist area it belongs to and what it risks:
Practice that adapts to you
Practise this lesson
8 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
These three questions ask you to rank findings, defend a choice with numbers and catch a rule that a rewrite changed; write your own answer before you open each first step:
-
Mid to senior
A pull request adds dbo.move_stock, which moves units between two SKUs in a WHILE loop, reads on_hand WITH (NOLOCK) before each update, and has a CATCH block that prints the message and returns 0. The release is tomorrow. Which findings block the merge, which become follow-ups, and which tests do you ask for?
-
Mid to senior
In a design review you defend your comment that a nightly procedure should stage 60,000 invoice lines in a temporary table, not in the table variable its author used. The author replies that the database is at compatibility level 160, so the row count is known. Make your case with the facts that decide it.
-
Senior to staff
A change accepted in code review replaced a cursor that applied customer credits to open invoices with one set-based UPDATE. The tests still succeed, but finance reports that some customers now have more invoices left open than before. Explain how a rewrite can change a business rule, how the review could have caught it, and what you do now.
Flashcards · 8 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
In which order does a T-SQL code review read a procedure?
In the order its failures cost: errors and transactions, sets and order, concurrency, security, performance, then tests.
-
Why does one passing call prove so little?
It cannot show what a failure halfway through leaves behind, what two callers at once do to the same account, or who can reach the tables without the procedure. Most findings appear only when a statement fails, a second session runs, a customer holds two accounts or a caller sends unexpected text.
-
What does the review's errors and transactions row ask for?
SET XACT_ABORT ON, a CATCH block that rolls back and re-raises rather than committing and printing, and nothing left half committed.
-
What does the review's concurrency row look for?
No NOLOCK on values the code acts on, and locks kept from the read until the write, so two sessions cannot spend one balance twice.
-
What does the review's security row check?
That values are passed as parameters rather than pasted into dynamic SQL, and that callers hold EXECUTE on the procedures instead of rights on the tables, so nobody can step around the procedure's rules.
-
Which two set-and-order findings does the checklist name?
A loop where one statement would do, and a TOP without an ORDER BY beside it, which takes whichever of a customer's accounts the engine reaches first.
-
Why is 0 findings from a pattern-based checker not the end of a review?
The checker knows a fixed set of patterns and is built on patterns rather than a T-SQL parser, so every line it names is a place to look and everything it does not know is still yours to read. Follow the whole checklist and use the checker so the known patterns are never missed.
-
What shape does a strong answer to an interview drill have, and which sources can you trust?
The failure first, reproduced, then the fix, then the test that proves it. Trust the vendor's documentation plus a run on your own lab; check an answer written for an old release against the current page; a list of questions said to be asked at a company is unverified, often leaked or copied.
Key takeaways
- A T-SQL code review reads a procedure in the order its failures cost: errors and transactions, sets and order, concurrency, security, performance, then tests.
- Most findings stay hidden on the happy path: they appear only when a statement fails, a second session runs or a caller sends unexpected text.
- A pattern-based checker finds the patterns it knows; the reader still follows the whole checklist.
- Interview drills here are original, and a strong answer shows the failure first, then the fix, then the test.
Terms in this lesson
- code review
- Reading a change to code before it is merged, to find defects, risks and unclear parts. For database code it covers errors and transactions, set-based logic, concurrency, permissions, performance and tests.
References
- CREATE PROCEDURE (Transact-SQL) (Microsoft)
- TRY...CATCH (Transact-SQL) (Microsoft)
- SET XACT_ABORT (Transact-SQL) (Microsoft)
- XACT_STATE (Transact-SQL) (Microsoft)
- THROW (Transact-SQL) (Microsoft)
- DECLARE CURSOR (Transact-SQL) (Microsoft)
- TOP (Transact-SQL) (Microsoft)
- SELECT - ORDER BY clause (Transact-SQL) (Microsoft)
- Table hints (Transact-SQL) (Microsoft)
- Transaction locking and row versioning guide (Microsoft)
- SQL injection (Microsoft)
- EXECUTE AS clause (Transact-SQL) (Microsoft)
- GRANT object permissions (Transact-SQL) (Microsoft)
- SQL Server and Azure SQL index architecture and design guide (Microsoft)
- MERGE (Transact-SQL) (Microsoft)
- table (Transact-SQL) (Microsoft)
- Execution plan overview (Microsoft)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress