Chapter 11: Analytical Queries in SQL

Accounting Analytics: An Integrated Approach

In this chapter

Part III of V: Accounting Analytics with SQL

Chapter 3 of 4 in this Part

  • 11.1 Beyond Grouping
  • 11.2 Classifying Rows with CASE
  • 11.3 Working with Dates
  • 11.4 Turning Rows into Columns with Conditional Aggregation
  • Guided Tutorial 11.1
  • 11.6 Window Functions
  • Guided Tutorial 11.2
  • 11.8 Common Table Expressions
  • 11.9 Saving an Analysis as a View
  • Guided Tutorial 11.3
  • 11.11 Looking Ahead

Learning objectives

  1. Classify rows with CASE expressions, and turn values into columns with conditional aggregation.
  2. Use SQLite’s date functions to group transactions by period, find period ends, and measure the time between two dates.
  3. Apply window functions, with partitions, frames, ranking functions, and offset functions such as LAG, to compare rows and periods without collapsing the detail.

Learning objectives (continued)

  1. Organize a multi-step analysis as a chain of common table expressions that can be read and checked one step at a time.
  2. Save a repeatable analysis as a view in a working copy of the database, and query it like a table.
  3. Combine these techniques into a monthly performance measure, cut off at the last day with time records, that management can track.

When did it start, and is it still growing?

  • Output was flat, but the plant recorded far more hours than its standards allow
  • Recorded direct hours stayed within the standard; indirect time and overtime grew
  • The plant manager will ask: when did it start, and is it still getting worse?
  • How much is overtime, and is it spread across the plant or concentrated?

11.1 Beyond Grouping

Four features answer what grouping cannot

  • CASE places a row in a category that no column records
  • Date functions turn dates into periods and measure the time between them
  • Window functions calculate across rows without collapsing them
  • Common table expressions and views break a long analysis into named, saved steps

11.2 Classifying Rows with CASE

CASE classifies rows that no column records

  • The searched form tests conditions in order; the first true one wins
  • Without an ELSE, a row that meets no condition gets NULL, so write one
  • The simple form compares one column with values, so WHEN NULL never matches
  • ORDER BY CASE sorts in an order no column holds, such as statement order

11.3 Working with Dates

SQLite’s date functions

Table 11.1 · SQLite date functions, for the date d of 2026-03-06

Function Returns Example result
strftime('%Y', d) The year, as text 2026
strftime('%m', d) The month, as two digits 03
strftime('%Y-%m', d) The year and month 2026-03
strftime('%w', d) The weekday, 0 for Sunday to 6 for Saturday 5
date(d) The date without its time 2026-03-06
date(d, 'start of month') The first day of the month 2026-03-01
date(d, '+1 month', '-1 day') A date moved by the modifiers, in order 2026-04-05
julianday(d) The date as a number of days 2461105.5

Month ends and the days between dates

  • date(d, 'start of month', '+1 month', '-1 day') is the month’s last day
  • So a posting date equal to it is a month-end entry, mostly accruals and depreciation
  • The difference of two julianday values is the number of days between them
  • Here the fiscal year is the calendar year, so strftime and FiscalPeriod agree

Watch out: date logic is easy to get subtly wrong

  • Modifier order matters: adding a month to the 31st can land in the month after next
  • Apply ‘start of month’ first whenever you step from month to month
  • strftime and date return text; julianday returns a number. Compare results with the matching type
  • Other systems name these functions differently, so date logic travels least well

11.4 Turning Rows into Columns with Conditional Aggregation

Conditional aggregation turns rows into columns

On the left, the postings to account 1090 in fiscal 2026 summarized by source document type, one row each for journal entries, payroll summaries, production completions, and work order closes, with their debits and credits. On the right, one row for fiscal 2026 with a column each for payroll, journal entries, and completions. Numbered badges link each source's debit or credit on the left to the column that receives it, and three lines below give the CASE expression behind each column. A note explains that the work order close rows meet no condition and add zero to every column.
Figure 11.1 · Conditional Aggregation Turns Rows into Columns

A CASE inside SUM is the SQL form of SUMIFS

  • The query names its columns, so it suits categories you know in advance
  • COUNT(CASE WHEN … THEN 1 END) counts rows, because COUNT skips the NULLs
  • SUM(CASE WHEN … THEN 1 ELSE 0 END) gives the same count as a 1-or-0 flag
  • A PivotTable, by contrast, makes a column for every value it finds

Check your understanding

Which tool does each task?

  1. Label each payroll line as pay, salary, employer cost or deduction
  2. Group transactions by month
  3. Find the last day of each month
  4. Count the days from a work order’s release to its completion

