Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

PL/SQL & T-SQL (Oracle and SQL Server)  Module 15 – Production PL/SQL: staging, concurrency, JSON and tests

PL/SQL code review and interview drills

Read a PL/SQL package against a checklist of errors, loops, dynamic SQL, rights, state and speed, then answer interview drills failure first.

  • Advanced
  • 15 minutes
  • Examples run with oracle-free 23.26.3.0.0, mssql 17.0.5005.3, Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free core, Pro depth: 5 of 15 minutes free
Free: A PL/SQL code review reads in the order failures cost · Eight planted problems, and what the checker flags · Name the release your answer depends on · A drill answer shows the failure, the fix and the test. With a pass: 2 in-depth parts and 3 interview answers, about 10 minutes.

What you will learn

  • Review a PL/SQL package against a checklist of errors, bulk processing, security and performance
  • Explain the 19c and 26ai differences that change an answer
  • Practise interview drills with model answers
  • Avoid unreliable or leaked interview-question sources

Before you start

A posting package passes its author’s test: a generated batch of 200 feed lines goes in and 200 ledger rows come out. A PL/SQL code review of the same package still finds eight problems, and one of them quietly drops the last 50 lines of a 250-line feed.

A 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. More in “T-SQL code review and interview drills” is a second reader taking a change apart before it ships. You will read one package with a checklist and run a checker that points at its eight problems. Interview drills then argue each finding the way a good review comment does: the failure, the fix, the test.

A PL/SQL code review reads in the order failures cost

A single successful call proves little about three things: the state a failure leaves partway, the effect of a caller’s text inside a statement, and the count a second call in one session reports. A PL/SQL code review asks about each of them in order, costliest failures first. Step down the order on the package ledger_post:

Interactive · stage by stage Reading ledger_post, worst failures first Step down the stages. Each names what to read in ledger_post and the question to ask of it.
Reading ledger_post, worst failures first, step 6 of 6. Showing Errors and transactions, Loops and bulk SQL, Dynamic SQL and rights, Package state, Performance, Tests. In focus: Tests.Errors and transactionswhat a failure leavesLoops and bulk SQLevery line in few callsDynamic SQL and rightscaller text and AUTHIDPackage statewhat outlives a callPerformancecan an index serve itTeststhe failure paths too

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. First, what a failure leaves. Find every handler and every COMMIT. Ask what is kept when line 180 of a 250-line feed fails. In ledger_post a handler that does nothing and a COMMIT inside the loop decide the answer.
  2. Then the loops. A loop that fetches with LIMIT must still use its last, short batch, and a loop that sends one UPDATE per line makes one call to SQL per line. Ask whether one SQL statement could do the work.
  3. Then caller text and rights. Look for parameters joined into a statement's text, and for the package's rights model. A value belongs in a bind; a column name belongs in a list of allowed names; AUTHID belongs in the specification.
  4. Then what outlives the call. Read the variables declared at the top of the body. Each session keeps its own copy until it ends, so a counter that a call adds to keeps growing over every call in that session.
  5. Then the conditions. Check that each condition leaves its column bare. A function wrapped around posted_at means a plain index on posted_at cannot narrow the search; read the plan with DBMS_XPLAN to be sure.
  6. Last, the tests. Ask which test fails if each finding comes back. The author's one test posts 200 lines that all succeed, so it walks past every stage above.

Each stage is a row of the checklist, linked to the lesson that teaches it:

Area What to check
Errors and transactions no WHEN OTHERS THEN NULL, no COMMIT in a reusable unit, error codes in the specification
Loops and bulk SQL SQL before loops; LIMIT loops that keep the last batch
Dynamic SQL and rights binds for values, an allowed list for names, an explicit AUTHID
Package state no variable that outlives a call without a reason
Performance bare columns in conditions; the plan read, not guessed
Tests a utPLSQL test per rule, failure paths too

Eight planted problems, and what the checker flags

