Chapter 9: SQL Essentials for Accounting Analytics

Accounting Analytics: An Integrated Approach

In this chapter

Part III of V: Accounting Analytics with SQL

Chapter 1 of 4 in this Part

  • 9.1 Standard Costs in Brief
  • 9.2 Why Query the Database Directly
  • 9.3 SQLite and DB Browser for SQLite
  • 9.4 The SELECT Statement
  • 9.5 When a Query Fails
  • 9.6 Calculated Columns and Functions
  • Guided Tutorial 9.1
  • 9.8 Filtering Rows with WHERE
  • 9.9 Missing Values and NULL
  • Guided Tutorial 9.2
  • 9.11 Sorting and Limiting Results
  • 9.12 Saving, Documenting, and Sharing Queries
  • 9.13 Reading and Correcting Queries
  • Guided Tutorial 9.3
  • 9.15 Looking Ahead

Learning objectives

  1. Explain when querying a database directly serves an accounting analysis better than working in a spreadsheet, and how a saved query documents the work.
  2. Set up DB Browser for SQLite with a working copy of the Charles River database, and read a table’s structure before querying it.
  3. Write SELECT statements that choose columns, rename them with aliases, calculate new values with arithmetic and functions, and list the distinct values a column holds.

Learning objectives (continued)

  1. Filter rows with WHERE, using comparison and logical operators, BETWEEN, IN, LIKE, and tests for missing values, and compare values of the right type.
  2. Sort and limit query results to rank transactions and find the largest and smallest items.
  3. Save queries in documented scripts, move their results into Excel, and find and correct syntax and logical errors.

This one is a database question

  • The controller turns to cost of goods sold, and one line in it: manufacturing variance
  • It rose each year, to about three-quarters above fiscal 2024, while sales grew less than a tenth
  • Is production less efficient, or do the standards no longer describe the plant?
  • The detail sits in tables the Part II workbook never imported

9.1 Standard Costs in Brief

Standard costing moves the leftover to a variance

  • Materials go to work in process; conversion cost to a clearing account as incurred
  • Completed units go to finished goods at standard, relieving both
  • At a work order’s close, actual less standard moves to the manufacturing variance account
  • Reported with cost of goods sold; a positive overhead part is underapplied overhead

9.2 Why Query the Database Directly

A query asks the database for only what you need

  • Part II’s imports copy rows, and a ledger-sized table slows a workbook
  • A spreadsheet analysis grows hard to implement and to audit (Borthick et al.)
  • A query is a written request: nothing is copied until you ask, and it can be rerun
  • SQL is declarative: you state what you want, and the database finds it

Import or query: two paths to an analysis

Two paths to an analysis. The upper path, from Part II, imports the chosen rows of the CharlesRiver.xlsx Tables into an analysis workbook with Power Query, whose steps are recorded and can be refreshed, and combines related tables through merges or lookups before analyzing them. The lower path, from Part III, sends a saved SQL query to CharlesRiver.sqlite, which returns only the rows and columns requested. The query text documents the extraction and runs again when the data changes.
Figure 9.1 · Importing Data Compared with Querying It

9.3 SQLite and DB Browser for SQLite

One database file, one tool, one working copy

  • SQLite keeps a whole database in a single file, with no server to install
  • Core SQL works the same in every major system; each adds its own dialect
  • DB Browser’s four tabs: Database Structure, Browse Data, Edit Pragmas, Execute SQL
  • Query a copy, CharlesRiver_Work.sqlite, so the download is never changed

Database Structure lists every column and its type

Detail of the Database Structure tab, with the GLEntry table expanded to its sixteen columns. The outlined name and type columns show INTEGER for the keys, the other identifiers and the fiscal year and period, REAL for Debit and Credit, and TEXT for the dates, the voucher type and number, the source document type and the description, with each column's schema line beside them.
Figure 9.2 · The Database Structure Tab of DB Browser for SQLite (detail)