Each task has its tool

  1. A searched CASE, with an ELSE
  2. strftime('%Y-%m', d) in GROUP BY
  3. date with ‘start of month’, ‘+1 month’ and ‘-1 day’, in that order
  4. The difference of two julianday values

Guided Tutorial 11.1: Classifying the Payroll and Measuring the Hours by Month

Classify and measure in five steps

  1. Classify the manufacturing payroll, and tie it to the ledger
  2. Pivot the labor time by month
  3. Find how far the time records fall short of the month’s end
  4. Compare the overtime share of each kind of labor
  5. Measure how long work orders take

Indirect time almost vanishes for four months

Detail of the result grid of the monthly labor time query, the first eight months, January to August 2024, with the columns WorkMonth, DaysWithTime, DirectHours, IndirectHours and OvertimeHours. January has more indirect than direct hours. The IndirectHours of February to May, outlined, are far lower than in the months that follow, and indirect time climbs from June.
Figure 11.2 · Manufacturing Labor Time in the First Months of 2024 (detail)

Direct overtime rose; indirect overtime stayed flat

Detail of the result grid of the overtime shares by month, the twelve months of 2026, with the columns WorkMonth, DirectOvertimeShare and IndirectOvertimeShare. The direct share, outlined, is a quarter or more in most months, with two dips, while the indirect share stays below a tenth.
Figure 11.3 · Overtime Shares by Kind of Labor and Month (detail)

Checkpoint

Check that:

  • the payroll categories other than employee deductions add up to the ledger’s charge to account 1090 for fiscal 2026;
  • the monthly pivot has one row for each of the 36 months, with far less indirect time from February to May 2024 than later in the year, and few days of time records in December 2026;
  • the time records end nearly three weeks before the end of December 2026;

Checkpoint (continued)

  • the overtime share of direct labor rose from about a tenth to a quarter or more, while that of indirect labor stayed at about a twelfth;
  • the average time from release to completion more than doubled from fiscal 2024 to fiscal 2026;
  • Chapter11.sql holds the header and the queries of this tutorial, each under a comment.

11.6 Window Functions

A window calculates across rows and keeps them

  • An aggregate with GROUP BY collapses rows; a window function keeps every row
  • OVER () looks at every row; PARTITION BY at the rows that share its columns
  • GROUP BY decides the rows of the result; PARTITION BY what each calculation sees
  • ORDER BY inside OVER turns a SUM into a running total

Watch out: the default window includes ties

  • With ORDER BY, the default window runs through the current row’s peers
  • Peers are the rows tied with it on the ORDER BY columns
  • Tied rows all show the total through the last of them
  • Stop the window at the current row with ROWS UNBOUNDED PRECEDING

Ranking functions number the rows

  • ROW_NUMBER gives every row its own number, even when rows tie
  • RANK gives ties one number and skips the numbers after them
  • DENSE_RANK gives ties one number without skipping
  • For a stable numbering, add a unique column to the window’s ORDER BY

A partition groups the rows; a frame slides

Twenty-four months of fiscal 2025 and 2026 in a column, each with a monthly amount. One bracket shows a partition, the months of one fiscal year, and beside it a year-to-date total that restarts in January. A second bracket shows a moving frame of three months, the current month and the two before it, sliding down the column, with the total of the three months shown beside each row. A note explains that a partition decides which rows belong together and a frame decides which of them each row's calculation uses.
Figure 11.4 · Partitions and Frames in a Window Function

Frames, named windows and offsets

  • ROWS BETWEEN 11 PRECEDING AND CURRENT ROW is a trailing twelve months
  • A ROWS frame counts rows: a missing month reaches one month further
  • WINDOW w AS (...) names a window that several functions share
  • LAG(x) partitioned by month: the latest earlier year with that month

The window functions of this chapter

Table 11.2 · Window functions used in this chapter

Function Returns
ROW_NUMBER() A sequence number within the partition: 1, 2, 3, even for ties
RANK(), DENSE_RANK() A rank in which ties share a number; RANK skips numbers after a tie
SUM(x) OVER (...) A total over the window: the whole partition, or a running or moving total
AVG, COUNT, MIN, MAX The same aggregates over the window
LAG(x, n), LEAD(x, n) The value of x n rows before, or after, the current row

The clauses that define a window

Table 11.3 · Clauses that define a window

Clause Effect
OVER () The window is every row of the result
PARTITION BY Divides the rows into groups that the window never crosses
ORDER BY Orders each partition; for an aggregate, the default window runs to the current row and its peers
ROWS BETWEEN n PRECEDING AND CURRENT ROW The current row and the n rows before it
ROWS UNBOUNDED PRECEDING Every row from the first of the partition to the current one
WINDOW w AS (...) Names a window, for use as OVER w