Here is ledger_post as it arrived, with the author’s test at the end of the script:

review_before.sql: ledger_post as submitted, with the author's test SQL · review/review_before.sql
-- Engine: Oracle AI Database 26ai Free, Release 23.26.3.0.0; user LAB in FREEPDB1. Client: SQL*Plus or SQLcl.
-- The package ledger_post as it arrives for a code review. post_batch moves one feed batch from feed_lines into the
-- ledger and adds each line to its account's balance; lines_posted reports how many lines were posted; open_report
-- opens a batch's ledger entries in the order a caller asks for; day_total adds up one day's entries. It compiles,
-- and the author's test at the end succeeds. Run schema_oracle.sql of practice-schema-and-dialect-basics first.
-- post_batch commits on its own, so the cleanup at the end deletes what the test posted and drops what the script made.
SET SERVEROUTPUT ON

DROP TABLE IF EXISTS feed_lines PURGE;
DROP INDEX IF EXISTS ledger_posted_ix;

CREATE TABLE feed_lines (
  batch_id   VARCHAR2(20)  NOT NULL,
  line_no    NUMBER(5)     NOT NULL,
  account_id NUMBER(10)    NOT NULL,
  amount     NUMBER(14, 2) NOT NULL,
  posted_at  DATE          NOT NULL,
  memo       VARCHAR2(60),
  CONSTRAINT feed_lines_pk PRIMARY KEY (batch_id, line_no)
);

CREATE INDEX ledger_posted_ix ON ledger (posted_at);

CREATE OR REPLACE PACKAGE ledger_post IS
  PROCEDURE post_batch (p_batch IN VARCHAR2);
  FUNCTION lines_posted RETURN PLS_INTEGER;
  PROCEDURE open_report (p_batch IN VARCHAR2, p_sort IN VARCHAR2, p_rows OUT SYS_REFCURSOR);
  FUNCTION day_total (p_day IN DATE) RETURN NUMBER;
END ledger_post;
/

CREATE OR REPLACE PACKAGE BODY ledger_post IS
  g_posted PLS_INTEGER := 0;

  PROCEDURE post_batch (p_batch IN VARCHAR2) IS
    CURSOR c_lines IS
      SELECT account_id, amount, posted_at, memo
      FROM feed_lines
      WHERE batch_id = p_batch
      ORDER BY line_no;
    TYPE t_lines IS TABLE OF c_lines%ROWTYPE INDEX BY PLS_INTEGER;
    v_lines t_lines;
  BEGIN
    OPEN c_lines;
    LOOP
      FETCH c_lines BULK COLLECT INTO v_lines LIMIT 100;
      EXIT WHEN c_lines%NOTFOUND;
      FORALL i IN 1 .. v_lines.COUNT
        INSERT INTO ledger (account_id, amount, posted_at, memo)
        VALUES (v_lines(i).account_id, v_lines(i).amount, v_lines(i).posted_at, v_lines(i).memo);
      FOR i IN 1 .. v_lines.COUNT LOOP
        UPDATE accounts
        SET balance = balance + v_lines(i).amount
        WHERE account_id = v_lines(i).account_id;
      END LOOP;
      g_posted := g_posted + v_lines.COUNT;
      COMMIT;
    END LOOP;
    CLOSE c_lines;
  EXCEPTION
    WHEN OTHERS THEN NULL;
  END post_batch;

  FUNCTION lines_posted RETURN PLS_INTEGER IS
  BEGIN
    RETURN g_posted;
  END lines_posted;

  PROCEDURE open_report (p_batch IN VARCHAR2, p_sort IN VARCHAR2, p_rows OUT SYS_REFCURSOR) IS
  BEGIN
    OPEN p_rows FOR
      'SELECT entry_id, account_id, amount, memo FROM ledger WHERE memo LIKE ''' || p_batch
      || ' line %'' ORDER BY ' || p_sort;
  END open_report;

  FUNCTION day_total (p_day IN DATE) RETURN NUMBER IS
    v_total NUMBER;
  BEGIN
    SELECT NVL(SUM(amount), 0)
    INTO v_total
    FROM ledger
    WHERE TRUNC(posted_at) = TRUNC(p_day);
    RETURN v_total;
  END day_total;