Watch out: do not type in Browse Data

  • A value typed into a cell there edits the table
  • Write Changes and Revert Changes, normally grayed out, become available
  • Write Changes saves the edit into the file; Revert Changes undoes it
  • If asked on closing whether to save changes you did not intend, click Discard

9.4 The SELECT Statement

SELECT names the columns; FROM names the table

SELECT AccountNumber, AccountName, AccountType
FROM Account;
  • SELECT * returns every column; a list returns only those, in that order
  • The result set is a new answer each time: the table never changes
  • With several queries in the editor, select one and run the selection

9.5 When a Query Fails

SQLite names the problem

Table 9.1 · Common SQLite error messages and their usual causes

Message Usual cause
no such column: AccountNumbr A column name misspelled, or a text value written without quotes
no such table: Accounts A table name misspelled
near "WHERE": syntax error Clauses out of order, or a comma or keyword missing before the word named
unrecognized token: "'Asset" A quotation mark opened but never closed

Watch out: when a query misbehaves, check these first

  • Was the whole query selected before you ran it?
  • Does the title bar show CharlesRiver_Work.sqlite?
  • Are the names spelled as on the Database Structure tab?
  • Save SQL file saves the script; Ctrl+S is Write Changes, which saves the database

9.6 Calculated Columns and Functions

Aliases and arithmetic make new columns

  • AS gives a result column a new heading; the table keeps its names
  • A calculated column applies +, -, * and / to each row
  • ROUND to two places rounds an amount to the cent
  • Two INTEGER values divide as integers: multiply one by 1.0 for a fraction

Functions that round, convert and clean text

Table 9.2 · Scalar functions and expressions used in this Part

Function Returns Example Result
ROUND(x, d) x rounded to d decimal places ROUND(1234.567, 2) 1234.57
ABS(x) x without its sign ABS(-2011.14) 2011.14
CAST(x AS type) x converted to INTEGER, REAL, or TEXT CAST('2026' AS INTEGER) 2026
UPPER(x) x in capital letters UPPER('llc') LLC
TRIM(x) x without spaces at either end TRIM(' LLC ') LLC

Functions that take text apart and fill gaps

Table 9.2 · Scalar functions and expressions used in this Part

Function Returns Example Result
REPLACE(x, a, b) x with every a replaced by b REPLACE('WO-2026', '-', '') WO2026
LENGTH(x) the number of characters in x LENGTH('WO-2026') 7
SUBSTR(x, s, n) n characters of x, from position s SUBSTR('WO-2026', 4, 4) 2026
SUBSTR(x, s) the rest of x, from position s SUBSTR('JE-2026-000296', 9) 000296
COALESCE(x, y) x, or y when x is NULL COALESCE(NULL, 'open') open

DISTINCT shows the values a column holds

  • SELECT DISTINCT SourceDocumentType FROM GLEntry lists each kind of source once
  • Use it before filtering: a filter on a value no row holds returns nothing, silently
  • SQLite’s ROUND treats negative places as zero, unlike Excel’s
  • A SELECT without FROM is a quick way to test an expression

Check your understanding

What does each expression return in SQLite?

  1. 7 / 2
  2. 7 / 2.0
  3. ROUND(1234.567, -2)
  4. UPPER(TRIM(' llc '))

SQLite has its own rules

  1. 3: two INTEGER values divide as integers
  2. 3.5: a REAL operand keeps the fraction
  3. 1235.0: negative places count as zero, unlike Excel’s 1200
  4. LLC: spaces trimmed, then capitals

Guided Tutorial 9.1: Opening the Database and Writing Your First Queries

Open the database and query it in seven steps

  1. Make a working copy of the database
  2. Open the working copy and read the structure of the ledger
  3. Start the chapter’s script
  4. Run your first query
  5. Choose the columns you need
  6. Make and correct a deliberate error
  7. Calculate a check on the variance records

Choose the columns you need