Windows are calculated with SELECT

  • After WHERE, GROUP BY and HAVING; before DISTINCT, ORDER BY and LIMIT
  • So a window can work on grouped totals: a running total of monthly sums
  • But WHERE and HAVING cannot filter on its result (“misuse of …”)
  • To keep the top-ranked rows, filter the window’s result in an outer query

Check your understanding

Which window does each question need?

  1. A year-to-date total that restarts each year
  2. A trailing twelve-month total
  3. Each month against the same month a year earlier
  4. The three largest months of each year

Each question has its window

  1. SUM partitioned by year, ordered by month
  2. SUM with ROWS BETWEEN 11 PRECEDING AND CURRENT ROW
  3. LAG partitioned by month, ordered by year
  4. RANK partitioned by year, filtered in an outer query

Guided Tutorial 11.2: Tracking the Gap Month by Month

Track the gap in seven steps

  1. Calculate the monthly gap and its year-to-date total
  2. Compare each month with the same month a year earlier
  3. Rank the months within each year
  4. Count the pay dates of the largest months
  5. Count the pay dates of every month
  6. Rank the work centers by the hours recorded against their plan
  7. Add the plant’s total

The year-to-date gap grew in each year

Detail of the result grid of the monthly gap in account 1090 for the twelve months of fiscal 2026, with the columns FiscalYear, FiscalPeriod, Gap and GapYearToDate. The outlined year-to-date column starts at the January gap and grows every month to the December total.
Figure 11.5 · The Monthly Gap in Account 1090 and Its Year-to-Date Total (detail)

The largest months have three pay dates

Detail of the result grid that keeps the three largest months of each year, nine rows with the columns FiscalYear, FiscalPeriod, Gap, PayDates and GapRank. The PayDates cells that show three pay dates are outlined. They include the month ranked first in every year and some of the months ranked second or third, and the other months have two.
Figure 11.6 · The Largest Months of the Gap and Their Pay Dates (detail)

Packing and QA record far more than planned

A mock of the Execute SQL editor and its result. The editor, on the tab Chapter11.sql, shows the start of a query that compares the direct labor hours recorded against each work center's operations with their planned load hours, by the year in which the operations ended, in a derived table. The outer query calculates the excess hours and the ratio of recorded to planned hours, ranks the work centers with RANK over a window partitioned by year, and totals the excess hours of each year with SUM over the same partition. The grid is scrolled to the five work centers of 2026. Packing and Quality Assurance rank first and second with about four times their planned hours, and those rows are outlined, while the plant's total excess is negative.
Figure 11.7 · Work Centers Ranked by Hours Recorded Against Plan

Checkpoint

Check that:

  • the year-to-date gap restarts each January, and its December figure grew in each year;
  • the same-month comparison is NULL for fiscal 2024 and shows larger gaps in most months of fiscal 2026, with July and August 2026 far from the rest;
  • the ranking query returns the three largest months of each year, filtered in the outer query;

Checkpoint (continued)

  • the largest month of each year has three pay dates, each year has two such months, and fiscal 2024 has two pay dates fewer than the later years;
  • Packing and Quality Assurance record several times their planned hours, while the plant as a whole records fewer direct hours than planned;
  • Chapter11.sql holds this tutorial’s queries under their comments.

11.8 Common Table Expressions

A CTE names each step of a long query

  • WITH name AS (…) defines a named subquery at the start of a query
  • Several CTEs, separated by commas, can each use the ones before them
  • The query reads from top to bottom, not from the inside out
  • Check each step by selecting from it on its own

Parameters, and CTE or derived table

  • A one-row CTE can hold a parameter, such as an as-of date
  • A scalar subquery reads it wherever it is needed, so one change updates the query
  • SQLite may calculate a CTE once or treat it as a derived table
  • So choose for readability, and time the query if its speed matters

11.9 Saving an Analysis as a View

A view saves the query, not the rows

DROP VIEW IF EXISTS MonthlyLaborEfficiency;
CREATE VIEW MonthlyLaborEfficiency AS
WITH ...
SELECT ...;
  • Every query of the view runs the saved query on the current data
  • Leave ORDER BY out: the query that uses the view sorts it

The view lives in the working copy

  • It is the one thing in this Part you write into the database
  • CREATE VIEW takes effect at once, but only Write Changes saves it to the file
  • Closing first and clicking Discard removes it
  • CREATE TEMP VIEW lasts one session and works even read-only

Watch out: a view can break when its tables change

  • A view depends on the tables and columns it uses
  • Remove or change one, and the view can fail or change its meaning
  • SQLite may refuse the change, or the view fails when next queried
  • Document every view: purpose, source tables, and the checks its results pass

Check your understanding