END ledger_post;
/

-- The author's test: a generated batch of 200 lines, 100 each on accounts 1 and 2, then the count it checks.
INSERT INTO feed_lines (batch_id, line_no, account_id, amount, posted_at, memo)
  SELECT 'b-0412', LEVEL, MOD(LEVEL, 2) + 1, 10.00, DATE '2031-04-12', 'b-0412 line ' || LEVEL
  FROM dual
  CONNECT BY LEVEL <= 200;
COMMIT;

BEGIN
  ledger_post.post_batch('b-0412');
  DBMS_OUTPUT.PUT_LINE('lines_posted = ' || ledger_post.lines_posted);
END;
/
-- Expected: lines_posted = 200

SELECT COUNT(*) AS ledger_rows FROM ledger WHERE memo LIKE 'b-0412 line %';
-- Expected: 200, the number the test compares, so the test succeeds.

-- Cleanup: post_batch committed, so its rows are removed by hand and the balances put back.
DELETE FROM ledger WHERE memo LIKE 'b-0412 line %';
UPDATE accounts SET balance = balance - 1000.00 WHERE account_id IN (1, 2);
COMMIT;
DROP PACKAGE ledger_post;
DROP INDEX ledger_posted_ix;
DROP TABLE feed_lines PURGE;
review_before.sql, run in the lab Python · review_before_session.py
"""review_before.sql in the lab: SQL*Plus runs it as LAB on a freshly loaded practice schema."""

from oracle_session import Lab

with Lab() as lab:
    lab.sqlplus("review/review_before.sql")

Output

$ sqlplus -s lab@//localhost/FREEPDB1 @review/review_before.sql
Table dropped.


Index dropped.


Table created.


Index created.


Package created.


Package body created.


200 rows created.


Commit complete.

lines_posted = 200

PL/SQL procedure successfully completed.


LEDGER_ROWS
-----------
        200


200 rows deleted.


2 rows updated.


Commit complete.


Package dropped.


Index dropped.


Table dropped.

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 applies eight rules, one per planted problem, to this script and the revised one. It matches patterns rather than parsing PL/SQL, so each flagged line is a place to read closely:

review_check.py: eight checklist rules over both versions of the package Python · review/review_check.py
"""Eight rules of the PL/SQL review checklist, run over review_before.sql and review_after.sql.

The checker reads each script beside it and prints the lines that its eight rules flag, in the checklist's order. It
is a teaching aid built on regular expressions, not a PL/SQL parser: it does not follow comments that span lines,
text inside quotes or code it was not written for. A flagged line is a place to read closely, and a script with no
findings has passed these eight rules only, which is not the same as being correct.
"""

import re
from pathlib import Path

HERE = Path(__file__).resolve().parent
FILES = ["review_before.sql", "review_after.sql"]