Detail of the Execute SQL tab, with numbered labels on the editor tab Chapter09.sql, the Open SQL file(s) and Save SQL file buttons, the Execute all/selected SQL button, the selected query and the result grid. The selected query lists AccountID, AccountNumber, AccountName, AccountType and AccountSubType from the Account table, and the grid, scrolled to the rows around the manufacturing accounts, outlines account 1090 Manufacturing Cost Clearing with AccountID 92 and account 5080 Manufacturing Variance with AccountID 93.
Figure 9.3 · Choosing Columns from the Account Table (detail)

A misspelled column turns the pane red

A mock of the Execute SQL tab. The editor holds the query from Step 5 with AccountNumber misspelled as AccountNumbr. The message pane below it is red with white text: Execution finished with errors, Result: no such column: AccountNumbr, and the line at which the statement starts.
Figure 9.4 · A Misspelled Column in the Message Pane

Every close adds up

Detail of the Execute SQL tab with the query that selects each work order close's material, conversion and total variance under the aliases Material, Conversion and Total, and a CheckDifference column that rounds material plus conversion minus total to two decimals. The first rows of the result show a CheckDifference of zero in every row, and that column is outlined.
Figure 9.5 · A Calculated Check on the Variance Records (detail)

Checkpoint

Check that:

  • the title bar shows the path of CharlesRiver_Work.sqlite, not the file you downloaded, and Write Changes is grayed out;
  • Chapter09.sql holds the header comment and three queries, each under a comment;
  • you have recorded AccountID 92 for account 1090 and AccountID 93 for account 5080;
  • the misspelled column produced a red message pane, and the corrected query ran;
  • the CheckDifference column is zero in the rows you can see.

9.8 Filtering Rows with WHERE

WHERE keeps the rows that meet a condition

  • Numbers go without quotes; text goes in single quotes and must match exactly
  • UPPER(TRIM(x)) compares text whose case or spaces cannot be trusted
  • A value compared with a column takes the column’s type where it can
  • Text compared with a number finds nothing: a year cut from text needs quotes or CAST

Comparison and logical operators

Table 9.3 · Comparison and logical operators in a WHERE clause

Operator Meaning Example
= and <> Equal to, not equal to AccountID = 93
<, >, <=, >= Less than, greater than, or equal Debit > 1000
BETWEEN a AND b Within a range, including both ends FiscalYear BETWEEN 2025 AND 2026
IN (a, b, c) Equal to any value in a list AccountNumber IN (1090, 5080)
LIKE Matches a pattern VoucherNumber LIKE 'JE-%'
IS NULL, IS NOT NULL Has no value, has a value ClosedDate IS NULL
AND, OR, NOT Combine or reverse conditions NOT Status = 'Closed'

AND is evaluated before OR

  • AND binds first, the way multiplication comes before addition
  • One account’s rows in two years, written without parentheses, returns every row of the second year
  • That is hundreds of thousands of rows from every account
  • Whenever AND meets OR, add parentheses

Dates are text in year-month-day form

  • Compare a date column with a quoted date written the same way
  • Any other form, such as month/day/year, compares wrongly without a warning
  • BETWEEN includes both ends, but a date with a time sorts after the end date
  • LIKE matches patterns: % for any run of characters, _ for one

9.9 Missing Values and NULL

NULL is unknown, so = NULL is never true

  • NULL is not zero or empty text: the value is unknown or does not exist
  • Find missing values with IS NULL; exclude them with IS NOT NULL
  • NULL spreads through calculations: a NULL in an addition makes the result NULL
  • COALESCE replaces a NULL, which is a decision, not a correction

IS NULL finds what = NULL cannot

A table of six Charles River work orders released in November 2026: two closed, with a ClosedDate, and four released or completed but not yet closed, whose ClosedDate is NULL. For each work order, three expressions are evaluated. ClosedDate = NULL is NULL for every row, so a WHERE clause that uses it keeps none of them. ClosedDate IS NULL is true for the four open work orders, which it keeps, and false for the two closed ones. COALESCE(ClosedDate, 'open') shows the date for the closed work orders and the word open for the others.
Figure 9.6 · How WHERE Treats a Missing Value

Check your understanding