What happens in each case?

  1. You close the database without clicking Write Changes
  2. A column the view uses is removed
  3. You put ORDER BY inside the view
  4. You need the view only for this session

Each case has its consequence

  1. DB Browser asks; Discard removes the view
  2. SQLite refuses to drop it while the view uses it
  3. Leave it out: the query using the view sorts
  4. Use CREATE TEMP VIEW

Guided Tutorial 11.3: Building the Monthly Measure and Saving It as a View

Build and save the measure in nine steps

  1. Build the output step and check it
  2. Build the hours step and check it
  3. Join the two steps, and check the join
  4. Add the trailing twelve months
  5. Look at the months before the window is full
  6. Save the measure as a view
  7. Find the view, and write it to the file
  8. Query the view
  9. Complete and save the script

The chain of CTEs in the monthly measure

A flow diagram of the view MonthlyLaborEfficiency. Two steps at the top run side by side: MonthlyOutput, which summarizes production completion lines and items into standard hours by month, up to the last day with time records, and MonthlyHours, which summarizes labor time entries into direct, indirect, and overtime hours by month. Both feed a step named Monthly, which joins them by month. Monthly feeds Trailing, which calculates the monthly ratio and the trailing twelve-month direct, indirect, and total hours per standard hour and overtime share over a named window. The final SELECT keeps the months whose window holds twelve monthly rows.
Figure 11.8 · The Chain of Common Table Expressions in the Monthly Measure

The saved view in the database

A mock of the Database Structure tab of DB Browser for SQLite with CharlesRiver_Work.sqlite open. The Tables and Indices groups are collapsed with their counts. The Views group, with one view, is expanded and shows MonthlyLaborEfficiency with its six columns: Month, HoursPerStandardHour, TTMDirect, TTMIndirect, TTMTotal, and TTMOvertimeShare. The view is outlined, and the Triggers group below it is empty.
Figure 11.9 · The Saved View in the Database Structure Tab

The measure for the year, queried from the view

A mock of the Execute SQL editor and its result. The editor, on the tab Chapter11.sql, is scrolled to a query that selects every column of the view MonthlyLaborEfficiency for the months of 2026. The result grid shows twelve months with the monthly hours per standard hour, which swings from month to month, and the trailing twelve-month direct, indirect, and total hours per standard hour and overtime share, which change slowly. The trailing twelve-month columns are outlined. A note marks the December row as provisional, because its indirect time ends a week before its direct time and output.
Figure 11.10 · The Monthly Measure for Fiscal 2026, Queried from the View

Checkpoint

Check that:

  • each step of the chain returns 36 months, including the join of the two summaries, and the view keeps the 25 months with full windows;
  • the months before December 2024 have partial windows, led by the start-up month of January 2024;
  • the trailing indirect hours per standard hour level off from the middle of fiscal 2025, while the trailing direct figure creeps up through fiscal 2026;
  • the trailing total reaches its highest point in October 2026, and each December’s figures equal the ratios of Tutorial 10.3;

Checkpoint (continued)

  • the view MonthlyLaborEfficiency appears under Views on the Database Structure tab of your working copy, and Write Changes has saved it;
  • the query of the view for fiscal 2026 returns the same values as the full query;
  • Chapter11.sql is saved with a completed header and a comment above each query.

11.11 Looking Ahead

Back to the plant manager’s questions

  • When: in the records, mid-way through the first year, as indirect time and overtime grow
  • Still worse? Indirect time has been flat since mid-way through the second year; direct time crept up
  • Overtime: about a fifth of all hours, and more than a quarter of direct hours
  • Where: Packing and QA record several times their plan; other centers record less

Summary

  • CASE classifies rows; a CASE inside an aggregate turns categories into columns
  • strftime groups dates into periods; date finds period ends; julianday differences count elapsed days
  • Window functions rank, accumulate and compare rows while keeping them
  • CTEs name the steps of a long query; a view saves it for every month’s run
  • The monthly measure combines them, cut off at the last day with time records

Key terms

CASE expression · Common table expression (CTE) · Conditional aggregation · Date function · Frame · LAG and LEAD · Named window · Partition · Ranking function · Running total · Trailing twelve months · View · Window function

Practice and next steps

  • Financial Accounting
    • Exercise 11.1: The Financial Statements from the Ledger
    • Exercise 11.2: Monthly Revenue with Year-to-Date and Prior-Year Comparisons
  • Managerial Accounting
    • Exercise 11.3: Ranking Items by Unit Margin Within Each Product Line
    • Exercise 11.4: Is Overtime Concentrated?
  • Auditing
    • Exercise 11.5: Journal Entry Tests in SQL
    • Exercise 11.6: Requisitions Just Under the Approval Threshold

Next: Chapter 12: Audit Analytics 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/11-analytical-queries/chapter.html