SPEC = re.compile(r"^\s*CREATE\s+(OR\s+REPLACE\s+)?PACKAGE\s+(?!BODY\b)(\w+)", re.I)
BODY = re.compile(r"^\s*CREATE\s+(OR\s+REPLACE\s+)?PACKAGE\s+BODY\s+(\w+)", re.I)
SUBPROGRAM = re.compile(r"^\s*(PROCEDURE|FUNCTION)\s+\w+", re.I)
DECLARATION = re.compile(r"^\s*([a-z]\w*)\s+[\w.%()]+[^;]*;", re.I)
NOT_VARIABLES = {"cursor", "type", "subtype", "pragma", "procedure", "function", "end"}
FOR_LOOP = re.compile(r"^\s*FOR\s+\w+\s+IN\b.*\bLOOP\b", re.I)
END_LOOP = re.compile(r"^\s*END\s+LOOP\b", re.I)
DML = re.compile(r"^\s*(INSERT|UPDATE|DELETE|MERGE)\b", re.I)
BULK_FETCH = re.compile(r"\bFETCH\b.*\bBULK\s+COLLECT\b.*\bLIMIT\b", re.I)
EXIT_NOTFOUND = re.compile(r"^\s*EXIT\s+WHEN\s+\w+%NOTFOUND\b", re.I)
DYNAMIC_START = re.compile(r"\b(OPEN\s+\w+\s+FOR|EXECUTE\s+IMMEDIATE)\b(.*)", re.I)
COLUMN_IN_FUNCTION = re.compile(
    r"\b(WHERE|AND|OR)\b.*?\b(TRUNC|UPPER|LOWER|TO_CHAR|SUBSTR|NVL)\s*\(\s*([a-z][\w.]*)\s*[,)]", re.I
)
PARAMETER_OR_VARIABLE = re.compile(r"^(p|v|l|g)_", re.I)

RULES = [
    ("errors", "a WHEN OTHERS handler that does nothing hides every failure from the caller"),
    ("transactions", "a COMMIT or ROLLBACK inside the package ends the caller's transaction"),
    ("bulk loop", "EXIT WHEN %NOTFOUND right after a LIMIT fetch drops the last, partial batch"),
    ("set-based", "one DML statement per row inside a FOR loop: one call to SQL per row"),
    ("dynamic SQL", "caller text joined into a statement with || instead of a bind or an allowed value"),
    ("rights", "a package specification without AUTHID: definer's rights by default, never decided"),
    ("state", "a package-level variable that a call changes: its value outlives the call"),
    ("performance", "a function around a column in a condition: an index on that column cannot serve it"),
]


def code(line):
    """The line without a trailing -- comment (enough for these two scripts)."""
    return line.split("--", 1)[0].rstrip()


def statement_from(lines, start):
    """The text of the statement that starts on line `start` (0-based), up to its semicolon."""
    parts = []
    for text in lines[start:]:
        parts.append(code(text))
        if ";" in code(text):
            break
    return " ".join(parts)


def findings(lines):
    """(rule number, line number) pairs for one script."""
    found = []
    body_start, body_end, body_name = None, None, None
    for n, text in enumerate(lines):
        m = BODY.match(text)
        if m:
            body_start, body_name = n, m.group(2)
        elif body_start is not None and body_end is None and re.match(rf"^\s*END\s+{body_name}\s*;", text, re.I):
            body_end = n
    in_body = lambda n: body_start is not None and body_start < n < (body_end or len(lines))

    # Rule 6: every package specification states its rights model.
    for n, text in enumerate(lines):
        if SPEC.match(text):
            header = statement_from(lines, n).split(" IS", 1)[0].split(" AS", 1)[0]
            if not re.search(r"\bAUTHID\b", header, re.I):
                found.append((6, n + 1))

    # Rule 7: variables declared in the body before its first subprogram, then assigned by a subprogram.
    if body_start is not None:
        first_sub = next((n for n in range(body_start + 1, len(lines)) if SUBPROGRAM.match(lines[n])), len(lines))
        for n in range(body_start + 1, first_sub):
            m = DECLARATION.match(code(lines[n]))
            if m and m.group(1).lower() not in NOT_VARIABLES:
                name = m.group(1)
                if any(re.search(rf"\b{name}\s*:=", code(t), re.I) for t in lines[first_sub:body_end]):
                    found.append((7, n + 1))

    depth = 0
    for n, text in enumerate(lines):
        line = code(text)
        if re.search(r"\bWHEN\s+OTHERS\s+THEN\s+NULL\s*;", line, re.I):
            found.append((1, n + 1))
        if in_body(n) and re.match(r"^\s*(COMMIT|ROLLBACK)\b", line, re.I):
            found.append((2, n + 1))
        if BULK_FETCH.search(line):
            nxt = next((k for k in range(n + 1, len(lines)) if code(lines[k]).strip()), None)
            if nxt is not None and EXIT_NOTFOUND.match(code(lines[nxt])):
                found.append((3, nxt + 1))
        if FOR_LOOP.match(line):
            depth += 1
        elif END_LOOP.match(line) and depth:
            depth -= 1
        elif depth and DML.match(line):
            found.append((4, n + 1))
        if DYNAMIC_START.search(line):
            rest = DYNAMIC_START.search(statement_from(lines, n)).group(2).strip()
            if not rest.upper().startswith(("SELECT", "WITH")) and "||" in rest:
                found.append((5, n + 1))
        m = COLUMN_IN_FUNCTION.search(line)
        if m and not PARAMETER_OR_VARIABLE.match(m.group(3)):
            found.append((8, n + 1))
    return sorted(found)