How many work orders or ledger rows does each condition keep?

  1. ClosedDate = NULL
  2. ClosedDate IS NULL
  3. SourceDocumentType = 'workorderclose'
  4. SourceDocumentType LIKE 'workorderclose'

Two of the four silently return no rows

  1. None: an ordinary comparison with NULL is never true
  2. Every work order with no ClosedDate
  3. None: = matches capitals exactly here, and the data says WorkOrderClose
  4. Every posting of a work order close, to any account: LIKE ignores case in SQLite

Guided Tutorial 9.2: Filtering the Ledger to the Variance Postings

Filter the ledger in eight steps

  1. Find the accounts by their numbers
  2. Filter the ledger to the variance account for fiscal 2026
  3. List the source document types
  4. Examine the journal entry and exclude it
  5. Confirm the check for every work order close
  6. Find the work orders that have not yet closed
  7. Label the open work orders with functions
  8. Test a year taken from the text

Two kinds of source post to the variance account

A mock of the Execute SQL tab. The editor, on the tab Chapter09.sql, is scrolled to a comment and a query that selects the distinct SourceDocumentType values of the ledger rows for AccountID 93 in fiscal year 2026. The result holds two rows, WorkOrderClose and JournalEntry, and the JournalEntry row is outlined. The message pane reports that two rows were returned.
Figure 9.7 · The Source Document Types Behind the Variance Account

Checkpoint

Check that:

  • the ledger holds several thousand variance postings for fiscal 2026, of two source document types;
  • the only journal entry is the closing entry JE-2026-000296, found by its source document type or by LIKE, and excluding it leaves the WorkOrderClose postings;
  • no work order close has parts that differ from its total;
  • ClosedDate IS NULL finds the open work orders, and ClosedDate = NULL finds none;
  • SUBSTR(WorkOrderNumber, 4, 4) = 2026 finds no rows, while = '2026' finds every open work order.

9.11 Sorting and Limiting Results

ORDER BY sorts; LIMIT keeps the first rows

  • ASC is the default; DESC sorts from largest to smallest
  • Later columns break ties; you can sort by an expression, such as ABS of the variance
  • In an ascending sort, SQLite places NULLs first
  • LIMIT with ORDER BY ranks; LIMIT alone returns whichever rows come first

Watch out: LIMIT is a dialect difference

  • SQLite, PostgreSQL and MySQL use LIMIT
  • Microsoft SQL Server uses SELECT TOP
  • The SQL standard and Oracle use FETCH FIRST … ROWS ONLY
  • If a book query fails elsewhere, look for a dialect difference first

9.12 Saving, Documenting, and Sharing Queries

A commented script is evidence

  • Save SQL file saves the script; reopen it, send it, run it again
  • -- starts a one-line comment; /* and */ enclose several lines
  • A header records the purpose, the database, the preparer and date, and the checks
  • A comment above each query says what it answers

Copy with Headers takes a result to Excel

  • Select the result’s cells, or click one and press Ctrl+A
  • Right-click and choose Copy with Headers, then paste into Excel
  • DB Browser loads a large result in blocks, so it may ask to load the rest
  • For a large result or a file to keep, Export to CSV

9.13 Reading and Correcting Queries

Clauses come in a fixed order

A query written clause by clause, each clause labeled with its role. SELECT lists the columns, including a calculated column with an alias. FROM names the WorkOrderClose table. WHERE keeps the closes of fiscal 2026 with BETWEEN. ORDER BY sorts the total variance in descending order, and LIMIT keeps the first ten rows. A note states that the clauses must be written in this order and that a comment line above the query explains what it answers.
Figure 9.8 · The Anatomy of a SQL Query

A logical error runs and answers another question

  • An error that stops a query names the problem; a logical error does neither
  • Errors recur in similar ways from student to student (Taipalus et al.)
  • SELECT AccountNumber AccountName runs: the second name becomes an alias
  • Know what the result should look like: row count, first rows, a control total