for name in FILES:
    lines = (HERE / name).read_text(encoding="utf-8").splitlines()
    result = findings(lines)
    print(f"{name}: {len(result)} finding{'s' if len(result) != 1 else ''}")
    for rule, line_no in result:
        area, why = RULES[rule - 1]
        print(f"  line {line_no:>3}  {area:<13} {why}")
    print()

Output

review_before.sql: 8 findings
  line  61  errors        a WHEN OTHERS handler that does nothing hides every failure from the caller
  line  57  transactions  a COMMIT or ROLLBACK inside the package ends the caller's transaction
  line  47  bulk loop     EXIT WHEN %NOTFOUND right after a LIMIT fetch drops the last, partial batch
  line  52  set-based     one DML statement per row inside a FOR loop: one call to SQL per row
  line  71  dynamic SQL   caller text joined into a statement with || instead of a bind or an allowed value
  line  24  rights        a package specification without AUTHID: definer's rights by default, never decided
  line  33  state         a package-level variable that a call changes: its value outlives the call
  line  82  performance   a function around a column in a condition: an index on that column cannot serve it

review_after.sql: 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 flags eight lines of the submitted package and none of the revised one: a WHEN OTHERS handler that swallowed exception: An exception that a handler catches and then drops, as WHEN OTHERS THEN NULL does, so the caller sees a normal return although the work failed., a COMMIT inside the loop, an exit straight after the LIMIT fetch, one UPDATE per line, caller text joined into a statement, no AUTHID, the counter g_posted, and TRUNC around the indexed posted_at. The author’s test takes only the happy path: The run of a program in which every input is valid and nothing fails. A test of the happy path alone cannot show what a failure, an unusual input or a second call does.: its 200 lines fill the LIMIT exactly and none of them fails.

Zero findings is where the reading starts

Wrong: close the review at 0 findings. Right: walk every stage yourself; the checker only makes sure that eight known patterns are never missed.

Checkpoint

Check: Reading a package in the order its failures cost · The planted problems and what a pattern checker shows

  1. Question 1 of 3 In the lesson's checklist, which area does a code review read first?

    Choose one answer.

    Show the answer: Question 1 of 3

    Answer: Errors and transactions

    The order follows the cost of each failure: errors and transactions, loops and bulk SQL, dynamic SQL and rights, package state, performance, then tests.

  2. Question 2 of 3 Why did the author's test of 200 valid lines pass although the package holds eight problems?

    Choose one answer.

    Show the answer: Question 2 of 3

    Answer: Its 200 lines fill the LIMIT exactly and none fails, so it never reaches the paths where the problems sit

    The test exercised only the happy path: 200 valid lines in two full batches and one call. None of the eight problems shows on that path.

  3. Question 3 of 3 A new package gets 0 findings from the lesson's checker. What can you conclude?

    Choose one answer.

    Show the answer: Question 3 of 3

    Answer: Only that none of its eight known patterns appears, so the reader still walks every stage

    A pattern checker is a safety net for known slips. The code review is the reading, and the tests are the proof.

Name the release your answer depends on