Watch out: SQLite’s tolerance can hide mistakes

  • It accepts a double-quoted value as text when no column has that name
  • It treats a column’s declared type as a preference, not a rule
  • It converts values in some comparisons and not in others
  • Single quotes for text and like-for-like comparisons make queries travel

Check your understanding

Name the logical error in each case.

  1. A query meant to show two columns shows one
  2. One account’s rows in two years returns rows from every account
  3. A year cut from a document number matches nothing
  4. A filter on a status returns no rows

Each one ran without complaint

  1. A missing comma: the second column became an alias
  2. AND and OR without parentheses
  3. Text compared with a number: quote the year or CAST it
  4. A value no row holds: check it with DISTINCT first

Guided Tutorial 9.3: Ranking the Largest Work-Order Variances

Rank the variances in six steps

  1. Rank the largest variances of fiscal 2026
  2. Measure the share of overhead
  3. Look at the favorable end of the ranking
  4. List the closes above a review threshold
  5. Copy a result into Excel
  6. Complete and save the script

Overhead makes up most of each large variance

Detail of the Execute SQL tab with the query that selects the work order closes of fiscal 2026 with their material, labor, overhead and total variance, sorts them by total variance in descending order and keeps ten rows. The grid shows the ten closes, with totals of a few thousand dollars each, and the outlined Overhead column holds most of each total.
Figure 9.9 · The Ten Largest Work-Order Variances of Fiscal 2026 (detail)

The script documents itself

Detail of the first lines of the saved script Chapter09.sql. A header comment records the purpose, the database file, the preparer and date, and the checks, and the first two queries of Tutorial 9.1 follow, each under a comment line, one selecting every column of the Account table and one selecting the columns that identify each account.
Figure 9.10 · The Chapter’s Script After Tutorial 9.3 (detail)

Checkpoint

Check that:

  • the ten largest variances of fiscal 2026 are each a few thousand dollars, and overhead makes up most of each one;
  • sorted in ascending order, the ranking starts with favorable variances of up to about two thousand dollars;
  • the review threshold finds several hundred closes above a thousand dollars either way, only a small share of them favorable;
  • the ten rows pasted into Excel with their headings;
  • Chapter09.sql is saved beside your working copy, with a completed header and a comment above each query.

9.15 Looking Ahead

Back to the controller’s question

  • The variance is recorded when a work order closes, in material and conversion parts
  • In the largest closes of the year, most of it is overhead
  • The ledger holds the same amounts, plus a year-end close to set apart
  • Which products, which years, what drives the overhead: Chapter 10 joins the tables

Summary

  • A saved query extracts only what it asks for, and documents how
  • SELECT, FROM, aliases, calculated columns, functions and DISTINCT build a result
  • WHERE filters with exact types, parentheses, quoted dates and IS NULL
  • ORDER BY and LIMIT rank; scripts with comments make the work reviewable
  • Errors that stop a query name themselves; logical errors need a check

Key terms

Calculated column · Clause · Column alias · Comment · Declarative language · DISTINCT · Logical error · Manufacturing variance · NULL · Operator precedence · Query · Result set · Scalar function · SQL (Structured Query Language) · SQL dialect · SQL script · SQLite · Standard cost · Syntax error · WHERE clause · Wildcard · Working copy

Practice and next steps

  • Financial Accounting
    • Exercise 9.1: Reviewing the Chart of Accounts
    • Exercise 9.2: The Purchase Price Variance in the Ledger
  • Managerial Accounting
    • Exercise 9.3: Standard Cost Components of Manufactured Items
    • Exercise 9.4: Open Work Orders at Year-End
  • Auditing
    • Exercise 9.5: Shipments That Need Follow-Up
    • Exercise 9.6: Missing Values in Time and Labor Records

Next: Chapter 10: Joining and Summarizing Data in SQL

About these slides

These slides accompany Accounting Analytics: An Integrated Approach, licensed under CC BY-SA 4.0. Every figure and table comes from the chapter:

https://aa.accountinganalyticshub.com/chapters/09-sql-essentials/chapter.html