Many PL/SQL answers hold on one release only, and a team still on 19c will ask which. Oracle’s 21c reference added iteration controls such as stepped ranges; its 26ai reference lists the SQL BOOLEAN type and IF [NOT] EXISTS on DDL among that release’s new features, and the 19c reference says IF [NOT] EXISTS reached 19c in Release Update 19.28:

Feature On 26ai On 19c
FOR i IN 1 .. 9 BY 2 yes, since 21c no: a counter loop, as in control structures
A BOOLEAN column yes no: NUMBER(1) with a check of 0 or 1
CREATE TABLE IF NOT EXISTS yes from 19.28; before it, a block that ignores ORA-00955 only

So lead with the release, and on 19c with the release update.

Checkpoint

Check: Naming the release an answer depends on

  1. One question A deployment script must create a table once and run twice safely on a 19c site at Release Update 19.27. What do you write?

    Choose one answer.

    Show the answer: One question

    Answer: A block that runs the CREATE and ignores ORA-00955 only

    Name the release and, on 19c, the release update: from 19.28 and on 26ai, IF NOT EXISTS; before 19.28, catch ORA-00955 by its number and let every other error through.

A drill answer shows the failure, the fix and the test

Good interview drills come from code you can run or from behaviour a manual documents, and they name no company and repeat no remembered question. A strong answer gives the deciding sizes first (rows per run, callers at once, what must survive a failure), then the failure, the fix and the test that proves it.

Where you practise matters too:

Source Use it?
Oracle’s reference pages, checked in your own lab Yes: the rule, then your proof
An answer written for 11g or 12c Only after checking the current page
Lists of “real interview questions” from companies No: no source, often leaked or copied

Checkpoint

Check: Drill answers from trustworthy sources, failure first

  1. Question 1 of 2 Which source suits drill practice best?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Oracle's reference pages, checked by a run in your own lab

    Practise from documented behaviour you have reproduced. Original drills built that way never need a leaked question.

  2. Question 2 of 2 An interviewer asks whether you would use FORALL for a load. Which opening is strongest?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The sizes that decide, such as rows per run, sessions at once and what must survive a failure

    A strong answer states the deciding facts first, then the failure of the wrong choice, the fix and the test.

Each planted problem fixed, with the test that would have caught it Pro: opens with a Pro, Premium or Ultimate pass

The revised package keeps what ledger_post is for and rewrites its contract wherever the original left the caller guessing. Each fix below is paired with a test that fails on the submitted package and passes on the revised one.

In this part:

  • Fixing the hidden error and the stray commit
  • Fixing the loop that drops a batch
  • Fixing the pasted SQL and the unstated rights
  • Fixing the shared counter and the date condition
  • Tests that fail on the submitted package

3 runnable examples (Python, SQL) · about 9 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.

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).

Four interview drills answered, with a rubric for each level Pro: opens with a Pro, Premium or Ultimate pass

Four drills follow, each with a model answer that uses only behaviour this track shows and Oracle documents. Answer each one aloud or on paper first; the rubric at the end scores your answer against each level.

In this part:

  • How a strong answer is put together
  • Drill 1: predict what the submitted package posts
  • Drill 2: a handler the author wants to keep
  • Drill 3: one change for 19c and 26ai sites
  • Drill 4: pasted SQL that only an administrator calls
  • A rubric to score your own answers

about 6 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.

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: name the area and the risk

Each question quotes a line of a package or a claim from a code review: name its checklist area and what it puts at risk.

Practice that adapts to you

Practise this lesson

12 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 12 A COMMIT sits inside a procedure of a reusable package. Which checklist area does it belong to?

    Choose one answer.

    Show the answer: Question 1 of 12

    Answer: Errors and transactions

    A COMMIT inside a reusable unit ends the caller's transaction too, so it belongs with errors and transactions.

  2. Question 2 of 12 Why does the checklist read errors and transactions before performance?

    Choose one answer.

    Show the answer: Question 2 of 12

    Answer: A failure there can keep half a batch or hide an error, which costs more than a slow query

    A code review spends its attention where a failure costs most, so it starts with what a failure leaves behind and ends with speed and tests.

  3. Question 3 of 12 A function called by a nightly report is slow, and it also ends in WHEN OTHERS THEN NULL. You can change only one thing today. Which do you change first?

    Choose one answer.

    Show the answer: Question 3 of 12

    Answer: The handler, so that failures reach the report instead of vanishing

    Apply the checklist's order to a choice: the finding that can corrupt results silently comes before the one that only wastes time.

  4. Question 4 of 12 Which shape of a LIMIT loop did the checker flag?

    Choose one answer.

    Show the answer: Question 4 of 12

    Answer: EXIT WHEN cursor%NOTFOUND on the line straight after the FETCH

    The checker flags an exit on %NOTFOUND placed right after a LIMIT fetch, the line the lesson names among the eight.

  5. Question 5 of 12 Which extra test feed would have exposed a planted problem that the author's 200-line test missed?

    Choose one answer.

    Show the answer: Question 5 of 12

    Answer: 250 valid lines

    The author's test filled the limit exactly. A size that does not, such as 250, is the case the code review asks for.

  6. Question 6 of 12 A job calls post_batch twice in one session and logs lines_posted after each call. Why is the second log wrong?

    Choose one answer.

    Show the answer: Question 6 of 12

    Answer: g_posted is package state, so it still holds the first call's count when the second call adds to it

    A variable at the top of the body lives as long as the session, so a counter there adds up every call made in it.

  7. Question 7 of 12 Which release's PL/SQL Language Reference lists the SQL BOOLEAN data type among its new features?

    Choose one answer.

    Show the answer: Question 7 of 12

    Answer: 26ai

    The lesson's table gives the release of each feature: iteration controls since 21c, and the SQL BOOLEAN type and IF [NOT] EXISTS in the 26ai list.

  8. Question 8 of 12 Code with FOR i IN 1 .. 9 BY 2 must also compile on a 19c site. What do you do?

    Choose one answer.

    Show the answer: Question 8 of 12

    Answer: Write a counter loop that computes i from a plain range

    Name the release the code must run on, then use only what that release has: on 19c, a counter loop.

  9. Question 9 of 12 An interviewer asks how you would add a yes-or-no column to a table, without saying which release runs it. What is the best first sentence of your answer?

    Choose one answer.

    Show the answer: Question 9 of 12

    Answer: It depends on the release, so here is the 26ai way and the 19c way

    An answer that names its release first is right on both releases, and it shows that you know where features begin.

  10. Question 10 of 12 Why does the lesson avoid lists of questions said to come from a company's interviews?

    Choose one answer.

    Show the answer: Question 10 of 12

    Answer: They have no source, may be leaked or copied, and nothing in them is checked

    Drills here come from documented behaviour and runs of this track's code, so every answer can be checked.

  11. Question 11 of 12 Which order makes the strongest answer to a design question in a drill?

    Choose one answer.

    Show the answer: Question 11 of 12

    Answer: The deciding sizes, the failure of the wrong choice, the fix, the test

    The pattern of a good review comment works in interviews too: the facts that decide, the failure, the fix and the test.

  12. Question 12 of 12 A popular answer written for 12c recommends a technique for your 26ai system. How do you use it in your preparation?

    Choose one answer.

    Show the answer: Question 12 of 12

    Answer: Check its claims against the 26ai page and reproduce them in your lab before relying on them

    Old material is a lead, not evidence: the current documentation and a reproduced run turn it into something you can say in an interview.

Interview questions

Warm-up (fresher to 2–5 years): why is WHEN OTHERS THEN NULL a finding even when the code seems to work?

It turns every error into success. The handler catches anything the block raises, a missing account, a broken constraint, a full tablespace, and the call returns normally, so the caller commits part of the work with no message. Oracle’s compiler flags it as warning PLW-06009, an OTHERS handler that does not end in RAISE or RAISE_APPLICATION_ERROR. Handle the errors you can by name, and let the rest reach the caller.

Next come a load that commits after every 1,000 lines and fails partway, five findings on the eve of a release, and a utility that deletes from any table it is told:

  1. Mid to senior

    A package procedure loads a 60,000-line feed and commits after each 1,000. Line 43,512 names an unknown account: the call fails with ORA-02291 and the job rolls back. After the fix, a rerun succeeds, but 43,000 lines appear twice in the ledger. Explain why, and rework the load so a failed run can run again.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Senior to staff

    The evening before a release, a code review of a payments package finds WHEN OTHERS THEN NULL in the refund procedure, a COMMIT inside an API procedure, a customer name joined into EXECUTE IMMEDIATE, TRUNC around an indexed date in a monthly report, and BULK COLLECT without LIMIT over a table of 5,000 rows. Which block the release?

    Model answer, Pro. First step and follow-ups

  3. Mid to senior

    A housekeeping procedure purge_rows(p_table, p_days) runs EXECUTE IMMEDIATE with 'DELETE FROM ' || p_table || ' WHERE created_at < SYSDATE - ' || p_days. It has no AUTHID clause, its owner holds DELETE ANY TABLE, and an operations role can execute it. What do you raise in code review, and how do you rework it?

    Model answer, Pro. First step and follow-ups

Flashcards · 8 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. In which order does a PL/SQL code review read a package?

    Costliest failures first: errors and transactions, loops and bulk SQL, dynamic SQL and rights, package state, performance, then tests.

  2. What does one successful call prove nothing about?

    The state a failure leaves partway, the effect of a caller's text inside a statement, and the count a second call in the same session reports.

  3. What does the review's errors and transactions row look for?

    No WHEN OTHERS THEN NULL, no COMMIT in a reusable unit, and the error codes documented in the specification.

  4. What do the loops and the dynamic SQL rows of the checklist ask for?

    SQL before loops, and LIMIT loops whose exit keeps the last batch; then binds for values, an allowed list for names, and an explicit AUTHID.

  5. What do the package state and performance rows check?

    That no variable outlives a call without a reason, and that conditions keep bare columns, with the plan read rather than guessed: TRUNC around an indexed column is a finding.

  6. Why did the author's test miss the bug that drops the last 50 lines of a 250-line feed?

    Its 200 lines filled the LIMIT exactly and none of them failed, so the exit placed straight after the fetch never had a short last batch to drop.

  7. Which three answers change with the release?

    A stepped FOR loop, which exists from 21c; a BOOLEAN column, which 26ai has and 19c replaces with NUMBER(1) and a check of 0 or 1; and CREATE TABLE IF NOT EXISTS, which reached 19c in Release Update 19.28 and before that needed a block that ignores ORA-00955 only.

  8. What shape does a strong drill answer have, and which sources are worth practising from?

    The deciding sizes first, rows per run, callers at once and what must survive a failure, then the failure, the fix and the test that proves it. Practise from the vendor's reference checked in your own lab; an answer written for an old release needs rechecking, and lists of real interview questions from companies have no source.

Key takeaways

  • A PL/SQL code review reads a package in the order its failures cost: errors and transactions, loops, dynamic SQL and rights, state, performance, then tests.
  • The planted problems hide on the happy path and show only when a line fails, a feed is not a round size, a caller sends odd text or a session calls twice.
  • A pattern checker catches only the patterns it knows, so the reader still walks every stage.
  • A strong drill answer names its release, then shows the failure, the fix and the test.

Terms in this lesson

happy path
The run of a program in which every input is valid and nothing fails. A test of the happy path alone cannot show what a failure, an unusual input or a second call does.
swallowed exception
An exception that a handler catches and then drops, as WHEN OTHERS THEN NULL does, so the caller sees a normal return although the work failed.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.