<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Michael Nocito</title>
    <description>The latest articles on DEV Community by Michael Nocito (@michaelnocito).</description>
    <link>https://dev.to/michaelnocito</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4045753%2F6a1c6ad3-194c-4cee-bcc7-1eb047452b0d.png</url>
      <title>DEV Community: Michael Nocito</title>
      <link>https://dev.to/michaelnocito</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/michaelnocito"/>
    <language>en</language>
    <item>
      <title>One Cell, Many Values: When a Column Holds a List</title>
      <dc:creator>Michael Nocito</dc:creator>
      <pubDate>Tue, 08 Sep 2026 13:00:05 +0000</pubDate>
      <link>https://dev.to/michaelnocito/one-cell-many-values-when-a-column-holds-a-list-2a90</link>
      <guid>https://dev.to/michaelnocito/one-cell-many-values-when-a-column-holds-a-list-2a90</guid>
      <description>&lt;p&gt;By &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/about.html" rel="noopener noreferrer"&gt;Michael Nocito&lt;/a&gt;, data analyst · Updated August 19, 2026&lt;/p&gt;

&lt;p&gt;You ask a table for its list of categories, and the answer is 2,923. You were expecting about thirty. Nothing errored, nothing warned you, and the number is not a bug. It is the correct answer to a question you did not mean to ask. This happens when a column stores a &lt;em&gt;list&lt;/em&gt; inside a single cell, and it quietly breaks &lt;code&gt;DISTINCT&lt;/code&gt;, &lt;code&gt;COUNT&lt;/code&gt;, and &lt;code&gt;GROUP BY&lt;/code&gt; all at once. Here is how to spot it in under a minute, why the number came back wrong, and three ways to work with the column, easiest first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The one-sentence version.&lt;/strong&gt; If a cell holds &lt;code&gt;Action,Indie,RPG&lt;/code&gt; instead of one value, then &lt;code&gt;DISTINCT&lt;/code&gt; counts unique &lt;em&gt;combinations&lt;/em&gt; , not unique values, and every count built on top of it inherits the error.&lt;/p&gt;

&lt;p&gt;The worked example throughout is a public Steam games dataset of 125,855 rows with a &lt;code&gt;Genres&lt;/code&gt; column. Every number below is a real result from it, including the wrong one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The symptom: a distinct count that is far too big
&lt;/h2&gt;

&lt;p&gt;The opening move on any new column is to ask what values it holds:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT COUNT(DISTINCT Genres) AS genre_count
FROM games_raw;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The answer came back &lt;strong&gt;2,923&lt;/strong&gt;. A digital storefront does not have 2,923 genres. It has a menu of a few dozen that a developer picks from.&lt;/p&gt;

&lt;p&gt;That gap between the number you got and the number you expected is the single most valuable signal in this whole guide. It is not an inconvenience to route around, it is the finding. Whenever a distinct count on a categorical column comes back in the thousands, stop, because the column is almost certainly not holding what you think it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Look at the values before you count them
&lt;/h2&gt;

&lt;p&gt;Before theorising, look. A count tells you how many, never what:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT DISTINCT Genres
FROM games_raw
LIMIT 20;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Genres&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Adventure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Casual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Casual,Indie,Simulation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Action,Early Access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Action,Adventure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Simulation,Strategy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There it is. Some cells hold one genre and some hold three, separated by commas. The column is not storing a genre, it is storing a list of genres as a piece of text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The habit worth stealing.&lt;/strong&gt; Look first, measure second. If you measure first, you can only ever confirm or deny the thing you already suspected, and the thing you did not suspect stays invisible. Twenty rows of eyeballing is the cheapest step in analysis and it catches problems no summary statistic will report. The &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/exploratory-data-analysis/" rel="noopener noreferrer"&gt;Exploratory Data Analysis&lt;/a&gt; guide builds the rest of that opening routine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure how widespread it is
&lt;/h2&gt;

&lt;p&gt;Twenty rows is an impression. Turn it into evidence, because the answer decides how much work you are in for. If a handful of rows hold lists, that is a small cleanup. If nearly all of them do, the shape of the column is the problem:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT COUNT(DISTINCT Genres) AS values_with_a_comma
FROM games_raw
WHERE Genres LIKE '%,%';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2,898&lt;/strong&gt; of the 2,923 distinct values contain a comma. That is 99 percent. This is not an edge case in the data, it is the data.&lt;/p&gt;

&lt;p&gt;(&lt;code&gt;LIKE '%,%'&lt;/code&gt; reads as "anything, then a comma, then anything." If that syntax is new, &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/sql-like-pattern-matching/" rel="noopener noreferrer"&gt;LIKE and Wildcards&lt;/a&gt; covers it.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Why DISTINCT gave you the wrong answer
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;DISTINCT&lt;/code&gt; did exactly what it promises. It returned each different value in the column once. The problem is that a "value" here is a whole comma separated string, and it has no idea the commas mean anything:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cell contents&lt;/th&gt;
&lt;th&gt;What DISTINCT sees&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Indie&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;One value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Casual,Indie&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A different value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Casual,Indie,Simulation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A third, different value&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three rows in your result, and the word &lt;code&gt;Indie&lt;/code&gt; is in all three. So 2,923 is not the number of genres. It is the number of unique &lt;em&gt;combinations&lt;/em&gt; of genres that at least one game uses. That is a real fact about the data, it just is not the fact anybody asked for.&lt;/p&gt;

&lt;p&gt;The same distortion runs through every aggregate you build on the column. &lt;code&gt;GROUP BY Genres&lt;/code&gt; makes 2,923 groups, most with a handful of games in them. A bar chart of it is unreadable. A percentage share computed from it is wrong, because a game tagged with three genres is counted once, in a bucket of its own, rather than once per genre.&lt;/p&gt;

&lt;h2&gt;
  
  
  The values that never appear alone
&lt;/h2&gt;

&lt;p&gt;Here is the trap that catches people who think they have solved it. Flip the comma filter around and you get the cells holding exactly one genre, which are genre names standing on their own:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT DISTINCT Genres
FROM games_raw
WHERE Genres NOT LIKE '%,%'
ORDER BY Genres;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That returns &lt;strong&gt;24&lt;/strong&gt; rows, and they look right. Action, Adventure, Casual, Indie, RPG, Racing, Simulation, Sports, Strategy, and so on. It is tempting to call that the genre list and move on.&lt;/p&gt;

&lt;p&gt;It is incomplete, and nothing in the result says so. A value that is only ever applied &lt;em&gt;alongside&lt;/em&gt; another one will sit inside the 2,898 lists and never once appear on its own. Probe for one:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT DISTINCT Genres
FROM games_raw
WHERE Genres LIKE '%Gore%';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;132&lt;/strong&gt; combinations come back, and not one of them is &lt;code&gt;Gore&lt;/code&gt; by itself. &lt;code&gt;Gore&lt;/code&gt; is real, it is used, and the 24 row list missed it completely. Splitting the column properly turns up &lt;strong&gt;33&lt;/strong&gt; distinct values, so nine of them never stand alone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The general rule.&lt;/strong&gt; Filtering a list column to the rows with one item gives you the values that &lt;em&gt;can&lt;/em&gt; appear alone, not the values that &lt;em&gt;exist&lt;/em&gt;. Those are different sets, and the difference is silent. Treat that 24 as a first draft to check, never as the answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three ways to work with it, easiest first
&lt;/h2&gt;

&lt;h2&gt;
  
  
  1. Do not split it, filter it with LIKE
&lt;/h2&gt;

&lt;p&gt;If your real question is "show me the RPGs," you never needed the full genre list. Ask whether the cell contains the tag:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT Name, Price
FROM games_raw
WHERE Genres LIKE '%RPG%';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This works whether the tag sits first, last, or in the middle. It needs no new syntax beyond the wildcards, and for most day to day filtering it is the right answer. Guard against matching part of a longer word by wrapping the value in commas first:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT Name, Price
FROM games_raw
WHERE ',' || Genres || ',' LIKE '%,RPG,%';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Good for:&lt;/strong&gt; filtering to one or two known categories. &lt;strong&gt;Not good for:&lt;/strong&gt; counting games per genre across all of them, which would mean writing one query per genre.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Match against a list you already have
&lt;/h2&gt;

&lt;p&gt;If the categories come from a fixed menu, you often have that menu somewhere, or can write it down once. Put it in a small table and join with a pattern:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT g.genre, COUNT(*) AS games
FROM genre_list AS g
JOIN games_raw AS r
  ON ',' || r.Genres || ',' LIKE '%,' || g.genre || ',%'
GROUP BY g.genre
ORDER BY games DESC;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This gives a proper count per genre without any string surgery, and it has a quiet advantage: because the list is yours, a genre with zero games still shows up when you switch to a &lt;code&gt;LEFT JOIN&lt;/code&gt;, which a split can never tell you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good for:&lt;/strong&gt; a known, stable set of categories. &lt;strong&gt;Not good for:&lt;/strong&gt; discovering categories you did not know were in there, which is the exact problem the 24 row list had.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Split the list into one row per value
&lt;/h2&gt;

&lt;p&gt;The complete answer. Break each list apart so one game with three genres becomes three rows, then group normally. Standard SQL has no split function, so this walks the text one comma at a time:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WITH RECURSIVE split(genre, rest) AS (
  SELECT '', Genres || ','
  FROM games_raw
  WHERE Genres IS NOT NULL AND Genres &amp;lt;&amp;gt; ''
  UNION ALL
  SELECT substr(rest, 1, instr(rest, ',') - 1),
         substr(rest, instr(rest, ',') + 1)
  FROM split
  WHERE rest &amp;lt;&amp;gt; ''
)
SELECT trim(genre) AS genre,
       COUNT(*) AS games
FROM split
WHERE trim(genre) &amp;lt;&amp;gt; ''
GROUP BY lower(trim(genre))
ORDER BY games DESC;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Reading it in words: start each game off with an empty genre and its full list with a comma glued on the end, so the last item has a delimiter too. Then repeatedly cut at the first comma, keeping the piece before it and carrying the rest forward, until nothing is left. &lt;code&gt;instr&lt;/code&gt; finds the position of the comma and &lt;code&gt;substr&lt;/code&gt; cuts the text. &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/sql-ctes/" rel="noopener noreferrer"&gt;SQL CTEs&lt;/a&gt; explains the &lt;code&gt;WITH&lt;/code&gt; block itself.&lt;/p&gt;

&lt;p&gt;That returns 33 genres with a game count each, and it is the version you would put in a report.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good for:&lt;/strong&gt; counts per category, charts, anything needing the complete set. &lt;strong&gt;Not good for:&lt;/strong&gt; a first look, since it is the most syntax for the least immediate payoff.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Most databases have a shortcut.&lt;/strong&gt; The recursive version above works everywhere, which is why it is worth seeing once, but you rarely need it: PostgreSQL has &lt;code&gt;unnest(string_to_array(genres, ','))&lt;/code&gt;, SQL Server has &lt;code&gt;STRING_SPLIT(genres, ',')&lt;/code&gt;, MySQL 8 has &lt;code&gt;JSON_TABLE&lt;/code&gt;, BigQuery has &lt;code&gt;UNNEST(SPLIT(genres, ','))&lt;/code&gt;, and DuckDB has &lt;code&gt;unnest(str_split(genres, ','))&lt;/code&gt;. Check yours before writing the long form.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which one to pick
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Your question&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Show me the rows in one category&lt;/td&gt;
&lt;td&gt;LIKE filter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How many rows in each of a few known categories&lt;/td&gt;
&lt;td&gt;LIKE filter, or the list join&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How many rows in every category&lt;/td&gt;
&lt;td&gt;Split, or the list join&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What categories even exist in here&lt;/td&gt;
&lt;td&gt;Split, it is the only one that discovers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A chart of category share&lt;/td&gt;
&lt;td&gt;Split, and say in the caption that rows count once per category&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That last row matters and gets skipped. Once you split, a game tagged with three genres appears in three rows, so the genre counts add up to more than the number of games. Nothing is wrong, but a reader will assume the parts sum to the whole unless you tell them otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write it down as a limitation
&lt;/h2&gt;

&lt;p&gt;A multi value column is exactly the kind of thing that belongs in your write up rather than buried in a query. It changes what your numbers mean. Three lines cover it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What the column actually holds.&lt;/strong&gt; "The Genres field stores a comma separated list, so a game can carry several genres."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How you handled it.&lt;/strong&gt; "Genres were split to one row per game per genre before counting."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What that does to the totals.&lt;/strong&gt; "Games with several genres are counted once in each, so genre counts sum to more than the 125,855 games."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is a second thing worth flagging in this particular dataset, and it is the kind of detail that separates a real analysis from a tutorial. Those 33 values are not all genres. &lt;code&gt;Gore&lt;/code&gt;, &lt;code&gt;Violent&lt;/code&gt;, &lt;code&gt;Nudity&lt;/code&gt; and &lt;code&gt;Sexual Content&lt;/code&gt; are content warnings. &lt;code&gt;Accounting&lt;/code&gt;, &lt;code&gt;Photo Editing&lt;/code&gt; and &lt;code&gt;Video Production&lt;/code&gt; are software categories, not games at all. The field mixes several kinds of label into one column, so "top genre" is a misleading headline unless you decide which of the 33 count and say which you dropped. &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/documenting-data-limitations/" rel="noopener noreferrer"&gt;Documenting Data Limitations&lt;/a&gt; covers writing that section without undermining your own work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the data looks like this
&lt;/h2&gt;

&lt;p&gt;It helps to know this is a known shape with a known name, not a mess somebody made by accident. A database is described as being in &lt;em&gt;first normal form&lt;/em&gt; when every cell holds a single value. A column packing a list into one cell breaks that rule, and the textbook fix is a separate small table with one row per pairing:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;game_id&lt;/th&gt;
&lt;th&gt;genre&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;Action&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;Indie&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;RPG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;Casual&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;With that table, counting per genre is an ordinary &lt;code&gt;GROUP BY&lt;/code&gt; and none of this guide is needed. So why does the flat version keep turning up? Because analysts receive data rather than design it. Exports flatten. Spreadsheets get typed by hand. APIs return an array and whoever wrote the CSV joined it with commas. Survey tools write "select all that apply" answers into one field as a matter of course.&lt;/p&gt;

&lt;p&gt;You will meet this column shape in tags, skills, categories, region lists, product options, and multi select survey questions. Once you have recognised it once, you recognise it everywhere, and the first thing you will do on any new categorical column is look at twenty rows before you count them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More detail on this, and more like it.&lt;/strong&gt; Every how-to sits in one place on the &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/" rel="noopener noreferrer"&gt;guides index&lt;/a&gt;: SQL, Tableau, data migration, and the working habits around them.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on Analyst Prep Kit: &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/sql-multi-value-column/" rel="noopener noreferrer"&gt;One Cell, Many Values: When a Column Holds a List&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Visit the site for more beginner data analysis guides and free resources: &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/" rel="noopener noreferrer"&gt;the full guide archive&lt;/a&gt; covers SQL, Excel, Power BI, Tableau, Python and statistics, and the &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/" rel="noopener noreferrer"&gt;practice kits&lt;/a&gt; run in your browser with nothing to install.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If it was useful: &lt;a href="https://buymeacoffee.com/michaelnocito" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>LIKE in SQL, Explained for Beginners</title>
      <dc:creator>Michael Nocito</dc:creator>
      <pubDate>Mon, 07 Sep 2026 13:00:28 +0000</pubDate>
      <link>https://dev.to/michaelnocito/like-in-sql-explained-for-beginners-24gh</link>
      <guid>https://dev.to/michaelnocito/like-in-sql-explained-for-beginners-24gh</guid>
      <description>&lt;p&gt;By &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/about.html" rel="noopener noreferrer"&gt;Michael Nocito&lt;/a&gt;, data analyst · Updated August 19, 2026&lt;/p&gt;

&lt;p&gt;Most of the time you ask SQL for an exact value. &lt;code&gt;WHERE status = 'Active'&lt;/code&gt; is a closed question with a yes or no answer. But a lot of real columns do not hold one tidy value. They hold a sentence, a product name with a code stuck on the front, an email address, or a list of tags jammed into a single cell. For those you need a looser question, and &lt;code&gt;LIKE&lt;/code&gt; is how you ask it. It is a small piece of syntax with exactly two moving parts, and once those click you can search inside text instead of only matching it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The one-sentence version.&lt;/strong&gt; &lt;code&gt;=&lt;/code&gt; asks "is this value exactly this?" &lt;code&gt;LIKE&lt;/code&gt; asks "does this value match this shape?" The shape is written with two wildcards: &lt;code&gt;%&lt;/code&gt; stands for any run of characters including none, and &lt;code&gt;_&lt;/code&gt; stands for exactly one character.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why equals asks a stricter question than you want
&lt;/h2&gt;

&lt;p&gt;Take a &lt;code&gt;products&lt;/code&gt; table with a &lt;code&gt;name&lt;/code&gt; column:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Blue Cotton Shirt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Shirt Stay Clips&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Red Wool Sweater&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Shirt&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You want the shirts. The obvious thing fails:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT name
FROM products
WHERE name = 'Shirt';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That returns one row, id 4, because &lt;code&gt;=&lt;/code&gt; compares the whole value start to finish. &lt;code&gt;Blue Cotton Shirt&lt;/code&gt; is not the same string as &lt;code&gt;Shirt&lt;/code&gt;, so it is excluded, and correctly so. Equals is doing its job. It is just the wrong question.&lt;/p&gt;

&lt;p&gt;The question you actually have is "does the name contain the word Shirt anywhere in it?" That is a pattern, and patterns are what &lt;code&gt;LIKE&lt;/code&gt; is for:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT name
FROM products
WHERE name LIKE '%Shirt%';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Three rows come back: &lt;code&gt;Blue Cotton Shirt&lt;/code&gt;, &lt;code&gt;Shirt Stay Clips&lt;/code&gt;, and &lt;code&gt;Shirt&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two wildcards, % and _
&lt;/h2&gt;

&lt;p&gt;Everything &lt;code&gt;LIKE&lt;/code&gt; can do comes from two characters.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Wildcard&lt;/th&gt;
&lt;th&gt;Means&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Matches&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Any run of characters, including none at all&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'S%'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;S&lt;/code&gt;, &lt;code&gt;Sam&lt;/code&gt;, &lt;code&gt;Sweater&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Exactly one character, no more, no fewer&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'S_m'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Sam&lt;/code&gt;, &lt;code&gt;Sum&lt;/code&gt;, but not &lt;code&gt;Steam&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The "including none at all" part of &lt;code&gt;%&lt;/code&gt; is the bit people miss. &lt;code&gt;'S%'&lt;/code&gt; matches the single letter &lt;code&gt;S&lt;/code&gt;, because &lt;code&gt;%&lt;/code&gt; is allowed to stand for nothing. The underscore is not so forgiving. It demands a character be there.&lt;/p&gt;

&lt;p&gt;Say this one out loud before you read the answer: what would &lt;code&gt;'_at'&lt;/code&gt; match, and would &lt;code&gt;flat&lt;/code&gt; be in the list? Commit to an answer first.&lt;/p&gt;

&lt;p&gt;It matches three-character values ending in &lt;code&gt;at&lt;/code&gt;, so &lt;code&gt;cat&lt;/code&gt;, &lt;code&gt;hat&lt;/code&gt;, &lt;code&gt;bat&lt;/code&gt;. &lt;code&gt;flat&lt;/code&gt; is out, because &lt;code&gt;_&lt;/code&gt; covers one character and &lt;code&gt;flat&lt;/code&gt; has two before the &lt;code&gt;at&lt;/code&gt;. If you wanted both you would write &lt;code&gt;'%at'&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can mix them. &lt;code&gt;'A__-%'&lt;/code&gt; matches a value that starts with &lt;code&gt;A&lt;/code&gt;, has exactly two more characters, then a hyphen, then anything. That is a useful shape for product codes and account numbers, where the format is fixed but the content is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where you put the % is the whole question
&lt;/h2&gt;

&lt;p&gt;Same wildcard, four positions, four completely different questions. This is the table worth memorising:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;You write&lt;/th&gt;
&lt;th&gt;You are asking&lt;/th&gt;
&lt;th&gt;Matches "Blue Cotton Shirt"?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LIKE 'Shirt'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Is it exactly Shirt (same as &lt;code&gt;=&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LIKE 'Blue%'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Does it start with Blue&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LIKE '%Shirt'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Does it end with Shirt&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LIKE '%Cotton%'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Does it contain Cotton anywhere&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A &lt;code&gt;LIKE&lt;/code&gt; pattern with no wildcard in it is just a slower &lt;code&gt;=&lt;/code&gt;. If you write &lt;code&gt;LIKE 'Shirt'&lt;/code&gt; you have asked for an exact match and should use &lt;code&gt;=&lt;/code&gt; instead, which is clearer to whoever reads the query next.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read the pattern left to right, in words.&lt;/strong&gt; &lt;code&gt;'%Cotton%'&lt;/code&gt; reads as "anything, then Cotton, then anything." &lt;code&gt;'Blue%'&lt;/code&gt; reads as "Blue, then anything." If you can say the pattern as a sentence, you will not put the percent signs in the wrong place.&lt;/p&gt;

&lt;h2&gt;
  
  
  NOT LIKE, and finding what is missing
&lt;/h2&gt;

&lt;p&gt;Put &lt;code&gt;NOT&lt;/code&gt; in front and you get the opposite set:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT name
FROM products
WHERE name NOT LIKE '%Shirt%';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That returns the rows with no &lt;code&gt;Shirt&lt;/code&gt; in them. This is more useful than it first looks, because "which rows do &lt;em&gt;not&lt;/em&gt; fit the expected shape" is one of the most productive questions in data cleaning. Emails with no at sign, product codes missing their prefix, a text column where some rows hold a list and some do not. &lt;code&gt;NOT LIKE&lt;/code&gt; finds the odd ones out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One catch worth knowing now.&lt;/strong&gt; A row where the column is &lt;code&gt;NULL&lt;/code&gt; is returned by neither &lt;code&gt;LIKE&lt;/code&gt; nor &lt;code&gt;NOT LIKE&lt;/code&gt;. NULL means unknown, and SQL will not claim an unknown value matches or fails to match a pattern. If you want the blanks too, ask for them: &lt;code&gt;WHERE name NOT LIKE '%Shirt%' OR name IS NULL&lt;/code&gt;. The full story is in &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/sql-null/" rel="noopener noreferrer"&gt;NULL in SQL&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case sensitivity, which changes by database
&lt;/h2&gt;

&lt;p&gt;Whether &lt;code&gt;'%shirt%'&lt;/code&gt; finds &lt;code&gt;Blue Cotton Shirt&lt;/code&gt; depends on which database you are sitting in. This surprises people who move between two of them, so it is worth checking once rather than assuming.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Database&lt;/th&gt;
&lt;th&gt;Is LIKE case sensitive?&lt;/th&gt;
&lt;th&gt;How to force insensitive&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SQLite&lt;/td&gt;
&lt;td&gt;No for plain A-Z letters, yes for accented and non-English ones&lt;/td&gt;
&lt;td&gt;Already insensitive for A-Z&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;ILIKE&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MySQL&lt;/td&gt;
&lt;td&gt;Depends on the column collation, usually no&lt;/td&gt;
&lt;td&gt;Usually already insensitive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQL Server&lt;/td&gt;
&lt;td&gt;Depends on the collation, usually no&lt;/td&gt;
&lt;td&gt;Usually already insensitive&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The portable move, if you want the same answer everywhere, is to flatten both sides yourself:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT name
FROM products
WHERE LOWER(name) LIKE '%shirt%';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That works in every database listed above, at the cost of a little speed. See the last section for why.&lt;/p&gt;

&lt;h2&gt;
  
  
  The over-matching trap
&lt;/h2&gt;

&lt;p&gt;Predict this one before reading on. You search a genre column with &lt;code&gt;LIKE '%Video%'&lt;/code&gt;. What comes back?&lt;/p&gt;

&lt;p&gt;In a real Steam games dataset, that pattern matches both &lt;code&gt;Video Production&lt;/code&gt; and &lt;code&gt;360 Video&lt;/code&gt;, because both contain those five letters. That may be exactly what you wanted, or it may quietly double your result set.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;%&lt;/code&gt; does not know about word boundaries. It matches letters, not meaning. Some classics:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;You meant&lt;/th&gt;
&lt;th&gt;You also get&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;'%art%'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;art&lt;/td&gt;
&lt;td&gt;cart, start, Bart, particle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;'%man%'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;man&lt;/td&gt;
&lt;td&gt;manage, human, Germany&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;'%IT%'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the IT department&lt;/td&gt;
&lt;td&gt;credit, exit, monitor (where LIKE is case insensitive)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The fix is to give the pattern more to hold on to. If the values are separated by a known character, include it: &lt;code&gt;'%, Sales%'&lt;/code&gt; is far more precise than &lt;code&gt;'%Sales%'&lt;/code&gt;. The next section shows the reliable version of that trick.&lt;/p&gt;

&lt;h2&gt;
  
  
  Searching a column that holds a list
&lt;/h2&gt;

&lt;p&gt;This is where analysts reach for &lt;code&gt;LIKE&lt;/code&gt; most often. Some columns pack several values into one cell, separated by commas:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;th&gt;genres&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Game A&lt;/td&gt;
&lt;td&gt;Action,Indie,RPG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Game B&lt;/td&gt;
&lt;td&gt;Casual,Indie&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Game C&lt;/td&gt;
&lt;td&gt;RPG&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;=&lt;/code&gt; is useless here, because almost no cell holds only &lt;code&gt;RPG&lt;/code&gt;. &lt;code&gt;LIKE&lt;/code&gt; handles it without any splitting:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT name
FROM games
WHERE genres LIKE '%RPG%';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That finds Game A and Game C, whether the tag sits first, last, or buried in the middle.&lt;/p&gt;

&lt;p&gt;The precise version guards against the over-matching trap. Wrap the value in the separator on both sides, then search for the separated tag:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT name
FROM games
WHERE ',' || genres || ',' LIKE '%,RPG,%';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;||&lt;/code&gt; glues text together, so &lt;code&gt;Action,Indie,RPG&lt;/code&gt; temporarily becomes &lt;code&gt;,Action,Indie,RPG,&lt;/code&gt;. Now every tag has a comma on both sides, including the first and last, and the pattern &lt;code&gt;'%,RPG,%'&lt;/code&gt; can only match a whole tag. A genre called &lt;code&gt;RPGMaker&lt;/code&gt; would no longer be a false hit. Use &lt;code&gt;CONCAT(',', genres, ',')&lt;/code&gt; instead of &lt;code&gt;||&lt;/code&gt; in MySQL and SQL Server.&lt;/p&gt;

&lt;p&gt;That column shape causes more trouble than just searching, including a distinct count that comes back wrong and never says so. &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/sql-multi-value-column/" rel="noopener noreferrer"&gt;One Cell, Many Values&lt;/a&gt; covers the whole problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Matching a literal % or _
&lt;/h2&gt;

&lt;p&gt;Every so often the character you are hunting for is a wildcard. Searching a notes column for an actual percent sign with &lt;code&gt;LIKE '%%%'&lt;/code&gt; matches every row, which is not useful. Declare an escape character and mark the literal one:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT note
FROM feedback
WHERE note LIKE '%!%%' ESCAPE '!';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Read that pattern as: anything, then a real percent sign (the one after the &lt;code&gt;!&lt;/code&gt;), then anything. The &lt;code&gt;ESCAPE '!'&lt;/code&gt; at the end is what tells SQL that &lt;code&gt;!&lt;/code&gt; is the marker. You can pick any character for the job as long as it does not appear in your data. The same applies to a literal underscore, which matters more than you would think, because underscores are everywhere in column names and product codes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why LIKE '%text%' gets slow
&lt;/h2&gt;

&lt;p&gt;An index on a text column works like the index at the back of a book: entries sorted alphabetically, so the database can jump straight to the right place. That works when you know how the value starts.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Can an index help?&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;'Blue%'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Everything starting with Blue is filed together&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;'%Shirt'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;The start is unknown, so there is nowhere to jump to&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;'%Cotton%'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Same problem, every row has to be read and checked&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On a few thousand rows this is invisible. On tens of millions it is the difference between an instant answer and a coffee break. It is not a reason to avoid &lt;code&gt;'%text%'&lt;/code&gt;, it is a reason to know why the query got slow when the table grew. If you need fast searching inside text at scale, that is a full text search index, a different tool. &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/sql-indexing-for-analysts/" rel="noopener noreferrer"&gt;Indexing for Analysts&lt;/a&gt; covers what an index can and cannot do.&lt;/p&gt;

&lt;p&gt;The same logic explains the cost of &lt;code&gt;LOWER(name) LIKE ...&lt;/code&gt; from earlier. Wrapping the column in a function means the index on that column no longer applies, because the index stores the original values, not the lowercased ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  A LIKE cheat sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;You want…&lt;/th&gt;
&lt;th&gt;Write&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Contains a word&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LIKE '%word%'&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Starts with&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LIKE 'word%'&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ends with&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LIKE '%word'&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exactly this&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;= 'word'&lt;/code&gt;, not LIKE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Does not contain&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NOT LIKE '%word%'&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exactly one unknown character&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LIKE 'A_C'&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A fixed format, like three letters then a dash&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LIKE '___-%'&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Case insensitive everywhere&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LOWER(col) LIKE '%word%'&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Case insensitive in PostgreSQL&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ILIKE '%word%'&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One whole tag from a comma separated list&lt;/td&gt;
&lt;td&gt;`','&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A literal percent sign&lt;/td&gt;
&lt;td&gt;{% raw %}&lt;code&gt;LIKE '%!%%' ESCAPE '!'&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Say it out loud before you run it.&lt;/strong&gt; Read your pattern left to right as a sentence, swapping "anything" in for every &lt;code&gt;%&lt;/code&gt;. If the sentence you say is not the question you meant to ask, the percent signs are in the wrong place. That one habit prevents most &lt;code&gt;LIKE&lt;/code&gt; mistakes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More detail on this, and more like it.&lt;/strong&gt; Every how-to sits in one place on the &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/" rel="noopener noreferrer"&gt;guides index&lt;/a&gt;: SQL, Tableau, data migration, and the working habits around them.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on Analyst Prep Kit: &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/sql-like-pattern-matching/" rel="noopener noreferrer"&gt;LIKE in SQL, Explained for Beginners&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Visit the site for more beginner data analysis guides and free resources: &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/" rel="noopener noreferrer"&gt;the full guide archive&lt;/a&gt; covers SQL, Excel, Power BI, Tableau, Python and statistics, and the &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/" rel="noopener noreferrer"&gt;practice kits&lt;/a&gt; run in your browser with nothing to install.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If it was useful: &lt;a href="https://buymeacoffee.com/michaelnocito" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Net Present Value (NPV): How to Discount Cash Flows and Read the Answer</title>
      <dc:creator>Michael Nocito</dc:creator>
      <pubDate>Mon, 07 Sep 2026 13:00:20 +0000</pubDate>
      <link>https://dev.to/michaelnocito/net-present-value-npv-how-to-discount-cash-flows-and-read-the-answer-2p60</link>
      <guid>https://dev.to/michaelnocito/net-present-value-npv-how-to-discount-cash-flows-and-read-the-answer-2p60</guid>
      <description>&lt;p&gt;By &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/about.html" rel="noopener noreferrer"&gt;Michael Nocito&lt;/a&gt;, data analyst · Published August 11, 2026&lt;/p&gt;

&lt;p&gt;By the end of this page you can take a project that costs money now and pays money later, and produce one number that says whether it is worth doing. You will know the discount factor and where it comes from, the SQL that computes NPV in one line, the three ways that line silently returns the wrong answer, and how to present a result whose whole meaning depends on an assumption you chose.&lt;/p&gt;

&lt;p&gt;The fastest way in is to do it once on something real. Take any project with an upfront cost and a few years of expected returns. Divide each future year's cash by 1.10 raised to that year's number, add them all up, then subtract the upfront cost. If what is left is positive, the project beats a 10% required return.&lt;/p&gt;

&lt;p&gt;The whole idea in one line: a dollar arriving in five years is not a dollar. NPV shrinks every future amount down to what it is worth today, then adds the whole stream up, including the negative one at the start.&lt;/p&gt;

&lt;p&gt;The shrinking is the idea the rest of the page rests on, so it gets the picture.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The original carries a diagram here. In words: Five vertical bars rising from a common baseline, one for each of years one to five. The full height of each bar is the cash the project expects to collect in that year, and the bars grow steadily taller from left to right, from one hundred and fifty thousand in year one to one hundred and ninety thousand in year five. Each bar is split into two parts. The solid accent-colored part at the bottom is what that cash is worth today after discounting at ten percent, and the pale hatched cap sitting on top of it is the part the waiting takes away. The solid parts shrink steadily from left to right, from about one hundred and thirty six thousand down to about one hundred and eighteen thousand, while the pale caps grow from a thin sliver in year one to a thick block in year five. A dashed line joins the tops of the five solid parts and slopes downward across the chart, against bars that are getting taller. That opposition is the whole point: a payment can be bigger and still be worth less, because each extra year of waiting divides it by another factor of one point one. A small legend at the top right pairs a pale swatch with the word waiting and a solid swatch with the word today.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Every number on this page is verified, and you can check them.&lt;/strong&gt; The worked project is six rows, shown in full below. Every discount factor, present value, NPV, rate table and error figure was computed in SQLite and cross-checked in pandas before it went on the page, so you can check any row on a calculator and it will agree.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Why a future dollar is worth less, in arithmetic rather than words
&lt;/h2&gt;

&lt;p&gt;You are offered $100 today or $100 in a year. Beyond simple impatience, name one concrete reason today is worth more.&lt;/p&gt;

&lt;p&gt;Because $100 today can be doing something for the next year. Put it somewhere that returns 10% and by this time next year it is $110. So $100 today and $100 next year are not the same offer, and the size of the difference is whatever return you could have got.&lt;/p&gt;

&lt;p&gt;Run that backwards and you have the whole method. If $100 today grows to $110 in a year, then $110 arriving in a year is worth exactly $100 today. Growing forwards multiplies by 1.10. Coming backwards divides by 1.10. The dividing is called discounting, and the 10% is called the discount rate.&lt;/p&gt;

&lt;p&gt;Do it twice for two years. $100 today grows to $110, then to $121. So $121 in two years is worth $100 today, and the division is by 1.10 twice, which is 1.21. Three years is 1.331. The number you divide by grows fast, which is why money far in the future gets cut down hard.&lt;/p&gt;

&lt;p&gt;Two numbers to hold on to, both from this page's project. At a 10% discount rate, a dollar arriving in five years is worth 62 cents today. Raise the rate to 15% and the same dollar is worth 50 cents. Nothing about the dollar changed. The rate did.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The discount factor, and the year-0 rule
&lt;/h2&gt;

&lt;p&gt;What should the discount factor be for money you spend today?&lt;/p&gt;

&lt;p&gt;One. Money spent today is already in today's dollars, so it does not get discounted at all, and dividing by 1 leaves it alone. That is not a special case bolted onto the formula. It falls out of it, because any number raised to the power of 0 is 1.&lt;/p&gt;

&lt;p&gt;The discount factor for year &lt;em&gt;t&lt;/em&gt; at rate &lt;em&gt;r&lt;/em&gt; is &lt;code&gt;(1 + r)&lt;/code&gt; raised to the power &lt;em&gt;t&lt;/em&gt;. Present value is the cash flow divided by that factor.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Factor at 10%&lt;/th&gt;
&lt;th&gt;How it is built&lt;/th&gt;
&lt;th&gt;A dollar is worth&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1.0000&lt;/td&gt;
&lt;td&gt;1.10 to the power 0&lt;/td&gt;
&lt;td&gt;100 cents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1.1000&lt;/td&gt;
&lt;td&gt;1.10&lt;/td&gt;
&lt;td&gt;91 cents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1.2100&lt;/td&gt;
&lt;td&gt;1.10 × 1.10&lt;/td&gt;
&lt;td&gt;83 cents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1.3310&lt;/td&gt;
&lt;td&gt;1.10 × 1.10 × 1.10&lt;/td&gt;
&lt;td&gt;75 cents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1.4641&lt;/td&gt;
&lt;td&gt;and again&lt;/td&gt;
&lt;td&gt;68 cents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;1.61051&lt;/td&gt;
&lt;td&gt;and again&lt;/td&gt;
&lt;td&gt;62 cents&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Net present value is the sum of every year's present value, with the upfront cost included as a negative number at year 0. The word "net" is that subtraction. A present value that ignores the cost is not an NPV, and section five shows what that mistake looks like when it reaches a slide.&lt;/p&gt;

&lt;p&gt;The decision rule is one line. NPV above zero means the project earns more than the rate you required of it, so it adds value. NPV below zero means it does not. NPV of exactly zero means it earns precisely the required return, no more.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The worked project, row by row
&lt;/h2&gt;

&lt;p&gt;A project costs $500,000 today and pays back $850,000 over five years. Guess whether it clears a 10% required return, and by roughly how much.&lt;/p&gt;

&lt;p&gt;Here is the project. A production line expansion: $500,000 to build, then five years of extra cash from the capacity it adds. The required return is 10%.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Cash flow&lt;/th&gt;
&lt;th&gt;Factor at 10%&lt;/th&gt;
&lt;th&gt;Present value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;-500,000&lt;/td&gt;
&lt;td&gt;1.0000&lt;/td&gt;
&lt;td&gt;-500,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;150,000&lt;/td&gt;
&lt;td&gt;1.1000&lt;/td&gt;
&lt;td&gt;136,364&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;160,000&lt;/td&gt;
&lt;td&gt;1.2100&lt;/td&gt;
&lt;td&gt;132,231&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;170,000&lt;/td&gt;
&lt;td&gt;1.3310&lt;/td&gt;
&lt;td&gt;127,724&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;180,000&lt;/td&gt;
&lt;td&gt;1.4641&lt;/td&gt;
&lt;td&gt;122,942&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;190,000&lt;/td&gt;
&lt;td&gt;1.61051&lt;/td&gt;
&lt;td&gt;117,975&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NPV&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;350,000&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;137,236&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Check one row by hand to trust the rest. Year 3: 170,000 divided by 1.331 is 127,723.5, which rounds to 127,724. Add the present-value column and you get 137,236.&lt;/p&gt;

&lt;p&gt;Look at the two totals on the bottom row, because they are the whole lesson. Undiscounted, the project nets $350,000. Discounted at 10%, it nets $137,236. The $212,764 difference is not a cost anyone pays and not a number in any ledger. It is the return you would have earned on that money elsewhere over five years, and NPV charges the project for it.&lt;/p&gt;

&lt;p&gt;The verdict, in a sentence someone can act on: &lt;strong&gt;the line expansion is worth $137,236 more than the 10% return we could get elsewhere on the same $500,000.&lt;/strong&gt; Notice how much work "at 10%" is doing in that sentence. Section six is about how much.&lt;/p&gt;

&lt;p&gt;Say why the discounted total is smaller than the undiscounted one, in your own words, before reading on. If you can say it, the rest of this page is careful arithmetic.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The query, and the rate written as 10/100
&lt;/h2&gt;

&lt;p&gt;In SQL, what does &lt;code&gt;10/100&lt;/code&gt; return when both are whole numbers?&lt;/p&gt;

&lt;p&gt;Zero. And that turns the whole NPV calculation into something else without changing a single row of data. Follow it through: &lt;code&gt;10/100&lt;/code&gt; is 0, so &lt;code&gt;1 + 10/100&lt;/code&gt; is 1, so &lt;code&gt;POWER(1, year)&lt;/code&gt; is 1 for every year, so every cash flow is divided by 1 and no discounting happens at all. The query runs, returns $350,000, and $350,000 is just the raw sum of the cash-flow column.&lt;/p&gt;

&lt;p&gt;That is the worst kind of wrong. It is not an error, it is not a null, and $350,000 is a believable answer for this project. The only way to catch it is to know it can happen.&lt;/p&gt;

&lt;p&gt;The working query, with the rate written as a decimal.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT ROUND(SUM(cash_flow / POWER(1 + 0.10, year)), 0) AS npv
FROM project_cash_flows
WHERE project = 'Line Expansion';        -- 137236
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And the same idea one row at a time, which is what you actually want on screen, because a single NPV number is impossible to check.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT year,
       cash_flow,
       ROUND(POWER(1 + 0.10, year), 4)             AS discount_factor,
       ROUND(cash_flow / POWER(1 + 0.10, year), 0) AS present_value
FROM project_cash_flows
WHERE project = 'Line Expansion'
ORDER BY year;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Three notes on the arithmetic, all worth checking once on your own database.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you write&lt;/th&gt;
&lt;th&gt;Returns&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;10/100&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Whole divided by whole. The rate vanishes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;10/100.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.1&lt;/td&gt;
&lt;td&gt;One decimal makes the whole expression decimal.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POWER(1 + 10/100, 5)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;Every factor is 1. No discounting happened.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POWER(1 + 0.10, 5)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1.61051&lt;/td&gt;
&lt;td&gt;Correct.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;POWER&lt;/code&gt; itself is safe, because it returns a decimal even when handed whole numbers, so &lt;code&gt;cash_flow / POWER(...)&lt;/code&gt; stays decimal. The danger is entirely in how the rate reaches it. Write rates as &lt;code&gt;0.10&lt;/code&gt;. If the rate has to come from a column, make that column a decimal type when the table is created, and check one value before you trust the output. The same trap in its percentage form is worked in &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/gross-vs-operating-vs-net-margin/" rel="noopener noreferrer"&gt;gross vs operating vs net margin&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One portability note. &lt;code&gt;POWER&lt;/code&gt; exists in SQLite, PostgreSQL, SQL Server, MySQL, Oracle, Snowflake and BigQuery, though older SQLite builds may not have it compiled in. Where it is missing, &lt;code&gt;EXP(year * LN(1 + 0.10))&lt;/code&gt; is the same calculation and works anywhere that has logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Two off-by-one errors, and which one flips the decision
&lt;/h2&gt;

&lt;p&gt;Your cash-flow table was built with a row number instead of a year number, so the first inflow is labelled 0 instead of 1. Does NPV come out too high or too low?&lt;/p&gt;

&lt;p&gt;Too high, and by a lot. Every inflow is being discounted one year less than it should be, so each one is worth more today than it really is, while the cost at year 0 is untouched. On this project the correct answer is $137,236 and the mislabelled one is $200,960, an overstatement of $63,724, which is 46%.&lt;/p&gt;

&lt;p&gt;It matters because it can change the answer. Take the same five inflows against a $660,000 upfront cost instead of $500,000.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;NPV at 10%&lt;/th&gt;
&lt;th&gt;Decision&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Inflows correctly at years 1 to 5&lt;/td&gt;
&lt;td&gt;-22,764&lt;/td&gt;
&lt;td&gt;Reject&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inflows mislabelled 0 to 4&lt;/td&gt;
&lt;td&gt;+40,960&lt;/td&gt;
&lt;td&gt;Accept&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A row number and a year number look identical in a table. Nothing in the query can tell them apart. The check that catches it takes five seconds: &lt;strong&gt;the year-0 row is the money you spend today, and it should be the only negative one and the only one whose present value equals its cash flow.&lt;/strong&gt; If your year-0 row is an inflow, the labels are shifted.&lt;/p&gt;

&lt;p&gt;The second off-by-one is worth knowing because it behaves completely differently. If the &lt;em&gt;whole&lt;/em&gt; stream shifts, cost and all, so the cost sits at year 1 and the inflows at 2 to 6, then every present value gets divided by one extra 1.10 and NPV goes from $137,236 to $124,760. That is exactly $137,236 divided by 1.10. Because everything moved together, the sign cannot change, so the accept-or-reject decision is always safe even though the value is 9.1% off. Only the partial shift is dangerous, and the partial shift is the one that happens.&lt;/p&gt;

&lt;p&gt;The third mistake in this family has nothing to do with years. The upfront cost simply is not in the table, because it lives in a different system, or because someone filtered on &lt;code&gt;year &amp;gt; 0&lt;/code&gt; to drop what looked like an empty row.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT ROUND(SUM(cash_flow / POWER(1 + 0.10, year)), 0)
FROM project_cash_flows
WHERE project = 'Line Expansion' AND year &amp;gt; 0;      -- 637236
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;$637,236, against a real NPV of $137,236. Overstated by exactly the $500,000 that was left out, and there is nothing in the number itself to say so. That is a present value, not a net present value, and the missing word is the entire cost of the project.&lt;/p&gt;

&lt;p&gt;Picture your own company's project table. Where does the upfront cost live, and is it a row in the same table as the returns? If it lives in a capital budget somewhere else, every NPV anyone runs on that table is a gross present value wearing the wrong name.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. The rate is the assumption, and IRR is the other way round
&lt;/h2&gt;

&lt;p&gt;Same five cash flows, same $500,000 cost. Guess what rate would make this project not worth doing.&lt;/p&gt;

&lt;p&gt;Just under 20%. Here is the same project at seven different required returns, with nothing else changed.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Discount rate&lt;/th&gt;
&lt;th&gt;NPV&lt;/th&gt;
&lt;th&gt;Decision&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;350,000&lt;/td&gt;
&lt;td&gt;Accept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5%&lt;/td&gt;
&lt;td&gt;231,791&lt;/td&gt;
&lt;td&gt;Accept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;137,236&lt;/td&gt;
&lt;td&gt;Accept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15%&lt;/td&gt;
&lt;td&gt;60,575&lt;/td&gt;
&lt;td&gt;Accept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;td&gt;-2,347&lt;/td&gt;
&lt;td&gt;Reject&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25%&lt;/td&gt;
&lt;td&gt;-54,573&lt;/td&gt;
&lt;td&gt;Reject&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30%&lt;/td&gt;
&lt;td&gt;-98,367&lt;/td&gt;
&lt;td&gt;Reject&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One project, one set of cash flows, and the verdict flips between 15% and 20%. Every row is arithmetically perfect. The rate is not something the data tells you; it is something you choose and then have to defend. An NPV presented without the rate beside it is not a result, it is half of one.&lt;/p&gt;

&lt;p&gt;Where the rate comes from, in practice. It is the return the company requires on money it puts at risk, usually its weighted average cost of capital, sometimes a hurdle rate set by the finance team, sometimes the return available on a comparable investment. Riskier projects get a higher rate, which is how risk enters the arithmetic: a higher rate punishes distant cash flows hardest, so speculative projects with far-off payoffs have to be much bigger to clear.&lt;/p&gt;

&lt;p&gt;The rate that makes NPV exactly zero has a name: the internal rate of return, or IRR. On this project it is 19.80%. Read it as the project's own return, the break-even required rate. Above it the project is worth doing; below it, not.&lt;/p&gt;

&lt;p&gt;IRR is not a SQL calculation. There is no closed-form solution, so it is found by trying rates until NPV lands on zero, which needs iteration that plain SQL does not do. That is why the practical division of labour is: compute NPV in SQL at the rate your company uses, and hand IRR to a spreadsheet or a Python script. If you want an approximate IRR without leaving SQL, run the NPV query at a handful of rates as above and read off where the sign changes. Between 15% and 20% here, and 19.80% when solved properly.&lt;/p&gt;

&lt;p&gt;Two more numbers worth having, both from the same project. Payback period, the point where the undiscounted cash adds back up to the cost, is 3.11 years. Discounted payback, the same question in today's dollars, is 3.84 years. Payback is easy to explain and it ignores everything that happens after it lands, which is why it is a useful second number and a poor first one.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. The full before and after
&lt;/h2&gt;

&lt;p&gt;Same project, two ways of putting it in front of a decision maker.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before
&lt;/h3&gt;

&lt;p&gt;"The line expansion costs $500,000 and returns $850,000 over five years, a 70% return. Recommend approval." Nothing there is false. The reader has no way to know that the $850,000 arrives slowly, that the return is 70% spread over five years rather than 70% a year, or that the same project loses money if the company's required return is 20%. And the single number carries no rate, so nobody in the room can disagree with it usefully.&lt;/p&gt;

&lt;h3&gt;
  
  
  After
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measure&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Read&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Upfront cost&lt;/td&gt;
&lt;td&gt;-500,000&lt;/td&gt;
&lt;td&gt;Year 0, spent today&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total inflows&lt;/td&gt;
&lt;td&gt;850,000&lt;/td&gt;
&lt;td&gt;Undiscounted, years 1 to 5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Discount rate used&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;The company hurdle rate, stated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NPV at 10%&lt;/td&gt;
&lt;td&gt;+137,236&lt;/td&gt;
&lt;td&gt;Clears the hurdle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NPV at 15%&lt;/td&gt;
&lt;td&gt;+60,575&lt;/td&gt;
&lt;td&gt;Still clears it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NPV at 20%&lt;/td&gt;
&lt;td&gt;-2,347&lt;/td&gt;
&lt;td&gt;Does not&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IRR&lt;/td&gt;
&lt;td&gt;19.80%&lt;/td&gt;
&lt;td&gt;The rate where it breaks even&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Discounted payback&lt;/td&gt;
&lt;td&gt;3.84 years&lt;/td&gt;
&lt;td&gt;When today's dollars come back&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same project, four extra rows, and the whole conversation changes. The rate is on the page, so it can be argued with. Two other rates are on the page, so the reader can see how much room there is before the answer changes. And the recommendation now has a shape: &lt;strong&gt;fund it if our required return is below about 19.8%, and the margin gets thin above 15%.&lt;/strong&gt; That is a sentence a finance director can either agree with or correct, which is the most useful thing an analysis can be.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Edge cases NPV cannot see
&lt;/h2&gt;

&lt;p&gt;NPV comes out at exactly zero. Accept or reject?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero means it earns precisely the required return.&lt;/strong&gt; It is not a failure and it is not a win. The project would return exactly what the money would have earned elsewhere, so on the numbers alone it is a coin toss, and the decision moves to everything the numbers left out: strategy, risk, what else the money could do, what the team learns by doing it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cash flow is not profit.&lt;/strong&gt; NPV wants cash moving in and out, not accounting profit. Depreciation is a real expense on the income statement and no cash leaves the building, so it does not belong in a cash-flow row. If someone hands you a project's projected profit and asks for an NPV, the first job is converting it back to cash.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The end of the model is not the end of the project.&lt;/strong&gt; A production line that still works in year 6 has value that a five-year model gives away for nothing. Either extend the model or add a terminal value in the final year. Whichever you do, say so, because a five-year NPV on a fifteen-year asset understates it badly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The cash flows are forecasts, and NPV reports to the nearest dollar.&lt;/strong&gt; $137,236 has six digits of apparent precision built on estimates that could be 20% out either way. Presenting NPV to the dollar invites a confidence nobody has earned. Round to the nearest thousand in the summary, keep the full number in the working, and show at least one alternative rate so the range is visible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Years are not always the right period.&lt;/strong&gt; If cash arrives monthly, discount monthly, and the rate has to be converted first. A 10% annual rate is not 10 divided by 12 per month; it is 1.10 raised to the power of one twelfth, minus 1, which is about 0.797% a month. Mixing an annual rate with monthly periods is a large error dressed as a small one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sign conventions have to be consistent.&lt;/strong&gt; Every model needs one rule: costs negative, inflows positive. A cost stored as a positive number in a column the query then adds up will not fail, it will just tell you the project is wonderful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this works
&lt;/h2&gt;

&lt;p&gt;Discounting is not a modern convenience. Fisher set out the theory that the value of an asset is its future income discounted to the present, and that the rate connecting them is the market rate of interest (Fisher, 1930, &lt;em&gt;The Theory of Interest&lt;/em&gt; , Macmillan). Hirshleifer's later paper is the cleaner statement of why NPV rather than IRR should decide, showing that the present-value rule follows directly from optimal investment choice while rate-of-return rules can rank projects wrongly (Hirshleifer, 1958, &lt;em&gt;Journal of Political Economy&lt;/em&gt; , 66(4), 329–352). That is the formal version of section six's point: IRR is a useful second number and a poor rule.&lt;/p&gt;

&lt;p&gt;The stronger practical reason to keep the year-by-year table on screen, rather than a single NPV, is that people are bad at future money in a specific and measurable way. Thaler found the implied discount rate people apply falls sharply as the delay lengthens, so a single constant rate does not describe how anyone actually feels about it (Thaler, 1981, &lt;em&gt;Economics Letters&lt;/em&gt; , 8(3), 201–207). Frederick, Loewenstein and O'Donoghue's review of the field found implied annual rates in the published literature ranging from below zero to many thousand percent, depending only on how the question was asked (Frederick, Loewenstein, &amp;amp; O'Donoghue, 2002, &lt;em&gt;Journal of Economic Literature&lt;/em&gt; , 40(2), 351–401). A reader shown one NPV has to trust your rate. A reader shown the schedule and three rates can find their own.&lt;/p&gt;

&lt;p&gt;The question at the top of each section is deliberate. Attempting an answer before receiving one improves learning of that specific material across sixty-four studies (Bisra, Liu, Nesbit, Salimi, &amp;amp; Winne, 2018, &lt;em&gt;Educational Psychology Review&lt;/em&gt; , 30(3), 703–725). Guessing what &lt;code&gt;10/100&lt;/code&gt; returns, before section four told you, is why that one will still be with you the next time you write a rate into a query.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using this on your own numbers
&lt;/h2&gt;

&lt;p&gt;Rebuilding every capital model in the company is not a job anyone will thank you for. Do this instead, in order.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Show the schedule, not just the NPV.&lt;/strong&gt; Year, cash flow, factor, present value. Four columns, and it makes every other check on this list possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify the year-0 row.&lt;/strong&gt; It should be negative, and its present value should equal its cash flow exactly. If it does not, your factors or your labels are wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the rate reached the query as a decimal.&lt;/strong&gt; Run &lt;code&gt;SELECT POWER(1 + your_rate, 5)&lt;/code&gt; on its own. If it returns 1, the rate vanished, and section four has the fix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put the rate in the output.&lt;/strong&gt; A column or a header, not a comment in the code. An NPV without its rate cannot be checked by the person reading it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run it at two more rates, one above and one below.&lt;/strong&gt; This is one more query and it converts a number into a range, which is what the estimate always was.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Find where the sign changes and report that as the approximate IRR.&lt;/strong&gt; If the decision is close, take the exact figure from a spreadsheet, and say which method you used.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Optional, and only if there is paper to hand. One drawing fixes this. Draw the figure from the top of this page from memory: five outlined bars getting taller left to right, five solid bars in front of them getting shorter, and the point where the two trends cross. Label one axis with the years. If your solid bars grow along with the outlines, the discounting has not landed yet, and the page is still open.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This page is one of five.&lt;/strong&gt; The Financial analysis set on the &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/" rel="noopener noreferrer"&gt;guides index&lt;/a&gt; is this page plus &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/gross-vs-operating-vs-net-margin/" rel="noopener noreferrer"&gt;Gross vs Operating vs Net Margin&lt;/a&gt;, &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/liquidity-and-leverage-ratios/" rel="noopener noreferrer"&gt;Current Ratio vs Quick Ratio&lt;/a&gt;, &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/contribution-margin-break-even/" rel="noopener noreferrer"&gt;Contribution Margin and Break-Even&lt;/a&gt; and &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/budget-vs-actual-variance/" rel="noopener noreferrer"&gt;Budget vs Actual Variance&lt;/a&gt;. They share a worked company, so the numbers carry across. The index also holds every other how-to: SQL, Excel, Tableau, and the working habits around them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The whole thing on one screen
&lt;/h2&gt;

&lt;p&gt;Cover the right column first. This one works as a test and does very little as a reread.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Time value of money&lt;/td&gt;
&lt;td&gt;Today's dollar can be invested, so it is worth more than tomorrow's.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Discounting&lt;/td&gt;
&lt;td&gt;Dividing a future amount back to what it is worth today.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Discount factor&lt;/td&gt;
&lt;td&gt;(1 + rate) raised to the power of the year. At 10%, year 5 is 1.6105.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Present value&lt;/td&gt;
&lt;td&gt;Cash flow ÷ discount factor. 190,000 in year 5 is 117,975 today.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The year-0 rule&lt;/td&gt;
&lt;td&gt;Factor is 1, so today's money is undiscounted. Anything to the power 0 is 1.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Net present value&lt;/td&gt;
&lt;td&gt;Every year's present value added up, including the negative cost.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The word "net"&lt;/td&gt;
&lt;td&gt;The upfront cost is in there. Without it you have a present value, not an NPV.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The decision rule&lt;/td&gt;
&lt;td&gt;Above zero, it beats the required return. Below zero, it does not.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The worked answer&lt;/td&gt;
&lt;td&gt;-500,000 then 150k to 190k over five years is +137,236 at 10%.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The rate trap&lt;/td&gt;
&lt;td&gt;Writing 10/100 gives 0, every factor becomes 1, and NPV returns the raw sum.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The partial shift&lt;/td&gt;
&lt;td&gt;Inflows labelled 0 to 4 gave 200,960 instead of 137,236, and flipped a reject.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The whole shift&lt;/td&gt;
&lt;td&gt;Everything one year late divides NPV by 1.10. Wrong value, safe decision.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The missing cost&lt;/td&gt;
&lt;td&gt;Filtering out year 0 gave 637,236. Overstated by exactly the cost.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rate sensitivity&lt;/td&gt;
&lt;td&gt;Same project: +137,236 at 10%, +60,575 at 15%, -2,347 at 20%.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IRR&lt;/td&gt;
&lt;td&gt;The rate where NPV is zero. 19.80% here. Needs iteration, so not SQL.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payback&lt;/td&gt;
&lt;td&gt;3.11 years undiscounted, 3.84 discounted. Ignores everything after.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cash, not profit&lt;/td&gt;
&lt;td&gt;Depreciation is an expense and not a cash flow. Convert before discounting.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The deliverable&lt;/td&gt;
&lt;td&gt;The schedule, the rate, and NPV at two other rates beside it.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The one habit to keep.&lt;/strong&gt; If only one thing survives the week, make it this. Never present an NPV without the discount rate next to it, in the output rather than in the code. The rate is the only part of the calculation that was a judgement, it is the part most likely to be wrong, and it is the part the person reading is best placed to correct. If a model fights back in a way this page does not cover, there is a general &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/technical-tenacity/" rel="noopener noreferrer"&gt;diagnosis loop for being stuck&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One thing I have never settled, and I would like to know how other people handle it. The first discounted model I built used a rate somebody had typed into a spreadsheet four years earlier, and nobody in the company could say where it came from. What is the oldest unexplained assumption still running in a model you rely on?&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Fisher, I. (1930). &lt;em&gt;The Theory of Interest: As Determined by Impatience to Spend Income and Opportunity to Invest It&lt;/em&gt;. Macmillan.&lt;/li&gt;
&lt;li&gt;Hirshleifer, J. (1958). On the theory of optimal investment decision. &lt;em&gt;Journal of Political Economy&lt;/em&gt; , 66(4), 329–352.&lt;/li&gt;
&lt;li&gt;Thaler, R. (1981). Some empirical evidence on dynamic inconsistency. &lt;em&gt;Economics Letters&lt;/em&gt; , 8(3), 201–207.&lt;/li&gt;
&lt;li&gt;Frederick, S., Loewenstein, G., &amp;amp; O'Donoghue, T. (2002). Time discounting and time preference: A critical review. &lt;em&gt;Journal of Economic Literature&lt;/em&gt; , 40(2), 351–401.&lt;/li&gt;
&lt;li&gt;Bisra, K., Liu, Q., Nesbit, J. C., Salimi, F., &amp;amp; Winne, P. H. (2018). Inducing self-explanation: A meta-analysis. &lt;em&gt;Educational Psychology Review&lt;/em&gt; , 30(3), 703–725.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on Analyst Prep Kit: &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/net-present-value-npv/" rel="noopener noreferrer"&gt;Net Present Value (NPV): How to Discount Cash Flows and Read the Answer&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Visit the site for more beginner data analysis guides and free resources: &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/" rel="noopener noreferrer"&gt;the full guide archive&lt;/a&gt; covers SQL, Excel, Power BI, Tableau, Python and statistics, and the &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/" rel="noopener noreferrer"&gt;practice kits&lt;/a&gt; run in your browser with nothing to install.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If it was useful: &lt;a href="https://buymeacoffee.com/michaelnocito" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dataanalysis</category>
      <category>career</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Current Ratio vs Quick Ratio, and Debt to Equity</title>
      <dc:creator>Michael Nocito</dc:creator>
      <pubDate>Mon, 07 Sep 2026 13:00:13 +0000</pubDate>
      <link>https://dev.to/michaelnocito/current-ratio-vs-quick-ratio-and-debt-to-equity-28en</link>
      <guid>https://dev.to/michaelnocito/current-ratio-vs-quick-ratio-and-debt-to-equity-28en</guid>
      <description>&lt;p&gt;By &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/about.html" rel="noopener noreferrer"&gt;Michael Nocito&lt;/a&gt;, data analyst · Published August 11, 2026&lt;/p&gt;

&lt;p&gt;By the end of this page you can look at a balance sheet and answer two questions a lender asks first: can this company pay the bills that are about to come due, and how much of it was bought with borrowed money. You will know the three ratios, the SQL for all of them, and the four places where the definition, not the arithmetic, decides the answer.&lt;/p&gt;

&lt;p&gt;One thing to do today, and it takes five minutes. Find any balance sheet, yours or a public company's. Add up cash and receivables, then compare that to the bills due within the year. If the first number is smaller, the company is relying on selling inventory to make its payments, and that is a different business than the totals suggest.&lt;/p&gt;

&lt;p&gt;Both questions in one line: liquidity ratios compare what you can turn into cash soon against what you owe soon. Leverage ratios compare what is borrowed against what is owned.&lt;/p&gt;

&lt;p&gt;The gap between two companies that look equally healthy is the thing worth seeing first.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The original carries a diagram here. In words: Two panels side by side, each showing a single tall stacked column that represents one company's current assets, measured against a dashed horizontal line that marks the bills coming due within the year. In the left panel, labeled Northlight, the column is built from a solid accent-colored lower block labeled cash and receivables, with a hatched grey block labeled inventory stacked on top of it. The solid block on its own already rises above the dashed line, so this company could pay its bills without selling any inventory, and the panel carries a check mark. In the right panel, labeled Ridgeway, the whole column is taller than Northlight's, which would make its current ratio look better. But the split is different: the solid cash-and-receivables block is short and stops well below the dashed line, and the hatched inventory block above it makes up three quarters of the height. A small bracket marks the gap between the top of the solid block and the dashed line, and this panel carries a cross. The two dashed lines sit at different heights because the two companies owe different amounts. The picture shows that total height, which is what the current ratio measures, says nothing about whether the part you can actually spend reaches the line.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Every number on this page is verified, and you can check them.&lt;/strong&gt; Two balance sheets are shown in full below, line by line. Every ratio, gap and quarterly figure was computed in SQLite and cross-checked in pandas before it went on the page, so you can add up any column by hand and it will agree.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Two questions a balance sheet answers
&lt;/h2&gt;

&lt;p&gt;A company made $560,000 of profit last year. Name something that could still put it out of business in March.&lt;/p&gt;

&lt;p&gt;Running out of cash. Profit and cash are not the same thing, and a company can be profitable on paper while having nothing in the bank on the day a supplier wants paying. The income statement tells you whether the business earns; the balance sheet tells you whether it can pay.&lt;/p&gt;

&lt;p&gt;A balance sheet is a snapshot on one specific date. It lists what the company owns, called assets, what it owes, called liabilities, and the difference between them, called equity. Two questions are asked of it more often than all the others put together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Liquidity: can we cover what is due soon with what we can turn into cash soon?&lt;/strong&gt; That is a question about the near term, usually the next twelve months, and it is answered by the current ratio and the quick ratio.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Leverage: how much of this company was paid for with borrowed money?&lt;/strong&gt; That is a question about risk over the long term, and it is answered by debt to equity. Borrowed money has to be repaid on a schedule whether or not the business has a good year, which is what makes leverage the thing that turns a bad quarter into a crisis.&lt;/p&gt;

&lt;p&gt;Both questions use the same trick: divide one part of the balance sheet by another so the answer does not depend on how big the company is.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The current ratio, and what "current" means
&lt;/h2&gt;

&lt;p&gt;What do you think makes an asset "current"?&lt;/p&gt;

&lt;p&gt;Time. "Current" means within one year, and it is the only thing the word means here. A current asset is cash, or something expected to become cash within a year: money customers owe you, inventory you expect to sell, expenses you have prepaid. A current liability is a bill due within a year: money you owe suppliers, wages accrued but not yet paid, the slice of a loan repayable this year.&lt;/p&gt;

&lt;p&gt;The current ratio is current assets divided by current liabilities. Above 1.0 means there is more coming in soon than going out soon. Below 1.0 means the company needs to raise money from somewhere else to get through the year.&lt;/p&gt;

&lt;p&gt;Here is the first of our two balance sheets, at 31 December 2024.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Northlight&lt;/th&gt;
&lt;th&gt;Amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cash&lt;/td&gt;
&lt;td&gt;260,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Receivables&lt;/td&gt;
&lt;td&gt;470,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inventory&lt;/td&gt;
&lt;td&gt;490,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prepaid expenses&lt;/td&gt;
&lt;td&gt;40,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Current assets&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,260,000&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accounts payable&lt;/td&gt;
&lt;td&gt;430,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accrued expenses&lt;/td&gt;
&lt;td&gt;145,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Short-term debt&lt;/td&gt;
&lt;td&gt;125,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Current liabilities&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;700,000&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long-term debt&lt;/td&gt;
&lt;td&gt;1,065,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Equity&lt;/td&gt;
&lt;td&gt;1,700,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Current ratio: 1,260,000 divided by 700,000 is 1.80. For every dollar due within the year, there is a dollar eighty of assets expected to arrive within the year. That reads as comfortable, and for this company it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The quick ratio, and why it drops inventory
&lt;/h2&gt;

&lt;p&gt;Of cash, receivables and inventory, which one might take six months to turn into money, and might never turn into all of it?&lt;/p&gt;

&lt;p&gt;Inventory. It is a current asset because you expect to sell it inside a year, and "expect" is doing a lot of work in that sentence. Stock can sit. It can go out of fashion, expire, or need discounting to move. Receivables can be chased and cash is already cash, but inventory is a plan, not a payment.&lt;/p&gt;

&lt;p&gt;So the quick ratio runs the same test with inventory taken out. It asks whether the company could meet its near-term bills if nothing on the shelf sold at all. Above 1.0 means yes.&lt;/p&gt;

&lt;p&gt;Northlight's quick ratio: 1,260,000 minus 490,000 of inventory is 770,000, divided by 700,000, which is 1.10. It clears the bar without selling a thing.&lt;/p&gt;

&lt;p&gt;Now the second company, and this is where the two ratios come apart.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ridgeway&lt;/th&gt;
&lt;th&gt;Amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cash&lt;/td&gt;
&lt;td&gt;90,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Receivables&lt;/td&gt;
&lt;td&gt;190,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inventory&lt;/td&gt;
&lt;td&gt;900,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prepaid expenses&lt;/td&gt;
&lt;td&gt;20,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Current assets&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,200,000&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accounts payable&lt;/td&gt;
&lt;td&gt;310,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accrued expenses&lt;/td&gt;
&lt;td&gt;105,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Short-term debt&lt;/td&gt;
&lt;td&gt;85,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Current liabilities&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;500,000&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long-term debt&lt;/td&gt;
&lt;td&gt;520,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Equity&lt;/td&gt;
&lt;td&gt;1,250,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ridgeway's current ratio is 1,200,000 divided by 500,000, which is 2.40. That is a third better than Northlight's 1.80, and on a page that only carried the current ratio, Ridgeway is the healthier company.&lt;/p&gt;

&lt;p&gt;Its quick ratio is 1,200,000 minus 900,000, which is 300,000, divided by 500,000, which is 0.60. Against $500,000 of bills due inside the year, Ridgeway has $300,000 it can actually spend. It is $200,000 short, and the only way to close that gap is to sell inventory faster than it has been selling it.&lt;/p&gt;

&lt;p&gt;The two companies side by side.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ratio&lt;/th&gt;
&lt;th&gt;Northlight&lt;/th&gt;
&lt;th&gt;Ridgeway&lt;/th&gt;
&lt;th&gt;What it says&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Current ratio&lt;/td&gt;
&lt;td&gt;1.80&lt;/td&gt;
&lt;td&gt;2.40&lt;/td&gt;
&lt;td&gt;Ridgeway looks stronger&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quick ratio&lt;/td&gt;
&lt;td&gt;1.10&lt;/td&gt;
&lt;td&gt;0.60&lt;/td&gt;
&lt;td&gt;Ridgeway cannot pay without selling stock&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inventory as a share of current assets&lt;/td&gt;
&lt;td&gt;39%&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;td&gt;The whole reason for the reversal&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The claim this page earns, in one sentence: &lt;strong&gt;Ridgeway's current ratio is 33% better than Northlight's and it is $200,000 short of paying next year's bills.&lt;/strong&gt; That is why the quick ratio exists, and why nobody who lends money reads the current ratio on its own.&lt;/p&gt;

&lt;p&gt;Say why a bigger pile of current assets can be the weaker position, in your own words, before reading on.&lt;/p&gt;

&lt;p&gt;One more thing about the quick ratio, because it is the first place a definition changes the answer. Some texts define it as current assets minus inventory. Others define it as cash plus marketable securities plus receivables, added up from the top instead of subtracted from the total. Those are not the same. On Northlight's balance sheet the first gives 770,000 and a quick ratio of 1.10; the second gives 730,000 and a quick ratio of 1.04, because prepaid expenses are in current assets and are not something you can pay a supplier with. Same company, same day, two defensible answers 0.06 apart. Write down which one you used.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Debt to equity, and the word that has two meanings
&lt;/h2&gt;

&lt;p&gt;A company owes $1,765,000 in total, of which $575,000 is unpaid supplier invoices. Is that $575,000 "debt"?&lt;/p&gt;

&lt;p&gt;It depends who is asking, and this is the single most common reason two people compute different debt-to-equity ratios from the same balance sheet.&lt;/p&gt;

&lt;p&gt;Debt to equity divides what the company borrowed by what the owners put in and left in. Equity is total assets minus total liabilities: the slice of the company nobody else has a claim on. A ratio of 0.50 means fifty cents borrowed for every dollar owned. A ratio of 2.00 means two dollars borrowed for every dollar owned, and a bad year now threatens the lenders' money as well as the owners'.&lt;/p&gt;

&lt;p&gt;The disagreement is over the numerator. &lt;strong&gt;Interest-bearing debt&lt;/strong&gt; counts only money that was borrowed and carries interest: loans, bonds, the short-term borrowings line. &lt;strong&gt;Total liabilities&lt;/strong&gt; counts everything the company owes anyone, including supplier invoices and accrued wages, which carry no interest and no repayment schedule.&lt;/p&gt;

&lt;p&gt;On Northlight, run both.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Definition&lt;/th&gt;
&lt;th&gt;Numerator&lt;/th&gt;
&lt;th&gt;Debt to equity&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Interest-bearing debt&lt;/td&gt;
&lt;td&gt;125,000 + 1,065,000 = 1,190,000&lt;/td&gt;
&lt;td&gt;0.70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total liabilities&lt;/td&gt;
&lt;td&gt;700,000 + 1,065,000 = 1,765,000&lt;/td&gt;
&lt;td&gt;1.04&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both divide by the same $1,700,000 of equity. One says the company is modestly borrowed. The other says it owes slightly more than it owns. The gap is the $575,000 of payables and accruals that carry no interest, and the wider definition is 48% higher on this company.&lt;/p&gt;

&lt;p&gt;Neither is wrong. A credit analyst usually wants interest-bearing debt, because that is what has to be serviced on a schedule. An accounting textbook usually means total liabilities. Pick one, name it in the column header, and never compare a ratio built one way against a ratio built the other. Ridgeway, for the record, is 0.48 on the narrow definition and 0.82 on the wide one.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The query, and the division that returns 1
&lt;/h2&gt;

&lt;p&gt;In SQL, current assets of 1,260,000 divided by current liabilities of 700,000, both stored as whole numbers. What comes back?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Not 1.80. Whole number divided by whole number gives a whole number in most databases, so the 0.80 is discarded. This is nastier here than in a percentage, because 1 is a perfectly plausible current ratio. It sits exactly on the line between covered and not covered, so a reader has no reason to doubt it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Watch what it does to all three ratios on Northlight.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you write&lt;/th&gt;
&lt;th&gt;Returns&lt;/th&gt;
&lt;th&gt;The truth&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;current_assets / current_liabilities&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1.80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;(current_assets - inventory) / current_liabilities&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1.10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;total_debt / equity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0.70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ROUND(current_assets / current_liabilities, 2)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;1.80&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last row is the dangerous one. &lt;code&gt;ROUND&lt;/code&gt; makes the output look like a decimal calculation, so the column reads 1.0 and 1.0 and 0.0 down the page and nothing suggests the arithmetic never happened. On Ridgeway the same bug returns a current ratio of 2 and a quick ratio of 0, which turns "0.60, and it is short" into "0, and it has nothing", a different and equally wrong story.&lt;/p&gt;

&lt;p&gt;The fix is one character in three places. Multiply by &lt;code&gt;1.0&lt;/code&gt; before dividing, and the whole expression goes decimal.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT company,
       ROUND(1.0 * current_assets / current_liabilities, 2)               AS current_ratio,
       ROUND(1.0 * (current_assets - inventory) / current_liabilities, 2) AS quick_ratio,
       ROUND(1.0 * total_debt / equity, 2)                                AS debt_to_equity
FROM balance_sheet
WHERE as_of = '2024-12-31'
ORDER BY company;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That returns 1.80 / 1.10 / 0.70 for Northlight and 2.40 / 0.60 / 0.48 for Ridgeway. The same rule catches every ratio you will ever write in SQL, and it is worked from the other direction in &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/gross-vs-operating-vs-net-margin/" rel="noopener noreferrer"&gt;gross vs operating vs net margin&lt;/a&gt;, where the same division returns 0 instead of 1.&lt;/p&gt;

&lt;p&gt;Now the &lt;code&gt;WHERE as_of&lt;/code&gt; line, which matters more than it looks. A balance sheet table usually holds several snapshots, one per quarter or per month. Leave the filter off and &lt;code&gt;SUM&lt;/code&gt; quietly adds four dates together. Northlight's four 2024 snapshots summed give a current ratio of 1.92; the year-end figure is 1.80. Both are plausible, only one is the company's current ratio, and nothing in the output tells you which you got.&lt;/p&gt;

&lt;p&gt;Picture your own company's balance-sheet table. How many dates are in it, and does the query you inherited filter to one of them? That check takes ten seconds and it is worth doing before you trust any ratio you did not write yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Benchmarks, and why a high ratio can be bad news
&lt;/h2&gt;

&lt;p&gt;A company's current ratio is 6.0. Name a reason that might not be good.&lt;/p&gt;

&lt;p&gt;Idle money. Assets sitting in cash or unsold stock are assets not being used to grow anything. A current ratio far above what the business needs usually means cash the company has not found a use for, or inventory it cannot shift. Neither is a crisis, and neither is a strength.&lt;/p&gt;

&lt;p&gt;The usual comfortable band is roughly 1.5 to 3.0 for the current ratio and at or above 1.0 for the quick ratio, but treat those as starting points rather than rules. A supermarket collects cash at the till and pays its suppliers weeks later, so it runs happily below 1.0 and always has. A shipbuilder holds enormous inventory for years and runs far higher. The band belongs to the industry, not to accounting.&lt;/p&gt;

&lt;p&gt;The same goes for leverage. Debt to equity above about 2.0 is a flag for most companies, and utilities, airlines and property firms run above it as a matter of course, because they own predictable assets that lenders are happy to lend against. Comparing an airline's leverage to a software company's tells you what industry they are in, not which is better run.&lt;/p&gt;

&lt;p&gt;Three comparisons are worth more than any published band.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The same company over time.&lt;/strong&gt; This is the one that always works, because everything else is held constant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A named competitor of similar size,&lt;/strong&gt; in the same industry, on the same date.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The covenant.&lt;/strong&gt; If the company has a loan, the agreement almost certainly names a minimum current ratio or a maximum debt-to-equity ratio in writing. That number beats every benchmark on the internet, because breaching it has consequences on a specific date.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Time is where the story usually is. Here are Northlight's four 2024 snapshots, with the three inputs each ratio comes from, so the trend can be checked the same way the year-end figures were.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;As of&lt;/th&gt;
&lt;th&gt;Current assets&lt;/th&gt;
&lt;th&gt;Inventory&lt;/th&gt;
&lt;th&gt;Receivables&lt;/th&gt;
&lt;th&gt;Current liabilities&lt;/th&gt;
&lt;th&gt;Current ratio&lt;/th&gt;
&lt;th&gt;Quick ratio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2024-03-31&lt;/td&gt;
&lt;td&gt;1,130,000&lt;/td&gt;
&lt;td&gt;300,000&lt;/td&gt;
&lt;td&gt;380,000&lt;/td&gt;
&lt;td&gt;545,000&lt;/td&gt;
&lt;td&gt;2.07&lt;/td&gt;
&lt;td&gt;1.52&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2024-06-30&lt;/td&gt;
&lt;td&gt;1,170,000&lt;/td&gt;
&lt;td&gt;360,000&lt;/td&gt;
&lt;td&gt;420,000&lt;/td&gt;
&lt;td&gt;595,000&lt;/td&gt;
&lt;td&gt;1.97&lt;/td&gt;
&lt;td&gt;1.36&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2024-09-30&lt;/td&gt;
&lt;td&gt;1,220,000&lt;/td&gt;
&lt;td&gt;430,000&lt;/td&gt;
&lt;td&gt;450,000&lt;/td&gt;
&lt;td&gt;653,000&lt;/td&gt;
&lt;td&gt;1.87&lt;/td&gt;
&lt;td&gt;1.21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2024-12-31&lt;/td&gt;
&lt;td&gt;1,260,000&lt;/td&gt;
&lt;td&gt;490,000&lt;/td&gt;
&lt;td&gt;470,000&lt;/td&gt;
&lt;td&gt;700,000&lt;/td&gt;
&lt;td&gt;1.80&lt;/td&gt;
&lt;td&gt;1.10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Check the March row against the December one you already worked. 1,130,000 minus 300,000 of inventory is 830,000, over 545,000 of bills, which is 1.52. The quick ratio here is the current-assets-minus-inventory version from section three, the same one used throughout this page.&lt;/p&gt;

&lt;p&gt;Read as one date, 1.80 and 1.10 are fine. Read as four, the quick ratio has fallen every quarter, from 1.52 to 1.10, and it is heading for the 1.0 line. Inventory grew from $300,000 to $490,000 across the year, up 63%, while receivables grew from $380,000 to $470,000, up 24%. Northlight is quietly turning into Ridgeway, and no single snapshot could have told you that.&lt;/p&gt;

&lt;p&gt;This is also where the missing filter from section five shows its teeth. Summed across all four dates, current assets are $4,780,000 against $2,493,000 of current liabilities, which is the 1.92 a query with no &lt;code&gt;WHERE as_of&lt;/code&gt; returns. It is not any quarter's ratio, it is not the average of the four, and it is the one number in this table that describes nothing that ever happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. The full before and after
&lt;/h2&gt;

&lt;p&gt;Same two balance sheets, two ways of reporting them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before
&lt;/h3&gt;

&lt;p&gt;"Ridgeway current ratio 2.40, Northlight 1.80. Both above the 1.5 threshold. No liquidity concerns." Every number is right, the threshold is a real one, and the conclusion is backwards. A reader has no way to see that one of these companies has $300,000 of spendable assets against $500,000 of bills, because the report never separated the spendable part from the rest.&lt;/p&gt;

&lt;h3&gt;
  
  
  After
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measure&lt;/th&gt;
&lt;th&gt;Northlight&lt;/th&gt;
&lt;th&gt;Ridgeway&lt;/th&gt;
&lt;th&gt;Read&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Current ratio&lt;/td&gt;
&lt;td&gt;1.80&lt;/td&gt;
&lt;td&gt;2.40&lt;/td&gt;
&lt;td&gt;Both above 1.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quick ratio&lt;/td&gt;
&lt;td&gt;1.10&lt;/td&gt;
&lt;td&gt;0.60&lt;/td&gt;
&lt;td&gt;Only one clears 1.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quick assets&lt;/td&gt;
&lt;td&gt;770,000&lt;/td&gt;
&lt;td&gt;300,000&lt;/td&gt;
&lt;td&gt;The dollars behind the ratio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Current liabilities&lt;/td&gt;
&lt;td&gt;700,000&lt;/td&gt;
&lt;td&gt;500,000&lt;/td&gt;
&lt;td&gt;What is due within the year&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gap&lt;/td&gt;
&lt;td&gt;+70,000&lt;/td&gt;
&lt;td&gt;-200,000&lt;/td&gt;
&lt;td&gt;Ridgeway must sell stock to pay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debt to equity (interest-bearing)&lt;/td&gt;
&lt;td&gt;0.70&lt;/td&gt;
&lt;td&gt;0.48&lt;/td&gt;
&lt;td&gt;Ridgeway borrowed less&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debt to equity (total liabilities)&lt;/td&gt;
&lt;td&gt;1.04&lt;/td&gt;
&lt;td&gt;0.82&lt;/td&gt;
&lt;td&gt;Same companies, wider definition&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two additions do all the work. The quick ratio splits the pile, and the gap row converts the ratio back into dollars so a reader does not have to. The definition of debt is named in the row label rather than assumed. And the finding is now visible without any expertise at all: the company that borrowed less is the one that might not make its payments.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Edge cases: a snapshot is easy to move
&lt;/h2&gt;

&lt;p&gt;It is 30 December. Name one thing a company could do that day to improve its current ratio without changing anything about the business.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pay a bill.&lt;/strong&gt; Take $200,000 of cash and settle $200,000 of accounts payable on the last working day of the year. Current assets fall to $1,060,000, current liabilities fall to $500,000, and the current ratio goes from 1.80 to 2.12. The company is no healthier. It has less cash than it did that morning. Because both sides of the fraction shrink by the same amount, and the top was already bigger, the ratio improves. This has a name in the trade, window dressing, and it is why a lender looks at several dates rather than one. Northlight's cash after that payment would be $60,000 of the $260,000 it started with, which is the part a ratio never shows you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A ratio is a snapshot and an income statement is a film.&lt;/strong&gt; Mixing them needs care. Ratios that put a balance-sheet number over an income-statement number, like inventory turnover, conventionally use the average of the opening and closing balance rather than the closing one, precisely because one date does not represent a year. If you build one of those, say which convention you used.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Equity can be negative, and then the ratio is meaningless.&lt;/strong&gt; A company that has lost more over its life than owners put in has negative equity, and debt to equity comes out negative. A negative leverage ratio is not low leverage, it is a company whose liabilities exceed its assets. Guard it in the query and report the words instead of the number.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not every current liability is equally urgent.&lt;/strong&gt; A payable due in eleven months and one due next Tuesday both sit in the same total. The ratio treats them identically, and the company's bank account does not. When liquidity is genuinely tight, the ratio stops being the right tool and a week-by-week cash forecast starts being one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Receivables are only as good as the customers.&lt;/strong&gt; The quick ratio trusts every dollar of receivables at face value. If a third of them are ninety days overdue, the real quick ratio is lower than the computed one. An ageing report sits next to this analysis, not after it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this works
&lt;/h2&gt;

&lt;p&gt;The reason a single ratio is not enough is a measurement problem, not an accounting one. Any single indicator used to judge performance tends to be managed toward rather than improved on, an effect Campbell set out from social program evaluation (Campbell, 1979, &lt;em&gt;Evaluation and Program Planning&lt;/em&gt; , 2(1), 67–90) and Goodhart put in one line about monetary targets (Goodhart, 1975, reprinted in &lt;em&gt;Monetary Theory and Practice&lt;/em&gt; , Macmillan, 1984, 91–121). The 30 December payment in section eight is that effect on a balance sheet: the number moved, the thing it measures did not. Two ratios that can disagree, read across four dates, is the cheapest defence available.&lt;/p&gt;

&lt;p&gt;Ratio analysis of this kind is also older and better tested than it looks. Beaver's study of 79 failed companies against 79 matched survivors found that balance-sheet ratios separated the two groups up to five years before failure, with cash-flow and debt ratios among the strongest single predictors (Beaver, 1966, &lt;em&gt;Journal of Accounting Research&lt;/em&gt; , 4, 71–111). Altman's multi-ratio model, built on 66 manufacturers, classified 95% of them correctly one year out (Altman, 1968, &lt;em&gt;The Journal of Finance&lt;/em&gt; , 23(4), 589–609). Neither found any one ratio sufficient on its own, which is the finding this page is built on.&lt;/p&gt;

&lt;p&gt;The question at the top of each section is deliberate. Attempting an answer before receiving one improves learning of that specific material across sixty-four studies (Bisra, Liu, Nesbit, Salimi, &amp;amp; Winne, 2018, &lt;em&gt;Educational Psychology Review&lt;/em&gt; , 30(3), 703–725). Guessing which asset is slowest to become cash, before section three said inventory, is why you will still know it in an interview.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using this on your own numbers
&lt;/h2&gt;

&lt;p&gt;Rebuilding every ratio report in the company is a slog and nobody thanks you for it. Do this instead, in order.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add the quick ratio wherever a current ratio already appears.&lt;/strong&gt; One column, one subtraction, and it is the column that would have caught Ridgeway.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the division is decimal.&lt;/strong&gt; If any ratio in your report is a whole number like 1, 2 or 0, you have integer division. Section five has the fix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confirm the query filters to one&lt;code&gt;as_of&lt;/code&gt; date.&lt;/strong&gt; Then put four dates side by side, because the trend is where the story was on this page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Name the debt definition in the column header.&lt;/strong&gt; "D/E (interest-bearing)" costs three words and removes an argument you would otherwise have every quarter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add a dollar column next to every ratio.&lt;/strong&gt; Quick assets and current liabilities in dollars, and the gap between them. A ratio persuades nobody; $200,000 short does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Find the loan covenant and write its number at the top of the report.&lt;/strong&gt; If there is a minimum current ratio in a credit agreement, that is the only threshold on the page that has consequences.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One optional drawing, if there is paper within reach. Draw the two columns from the top of this page from memory: a stack of current assets for each company, a dashed line for the bills due, and the split between the part you can spend and the inventory above it. Mark which solid block reaches its line. If both of yours clear it, you have drawn the current ratio rather than the quick ratio, and the page is still open.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This page is one of five.&lt;/strong&gt; The Financial analysis set on the &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/" rel="noopener noreferrer"&gt;guides index&lt;/a&gt; is this page plus &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/gross-vs-operating-vs-net-margin/" rel="noopener noreferrer"&gt;Gross vs Operating vs Net Margin&lt;/a&gt;, &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/contribution-margin-break-even/" rel="noopener noreferrer"&gt;Contribution Margin and Break-Even&lt;/a&gt;, &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/net-present-value-npv/" rel="noopener noreferrer"&gt;Net Present Value (NPV)&lt;/a&gt; and &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/budget-vs-actual-variance/" rel="noopener noreferrer"&gt;Budget vs Actual Variance&lt;/a&gt;. They share a worked company, so the numbers carry across. The index also holds every other how-to: SQL, Excel, Tableau, and the working habits around them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The whole thing on one screen
&lt;/h2&gt;

&lt;p&gt;Cover the right column. Work down the left and say each answer before you look.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Liquidity&lt;/td&gt;
&lt;td&gt;Can we pay what is due soon with what becomes cash soon.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leverage&lt;/td&gt;
&lt;td&gt;How much of the company was paid for with borrowed money.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Current&lt;/td&gt;
&lt;td&gt;Within one year. That is the whole meaning of the word.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Current ratio&lt;/td&gt;
&lt;td&gt;Current assets ÷ current liabilities. Northlight 1.80.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quick ratio&lt;/td&gt;
&lt;td&gt;The same test with inventory taken out. Northlight 1.10.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Why drop inventory&lt;/td&gt;
&lt;td&gt;It is the slowest current asset to become cash, and it might not.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The reversal&lt;/td&gt;
&lt;td&gt;Ridgeway: current 2.40, quick 0.60, and $200,000 short of its bills.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two quick-ratio formulas&lt;/td&gt;
&lt;td&gt;Current assets minus inventory gives 1.10. Cash plus receivables gives 1.04.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debt to equity&lt;/td&gt;
&lt;td&gt;Borrowed ÷ owned. 0.70 on interest-bearing debt for Northlight.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The debt argument&lt;/td&gt;
&lt;td&gt;Total liabilities instead gives 1.04 on the same balance sheet.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The integer trap&lt;/td&gt;
&lt;td&gt;1260000 / 700000 returns 1. Write &lt;code&gt;1.0 *&lt;/code&gt; before the division.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The missing filter&lt;/td&gt;
&lt;td&gt;No &lt;code&gt;WHERE as_of&lt;/code&gt; summed four snapshots and gave 1.92 instead of 1.80.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Comfortable bands&lt;/td&gt;
&lt;td&gt;Current 1.5 to 3.0, quick at or above 1.0, D/E under 2.0. Industry decides.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Too high&lt;/td&gt;
&lt;td&gt;Idle cash or unsold stock. Not a strength.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Window dressing&lt;/td&gt;
&lt;td&gt;Paying $200,000 on 30 December moved 1.80 to 2.12 and changed nothing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Negative equity&lt;/td&gt;
&lt;td&gt;D/E goes negative. That is not low leverage, it is insolvency.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The deliverable&lt;/td&gt;
&lt;td&gt;The ratio, the dollars behind it, and the trend across four dates.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The one habit to keep.&lt;/strong&gt; Keep this one and you can let the rest go. Never report a liquidity ratio without the dollar gap beside it. "Quick ratio 0.60" is a number people nod at. "$300,000 of spendable assets against $500,000 of bills" is a number people act on, and it is the same fact. If the numbers fight back in a way this page does not cover, there is a general &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/technical-tenacity/" rel="noopener noreferrer"&gt;diagnosis loop for being stuck&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A question back to you, and I do want the answers. The first ratio table I built had a column header that just said "D/E", and two people in the room were reading it as two different numbers for ten minutes before anyone noticed. What is the shortest column header you have seen cause the longest argument?&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Beaver, W. H. (1966). Financial ratios as predictors of failure. &lt;em&gt;Journal of Accounting Research&lt;/em&gt; , 4, 71–111.&lt;/li&gt;
&lt;li&gt;Altman, E. I. (1968). Financial ratios, discriminant analysis and the prediction of corporate bankruptcy. &lt;em&gt;The Journal of Finance&lt;/em&gt; , 23(4), 589–609.&lt;/li&gt;
&lt;li&gt;Campbell, D. T. (1979). Assessing the impact of planned social change. &lt;em&gt;Evaluation and Program Planning&lt;/em&gt; , 2(1), 67–90.&lt;/li&gt;
&lt;li&gt;Goodhart, C. A. E. (1975). Problems of monetary management: The U.K. experience. Reprinted in &lt;em&gt;Monetary Theory and Practice: The U.K. Experience&lt;/em&gt; (pp. 91–121). Macmillan, 1984.&lt;/li&gt;
&lt;li&gt;Bisra, K., Liu, Q., Nesbit, J. C., Salimi, F., &amp;amp; Winne, P. H. (2018). Inducing self-explanation: A meta-analysis. &lt;em&gt;Educational Psychology Review&lt;/em&gt; , 30(3), 703–725.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on Analyst Prep Kit: &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/liquidity-and-leverage-ratios/" rel="noopener noreferrer"&gt;Current Ratio vs Quick Ratio, and Debt to Equity&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Visit the site for more beginner data analysis guides and free resources: &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/" rel="noopener noreferrer"&gt;the full guide archive&lt;/a&gt; covers SQL, Excel, Power BI, Tableau, Python and statistics, and the &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/" rel="noopener noreferrer"&gt;practice kits&lt;/a&gt; run in your browser with nothing to install.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If it was useful: &lt;a href="https://buymeacoffee.com/michaelnocito" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dataanalysis</category>
      <category>career</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Gross vs Operating vs Net Margin: What Each One Tells You</title>
      <dc:creator>Michael Nocito</dc:creator>
      <pubDate>Mon, 07 Sep 2026 13:00:04 +0000</pubDate>
      <link>https://dev.to/michaelnocito/gross-vs-operating-vs-net-margin-what-each-one-tells-you-66h</link>
      <guid>https://dev.to/michaelnocito/gross-vs-operating-vs-net-margin-what-each-one-tells-you-66h</guid>
      <description>&lt;p&gt;By &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/about.html" rel="noopener noreferrer"&gt;Michael Nocito&lt;/a&gt;, data analyst · Published August 11, 2026&lt;/p&gt;

&lt;p&gt;By the end of this page you can look at an income statement and say, in one sentence, which layer of the business is losing the money. You will know what each of the three margins measures, why they always step down in order, the SQL that computes all three in one pass, and the two ways that SQL quietly returns the wrong number.&lt;/p&gt;

&lt;p&gt;Try this before you read another word. Take any two periods of your company's numbers. Work out what percent revenue grew, then what percent profit grew. If profit grew slower, the business got bigger and kept less of each dollar, and the three margins tell you which layer ate the difference.&lt;/p&gt;

&lt;p&gt;In one line: a margin is a profit line divided by revenue. Each one subtracts one more layer of cost, so gross is always at least operating, and operating is always at least net.&lt;/p&gt;

&lt;p&gt;The stepping-down is the whole shape of it, so it gets the picture.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The original carries a diagram here. In words: A single horizontal bar representing one dollar of revenue, drawn four times, one row under the next, each row shorter than the one above it. The top row is the full bar, labeled revenue, running the whole width. The second row is 60 percent as long and labeled gross; the piece missing from its right-hand end is drawn as a hatched block labeled cost of goods. The third row is 22 percent as long and labeled operating; the piece missing from the second row is a hatched block labeled operating expense. The fourth row is 14 percent as long and labeled net; the piece missing from the third row is a hatched block labeled interest and tax. Each hatched block sits directly to the right of the bar it was taken from, so the four rows form a descending staircase with the removed costs stacking to the right. A dashed vertical line marks the left edge shared by every bar, showing that all four are measured from the same starting point, which is revenue. The picture is the reason the three margins can never cross: each one starts from the row above it and can only get shorter.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Every number on this page is verified, and you can check them.&lt;/strong&gt; The worked example is one small company's two most recent years, shown in full below. Every margin, growth rate, subtotal and monthly figure was computed in SQLite and cross-checked in pandas before it went on the page, so you can check any cell by hand and it will agree.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. What a margin is, and why divide by revenue at all
&lt;/h2&gt;

&lt;p&gt;Two years of profit, $538,000 then $560,000. Say whether that company had a good year, and say what you would need to know to be sure.&lt;/p&gt;

&lt;p&gt;A margin is a profit line divided by revenue, written as a percent. That is the entire definition. If revenue is $4,000,000 and net income is $560,000, the net margin is 560,000 divided by 4,000,000, which is 0.14, which is 14%.&lt;/p&gt;

&lt;p&gt;The reason to divide is that raw profit dollars cannot be compared to anything. A bigger year makes more profit almost automatically, so "profit went up" tells you the company got bigger, not that it got better. Dividing by revenue converts the number into cents kept per sales dollar, and cents kept per dollar can be compared to last year, to the plan, and to a competitor ten times the size.&lt;/p&gt;

&lt;p&gt;One warning about the word "kept," because it is doing less work than it sounds like. A margin of 14% does not mean 14 cents of every dollar is in the bank. It means 14 cents survived on paper, and paper and cash part company constantly. A customer who has not paid yet still counts as revenue and still counts as profit. A company can post its best margin ever and miss payroll in the same month. What a business can actually pay is a different question with different arithmetic, and it lives on the balance sheet rather than here: &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/liquidity-and-leverage-ratios/" rel="noopener noreferrer"&gt;the current ratio and the quick ratio&lt;/a&gt; are the two numbers that answer it.&lt;/p&gt;

&lt;p&gt;Here is the same idea in one line you can hold on to. Profit answers "how much did we make." A margin answers "how much of what came in did we keep, on paper." Those are different questions, and only the second one survives a change in size.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The three margins, and the layer each one accuses
&lt;/h2&gt;

&lt;p&gt;If a company's product sells profitably but the company loses money overall, which of the three margins would still look healthy?&lt;/p&gt;

&lt;p&gt;An income statement is a cascade. Revenue at the top, then costs subtract in layers, and each subtotal along the way is a profit line worth dividing by revenue. Three of those subtotals matter most.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gross margin&lt;/strong&gt; is gross profit divided by revenue, where gross profit is revenue minus cost of goods sold. Cost of goods sold is what it costs to make or deliver the thing you sold: materials, the people who build it, shipping. Gross margin is the product's own economics. It moves when you change your price or when your suppliers change theirs, and almost nothing else touches it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Operating margin&lt;/strong&gt; is operating income divided by revenue, where operating income is gross profit minus operating expense. Operating expense is the cost of running the company rather than making the product: sales salaries, rent, software, marketing, the finance team. Operating margin is the core business. It moves when overhead grows faster than sales.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Net margin&lt;/strong&gt; is net income divided by revenue, and net income is what is left after everything, including interest on borrowed money and tax. Net margin moves on how much debt the company carries and what it pays the government, neither of which has much to do with how well the product sells.&lt;/p&gt;

&lt;p&gt;So each margin removes one more layer, and that is why they always fall in the same order: gross is at least operating, and operating is at least net. When a company's product is profitable but the company is not, gross margin stays healthy and the collapse shows up below it. The margin that stops looking good is the layer that owes you an explanation.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The worked income statement, two years side by side
&lt;/h2&gt;

&lt;p&gt;Revenue is about to grow by a third. Guess whether net margin goes up or down, and write your guess down.&lt;/p&gt;

&lt;p&gt;Here is the company. It sells coffee equipment, it books everything in whole dollars, and these are its last two fiscal years.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Line&lt;/th&gt;
&lt;th&gt;2023&lt;/th&gt;
&lt;th&gt;2024&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Revenue&lt;/td&gt;
&lt;td&gt;3,000,000&lt;/td&gt;
&lt;td&gt;4,000,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost of goods sold&lt;/td&gt;
&lt;td&gt;1,110,000&lt;/td&gt;
&lt;td&gt;1,600,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Gross profit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,890,000&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2,400,000&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operating expense&lt;/td&gt;
&lt;td&gt;1,116,000&lt;/td&gt;
&lt;td&gt;1,520,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Operating income&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;774,000&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;880,000&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interest&lt;/td&gt;
&lt;td&gt;60,000&lt;/td&gt;
&lt;td&gt;120,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tax&lt;/td&gt;
&lt;td&gt;176,000&lt;/td&gt;
&lt;td&gt;200,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Net income&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;538,000&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;560,000&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Check one column by hand to trust the rest. 2024: 4,000,000 minus 1,600,000 is 2,400,000 gross profit. Minus 1,520,000 of operating expense is 880,000. Minus 120,000 of interest and 200,000 of tax is 560,000.&lt;/p&gt;

&lt;p&gt;Now the same two years as margins.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Margin&lt;/th&gt;
&lt;th&gt;2023&lt;/th&gt;
&lt;th&gt;2024&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gross&lt;/td&gt;
&lt;td&gt;63.0%&lt;/td&gt;
&lt;td&gt;60.0%&lt;/td&gt;
&lt;td&gt;-3.0 points&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operating&lt;/td&gt;
&lt;td&gt;25.8%&lt;/td&gt;
&lt;td&gt;22.0%&lt;/td&gt;
&lt;td&gt;-3.8 points&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Net&lt;/td&gt;
&lt;td&gt;17.9%&lt;/td&gt;
&lt;td&gt;14.0%&lt;/td&gt;
&lt;td&gt;-3.9 points&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both tables describe the same two years. The first one says the company had its best year. The second one says the company had its worst year in a while. Revenue grew 33.3%, gross profit grew 27.0%, operating income grew 13.7%, and net income grew 4.1%. Every line went up, and every line went up slower than the one above it.&lt;/p&gt;

&lt;p&gt;The one sentence this table earns: &lt;strong&gt;the company grew a third bigger and now keeps 14.0 cents of every dollar instead of 17.9.&lt;/strong&gt; Put the shortfall in dollars and it stops being abstract. At last year's net margin, $4,000,000 of revenue would have produced $717,333 of net income. It produced $560,000. The difference is $157,333, which is more than a quarter of the profit the company actually made.&lt;/p&gt;

&lt;p&gt;Say why net margin can fall while net income rises, in your own words, before reading on. If you can say it, the rest of this page is arithmetic.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The query, and the division that returns 0
&lt;/h2&gt;

&lt;p&gt;In SQL, &lt;code&gt;2400000 / 4000000&lt;/code&gt; where both columns are whole numbers. What comes back?&lt;/p&gt;

&lt;p&gt;Zero. Not 0.6. Most databases follow the rule that a whole number divided by a whole number is a whole number, so the 0.6 is thrown away and you get 0. Multiply that 0 by 100 afterwards and you get 0. The query runs, the column fills with zeros, and nothing anywhere says an error happened.&lt;/p&gt;

&lt;p&gt;The fix is to make one side of the division a decimal, and to do it &lt;em&gt;before&lt;/em&gt; the division rather than after. Writing &lt;code&gt;100.0 *&lt;/code&gt; at the front of the expression turns the whole calculation decimal from that point on.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT fiscal_year,
       ROUND(100.0 * (revenue - cogs) / revenue, 1)
         AS gross_margin_pct,
       ROUND(100.0 * (revenue - cogs - operating_expense) / revenue, 1)
         AS operating_margin_pct,
       ROUND(100.0 * (revenue - cogs - operating_expense - interest - tax) / revenue, 1)
         AS net_margin_pct
FROM income_statement
ORDER BY fiscal_year;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Run against the two years above, that returns 63.0 / 25.8 / 17.9 and 60.0 / 22.0 / 14.0. Here is the same expression written four ways, all on the 2024 row, so you can see exactly where the decimal has to sit.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you write&lt;/th&gt;
&lt;th&gt;What comes back&lt;/th&gt;
&lt;th&gt;Right?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gross_profit / revenue * 100&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;No. The division happens first and rounds to 0.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;100 * gross_profit / revenue&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;Right this time, wrong in general. It rounds the answer down to a whole percent.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;100.0 * gross_profit / revenue&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;60.0&lt;/td&gt;
&lt;td&gt;Yes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ROUND(100.0 * gross_profit / revenue, 1)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;60.0&lt;/td&gt;
&lt;td&gt;Yes, and it stops the display carrying twelve decimal places.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Row two is the one that catches people, because on this data it returns 60 and 60 is correct. It is correct by luck. Multiply first and the result is a whole number of percent, so 63.0% would come back as 63 and a genuine 22.4% would come back as 22. You would never see the missing four tenths. The wider version of this rule shows up in every growth calculation, and &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/sql-month-over-month/" rel="noopener noreferrer"&gt;month-over-month growth in SQL&lt;/a&gt; works the same trap on a different formula.&lt;/p&gt;

&lt;p&gt;One more note on &lt;code&gt;ROUND&lt;/code&gt;. It does not protect you from integer division, because by the time &lt;code&gt;ROUND&lt;/code&gt; sees the value the 0 has already happened. &lt;code&gt;ROUND(0, 1)&lt;/code&gt; is &lt;code&gt;0.0&lt;/code&gt;, which looks like a decimal calculation and is not one.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Averaging monthly margins loses 3.1 points
&lt;/h2&gt;

&lt;p&gt;You have twelve monthly operating margins. Somebody asks for the year's operating margin. Is averaging the twelve the right move?&lt;/p&gt;

&lt;p&gt;No, and here is the size of the mistake. This is the same 2024 as above, split into the twelve months it was made of. Cost of goods runs at 40% of each month's revenue, and operating expense is a fixed $110,000 base plus 5% of revenue, which is what a real cost structure looks like: some of it scales with sales and some of it arrives whether you sell anything or not.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Month&lt;/th&gt;
&lt;th&gt;Revenue&lt;/th&gt;
&lt;th&gt;Cost of goods&lt;/th&gt;
&lt;th&gt;Operating expense&lt;/th&gt;
&lt;th&gt;Operating income&lt;/th&gt;
&lt;th&gt;Operating margin&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2024-01&lt;/td&gt;
&lt;td&gt;180,000&lt;/td&gt;
&lt;td&gt;72,000&lt;/td&gt;
&lt;td&gt;119,000&lt;/td&gt;
&lt;td&gt;-11,000&lt;/td&gt;
&lt;td&gt;-6.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2024-02&lt;/td&gt;
&lt;td&gt;195,000&lt;/td&gt;
&lt;td&gt;78,000&lt;/td&gt;
&lt;td&gt;119,750&lt;/td&gt;
&lt;td&gt;-2,750&lt;/td&gt;
&lt;td&gt;-1.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2024-03&lt;/td&gt;
&lt;td&gt;250,000&lt;/td&gt;
&lt;td&gt;100,000&lt;/td&gt;
&lt;td&gt;122,500&lt;/td&gt;
&lt;td&gt;27,500&lt;/td&gt;
&lt;td&gt;11.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2024-04&lt;/td&gt;
&lt;td&gt;300,000&lt;/td&gt;
&lt;td&gt;120,000&lt;/td&gt;
&lt;td&gt;125,000&lt;/td&gt;
&lt;td&gt;55,000&lt;/td&gt;
&lt;td&gt;18.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2024-05&lt;/td&gt;
&lt;td&gt;330,000&lt;/td&gt;
&lt;td&gt;132,000&lt;/td&gt;
&lt;td&gt;126,500&lt;/td&gt;
&lt;td&gt;71,500&lt;/td&gt;
&lt;td&gt;21.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2024-06&lt;/td&gt;
&lt;td&gt;360,000&lt;/td&gt;
&lt;td&gt;144,000&lt;/td&gt;
&lt;td&gt;128,000&lt;/td&gt;
&lt;td&gt;88,000&lt;/td&gt;
&lt;td&gt;24.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2024-07&lt;/td&gt;
&lt;td&gt;340,000&lt;/td&gt;
&lt;td&gt;136,000&lt;/td&gt;
&lt;td&gt;127,000&lt;/td&gt;
&lt;td&gt;77,000&lt;/td&gt;
&lt;td&gt;22.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2024-08&lt;/td&gt;
&lt;td&gt;315,000&lt;/td&gt;
&lt;td&gt;126,000&lt;/td&gt;
&lt;td&gt;125,750&lt;/td&gt;
&lt;td&gt;63,250&lt;/td&gt;
&lt;td&gt;20.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2024-09&lt;/td&gt;
&lt;td&gt;350,000&lt;/td&gt;
&lt;td&gt;140,000&lt;/td&gt;
&lt;td&gt;127,500&lt;/td&gt;
&lt;td&gt;82,500&lt;/td&gt;
&lt;td&gt;23.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2024-10&lt;/td&gt;
&lt;td&gt;400,000&lt;/td&gt;
&lt;td&gt;160,000&lt;/td&gt;
&lt;td&gt;130,000&lt;/td&gt;
&lt;td&gt;110,000&lt;/td&gt;
&lt;td&gt;27.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2024-11&lt;/td&gt;
&lt;td&gt;480,000&lt;/td&gt;
&lt;td&gt;192,000&lt;/td&gt;
&lt;td&gt;134,000&lt;/td&gt;
&lt;td&gt;154,000&lt;/td&gt;
&lt;td&gt;32.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2024-12&lt;/td&gt;
&lt;td&gt;500,000&lt;/td&gt;
&lt;td&gt;200,000&lt;/td&gt;
&lt;td&gt;135,000&lt;/td&gt;
&lt;td&gt;165,000&lt;/td&gt;
&lt;td&gt;33.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Those twelve rows add to exactly the annual numbers: $4,000,000 of revenue, $1,600,000 of cost of goods, $1,520,000 of operating expense. Now the two ways to answer "what was our operating margin this year."&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- Wrong. Averages twelve percentages as if each month were the same size.
SELECT ROUND(AVG(100.0 * (revenue - cogs - operating_expense) / revenue), 1)
FROM monthly_pl;                                     -- 18.9

-- Right. Adds the money first, divides once.
SELECT ROUND(100.0 * SUM(revenue - cogs - operating_expense) / SUM(revenue), 1)
FROM monthly_pl;                                     -- 22.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Three point one percentage points apart, from the same twelve rows. On $4,000,000 of revenue that gap is $124,000 of profit that the first query cannot see.&lt;/p&gt;

&lt;p&gt;The reason is that January's revenue of $180,000 and December's $500,000 count equally in an average of percentages, and they are not equal amounts of business. January lost money, at an operating margin of -6.1%, on less than half the revenue of a good month. Averaging lets that small bad month push the answer down as hard as a large good month pushes it up.&lt;/p&gt;

&lt;p&gt;The rule that fixes it: &lt;strong&gt;add the money, then divide once.&lt;/strong&gt; A margin is a ratio, and ratios are not averaged, they are recomputed on the totals. This is the same reasoning that makes a class's overall pass rate different from the average of each teacher's pass rate, and it is worth recognising by name, because it shows up whenever a rate is rolled up from parts of different sizes.&lt;/p&gt;

&lt;p&gt;Picture running that &lt;code&gt;AVG&lt;/code&gt; query on your own company's monthly figures. Do you have a January, a quiet month with the same fixed costs as a busy one? If you do, your averaged margin is already wrong, and it is wrong in the optimistic direction whenever the quiet months are the profitable ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Reading a margin: the four benchmarks
&lt;/h2&gt;

&lt;p&gt;A company reports a 2% net margin. Good or bad?&lt;/p&gt;

&lt;p&gt;Unanswerable, and that is the point. Two percent is healthy for a grocery chain and alarming for a software company. A margin on its own is a number with no verdict attached, and it becomes an analysis only when you put it next to something. There are four somethings, and they answer different questions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Against its own past.&lt;/strong&gt; Same company, earlier period. This is the one that always works, because the company is its own control: same products, same customers, same accounting. Our example gives 17.9% falling to 14.0%, and that comparison needs no outside information at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Against the plan.&lt;/strong&gt; The margin the budget assumed. A miss against plan is a different conversation from a fall against last year, because someone chose the plan number and can be asked why. &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/budget-vs-actual-variance/" rel="noopener noreferrer"&gt;Budget vs actual variance&lt;/a&gt; is the full method for that comparison.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Against the industry.&lt;/strong&gt; Grocery retail runs on net margins of a few percent because it turns over enormous volume at tiny per-item profit. Packaged software can run above 20% because the cost of the next copy is close to nothing. A margin outside its industry's usual band is either a real advantage or an accounting difference, and it is worth finding out which before you present it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Against a specific peer.&lt;/strong&gt; The most useful comparison, and the one that points at a cause. Take two companies with the same gross margin.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Company&lt;/th&gt;
&lt;th&gt;Revenue&lt;/th&gt;
&lt;th&gt;Gross margin&lt;/th&gt;
&lt;th&gt;Operating margin&lt;/th&gt;
&lt;th&gt;Net margin&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ours&lt;/td&gt;
&lt;td&gt;4,000,000&lt;/td&gt;
&lt;td&gt;60.0%&lt;/td&gt;
&lt;td&gt;22.0%&lt;/td&gt;
&lt;td&gt;14.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Peer&lt;/td&gt;
&lt;td&gt;4,000,000&lt;/td&gt;
&lt;td&gt;60.0%&lt;/td&gt;
&lt;td&gt;29.0%&lt;/td&gt;
&lt;td&gt;23.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Identical products, as far as gross margin can tell. Nine and a half points apart at the bottom. The cascade says exactly where to look: the two companies spend $1,520,000 and $1,240,000 on operating expense, a $280,000 gap, and below that our interest and tax take $320,000 against their $220,000. Same product economics, more overhead, more debt. That is a finding, and it took no information beyond two income statements.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. The full before and after
&lt;/h2&gt;

&lt;p&gt;Same company, same two years, two ways of reporting it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before
&lt;/h3&gt;

&lt;p&gt;"Revenue up $1,000,000 to $4,000,000. Gross profit up $510,000. Net income up $22,000 to $560,000. A record year on every line." Every sentence is true. A reader gets no way to tell that the company is keeping less of what it earns, because nothing on the page is measured against anything. The $22,000 improvement even reads as good news, which is the part that costs you credibility three months later.&lt;/p&gt;

&lt;h3&gt;
  
  
  After
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measure&lt;/th&gt;
&lt;th&gt;2023&lt;/th&gt;
&lt;th&gt;2024&lt;/th&gt;
&lt;th&gt;Read&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Revenue&lt;/td&gt;
&lt;td&gt;3,000,000&lt;/td&gt;
&lt;td&gt;4,000,000&lt;/td&gt;
&lt;td&gt;+33.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gross margin&lt;/td&gt;
&lt;td&gt;63.0%&lt;/td&gt;
&lt;td&gt;60.0%&lt;/td&gt;
&lt;td&gt;-3.0 pts, pricing or input costs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operating margin&lt;/td&gt;
&lt;td&gt;25.8%&lt;/td&gt;
&lt;td&gt;22.0%&lt;/td&gt;
&lt;td&gt;-3.8 pts, overhead grew faster than sales&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Net margin&lt;/td&gt;
&lt;td&gt;17.9%&lt;/td&gt;
&lt;td&gt;14.0%&lt;/td&gt;
&lt;td&gt;-3.9 pts, interest doubled to 120,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Net income&lt;/td&gt;
&lt;td&gt;538,000&lt;/td&gt;
&lt;td&gt;560,000&lt;/td&gt;
&lt;td&gt;+4.1%, and $157,333 below last year's margin&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same numbers, plus one division per row. The report now names three separate causes and sizes each one, and the last row converts the whole thing into the only unit a manager acts on, which is dollars. The claim on the top of the email writes itself: &lt;strong&gt;we grew 33.3% and net margin fell 3.9 points, which cost $157,333 against holding last year's rate.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Edge cases that break the cascade
&lt;/h2&gt;

&lt;p&gt;Net margin comes out higher than operating margin. Name something that could do that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A one-off gain below the line.&lt;/strong&gt; Sell a building for $400,000 and net income becomes $960,000 on the same $4,000,000 of revenue. Net margin is 24.0% and operating margin is 22.0%, so the cascade is upside down. Nothing is broken. Money arrived from somewhere that is not the business, and next year it will not. Whenever net sits above operating, look for a sale, a legal settlement, or a tax credit, and say so in the same sentence as the number.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost of goods and operating expense are not defined the same way everywhere.&lt;/strong&gt; Whether shipping, customer support, or the cloud bill sits above or below the gross-profit line is a choice each company makes. Two companies can have genuinely identical economics and gross margins eight points apart. That does not make either wrong, and it does make a gross-margin comparison between two companies weaker than it looks. The comparison inside one company across time is always the safer one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A negative revenue or a zero revenue makes the percent meaningless.&lt;/strong&gt; A month with no sales divides by zero, and depending on the database that is an error or a null. Guard it: &lt;code&gt;CASE WHEN revenue &amp;gt; 0 THEN 100.0 * profit / revenue END&lt;/code&gt;. A blank is honest. A zero is a lie, because 0% margin means you broke even, not that you had no revenue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Points are not percents, and mixing them up is common in writing.&lt;/strong&gt; Net margin fell from 17.9% to 14.0%. That is a fall of 3.9 &lt;em&gt;percentage points&lt;/em&gt;. It is also a fall of about 22% &lt;em&gt;of the margin&lt;/em&gt; , because 3.9 is close to a fifth of 17.9. Both are true, they are wildly different numbers, and only one of them belongs in a sentence at a time. Say "points" when you mean the subtraction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Margins hide mix.&lt;/strong&gt; A company selling two products at 80% and 40% gross margin has an overall gross margin somewhere in between, and that overall number moves whenever the sales mix moves, even if neither product changed at all. If your gross margin dropped and no price changed, check whether you simply sold more of the cheaper thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this works
&lt;/h2&gt;

&lt;p&gt;The rule in section five, add the money then divide once, is not a style preference. Combining groups of different sizes can reverse the direction of a rate, an effect first set out formally by Simpson (Simpson, 1951, &lt;em&gt;Journal of the Royal Statistical Society, Series B&lt;/em&gt; , 13(2), 238–241) and traced to Yule half a century earlier. Blyth's later treatment gives the plainest statement of the danger: an association that holds in every subgroup can vanish or flip when the subgroups are pooled (Blyth, 1972, &lt;em&gt;Journal of the American Statistical Association&lt;/em&gt; , 67(338), 364–366). Averaging twelve monthly margins pools twelve groups of very different sizes while pretending they are equal, which is exactly the condition those papers warn about, and the 3.1-point gap on this page is the mild version of it.&lt;/p&gt;

&lt;p&gt;The question at the top of each section is not decoration. Attempting an answer before you are given one improves learning of that specific material, an effect measured across sixty-four studies of self-explanation prompts (Bisra, Liu, Nesbit, Salimi, &amp;amp; Winne, 2018, &lt;em&gt;Educational Psychology Review&lt;/em&gt; , 30(3), 703–725). Guessing what &lt;code&gt;2400000 / 4000000&lt;/code&gt; returns before section four told you is why you will still remember it next month. The same evidence says the cheat sheet below works best covered up and recalled rather than reread (Roediger &amp;amp; Karpicke, 2006, &lt;em&gt;Psychological Science&lt;/em&gt; , 17(3), 249–255).&lt;/p&gt;

&lt;h2&gt;
  
  
  Using this on your own numbers
&lt;/h2&gt;

&lt;p&gt;Retrofitting margins onto every report you have inherited is a slog, and the old ones have owners who like them. Do this instead, in order.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add three columns to the report you already own.&lt;/strong&gt; Gross, operating and net margin next to the dollars. Three divisions, and the dollars stay exactly where they are so nobody has to relearn the page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the division is decimal.&lt;/strong&gt; Run one row by hand. If a margin comes back as 0, 60 or 1, you have integer division, and section four has the fix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put last year's margin in the next column.&lt;/strong&gt; This is the comparison that always works and it needs no outside data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search your codebase for&lt;code&gt;AVG(&lt;/code&gt; next to a division.&lt;/strong&gt; Every one of those is a rate being averaged, and each one needs checking against the sum-then-divide version. Fix the ones that disagree by more than a rounding step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Convert one margin change into dollars every cycle.&lt;/strong&gt; Last year's rate times this year's revenue, minus this year's actual. That single number is what makes a manager act.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write the industry band down once, somewhere permanent.&lt;/strong&gt; Your own sector's usual range, in a comment at the top of the query. Six months from now nobody will remember whether 14% was good.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you have paper nearby, one optional drawing locks the core idea in. Draw the staircase from the top of this page from memory: four bars measured from the same left edge, each shorter than the one above, with the removed cost hatched beside it. Label the three layers you strip. If your bars come out the same length, or in the wrong order, the page is still open.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This page is one of five.&lt;/strong&gt; The Financial analysis set on the &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/" rel="noopener noreferrer"&gt;guides index&lt;/a&gt; is this page plus &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/liquidity-and-leverage-ratios/" rel="noopener noreferrer"&gt;Current Ratio vs Quick Ratio&lt;/a&gt;, &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/contribution-margin-break-even/" rel="noopener noreferrer"&gt;Contribution Margin and Break-Even&lt;/a&gt;, &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/net-present-value-npv/" rel="noopener noreferrer"&gt;Net Present Value (NPV)&lt;/a&gt; and &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/budget-vs-actual-variance/" rel="noopener noreferrer"&gt;Budget vs Actual Variance&lt;/a&gt;. They share a worked company, so the numbers carry across. The index also holds every other how-to: SQL, Excel, Tableau, and the working habits around them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The whole thing on one screen
&lt;/h2&gt;

&lt;p&gt;Cover the right column and work down the left. Saying each answer out loud before you look is what makes it stick.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Margin&lt;/td&gt;
&lt;td&gt;A profit line divided by revenue. Cents kept per sales dollar.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kept, on paper&lt;/td&gt;
&lt;td&gt;A 14% margin is not 14 cents in the bank. Unpaid customers still count as profit.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Why divide&lt;/td&gt;
&lt;td&gt;Profit dollars grow with size. A margin survives a change in size.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gross margin&lt;/td&gt;
&lt;td&gt;(Revenue − cost of goods) ÷ revenue. The product's own economics.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operating margin&lt;/td&gt;
&lt;td&gt;Operating income ÷ revenue. The core business, before financing and tax.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Net margin&lt;/td&gt;
&lt;td&gt;Net income ÷ revenue. What is left after absolutely everything.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The cascade rule&lt;/td&gt;
&lt;td&gt;Gross ≥ operating ≥ net. Each strips one more layer of cost.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reading the cascade&lt;/td&gt;
&lt;td&gt;The first margin that stops looking healthy names the layer to investigate.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The integer trap&lt;/td&gt;
&lt;td&gt;2400000 / 4000000 returns 0. Put &lt;code&gt;100.0 *&lt;/code&gt; at the front, before the division.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Why ROUND does not help&lt;/td&gt;
&lt;td&gt;The 0 already happened. ROUND(0, 1) is 0.0 and looks fine.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The averaging trap&lt;/td&gt;
&lt;td&gt;AVG of twelve monthly margins gave 18.9%. The truth was 22.0%.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The rule that fixes it&lt;/td&gt;
&lt;td&gt;Add the money, then divide once. SUM(profit) / SUM(revenue).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Four benchmarks&lt;/td&gt;
&lt;td&gt;Its own past, the plan, the industry, a named peer.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Net above operating&lt;/td&gt;
&lt;td&gt;Money arrived from outside the business. Find it and say so.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Points vs percent&lt;/td&gt;
&lt;td&gt;17.9 to 14.0 is 3.9 points, and also about 22% of the margin. Pick one.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mix&lt;/td&gt;
&lt;td&gt;Selling more of the cheaper product moves the blended margin with no price change.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The deliverable&lt;/td&gt;
&lt;td&gt;One sentence: the margin, the direction, and what it cost in dollars.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The one habit to keep.&lt;/strong&gt; One habit, if you keep only one. Never average a rate. Add the numerators, add the denominators, divide once. That one habit covers margins, conversion rates, defect rates, retention and every other percentage you will ever roll up. If a query fights back in a way this page does not cover, there is a general &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/technical-tenacity/" rel="noopener noreferrer"&gt;diagnosis loop for being stuck&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One last thought, and I would genuinely like other people's answers. The first margin I ever reported was averaged across months, and it was wrong by about three points for four straight quarters before anyone checked. Nobody caught it because it was in the right neighbourhood every single time. What is the wrongest number you have shipped that survived because it looked reasonable?&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Simpson, E. H. (1951). The interpretation of interaction in contingency tables. &lt;em&gt;Journal of the Royal Statistical Society, Series B&lt;/em&gt; , 13(2), 238–241.&lt;/li&gt;
&lt;li&gt;Blyth, C. R. (1972). On Simpson's paradox and the sure-thing principle. &lt;em&gt;Journal of the American Statistical Association&lt;/em&gt; , 67(338), 364–366.&lt;/li&gt;
&lt;li&gt;Bisra, K., Liu, Q., Nesbit, J. C., Salimi, F., &amp;amp; Winne, P. H. (2018). Inducing self-explanation: A meta-analysis. &lt;em&gt;Educational Psychology Review&lt;/em&gt; , 30(3), 703–725.&lt;/li&gt;
&lt;li&gt;Roediger, H. L., &amp;amp; Karpicke, J. D. (2006). Test-enhanced learning: Taking memory tests improves long-term retention. &lt;em&gt;Psychological Science&lt;/em&gt; , 17(3), 249–255.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on Analyst Prep Kit: &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/gross-vs-operating-vs-net-margin/" rel="noopener noreferrer"&gt;Gross vs Operating vs Net Margin: What Each One Tells You&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Visit the site for more beginner data analysis guides and free resources: &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/" rel="noopener noreferrer"&gt;the full guide archive&lt;/a&gt; covers SQL, Excel, Power BI, Tableau, Python and statistics, and the &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/" rel="noopener noreferrer"&gt;practice kits&lt;/a&gt; run in your browser with nothing to install.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If it was useful: &lt;a href="https://buymeacoffee.com/michaelnocito" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dataanalysis</category>
      <category>career</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Contribution Margin and Break-Even: The Fixed Cost That Does Not Belong in the Unit</title>
      <dc:creator>Michael Nocito</dc:creator>
      <pubDate>Sun, 06 Sep 2026 13:00:31 +0000</pubDate>
      <link>https://dev.to/michaelnocito/contribution-margin-and-break-even-the-fixed-cost-that-does-not-belong-in-the-unit-3lbn</link>
      <guid>https://dev.to/michaelnocito/contribution-margin-and-break-even-the-fixed-cost-that-does-not-belong-in-the-unit-3lbn</guid>
      <description>&lt;p&gt;By &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/about.html" rel="noopener noreferrer"&gt;Michael Nocito&lt;/a&gt;, data analyst · Published August 11, 2026&lt;/p&gt;

&lt;p&gt;By the end of this page you can answer three questions that come up constantly and get answered wrong: does one more sale make money, how many sales cover the fixed base, and what a discount actually costs. You will know the contribution margin formula, the break-even formula, the SQL for both, and the one mistake that turns a 750-unit answer into a 12,000-unit answer.&lt;/p&gt;

&lt;p&gt;Start here, on one product, today. Take one product and split its costs into two piles: costs that happen because you sold that unit, and costs that would have happened anyway. Subtract only the first pile from the price. That number is what each sale is really worth, and almost nobody has it to hand.&lt;/p&gt;

&lt;p&gt;Two formulas, and that is the lot. Contribution margin is price minus variable cost. Divide the fixed costs by it and you get break-even, the number of units where the business stops losing money.&lt;/p&gt;

&lt;p&gt;Where the fixed costs sit is the thing everybody gets wrong, so it gets the picture.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The original carries a diagram here. In words: Two panels, each showing a single tall bar that represents the selling price of one unit, split into stacked segments. In the left panel, marked with a check, the price bar has just two segments: a hatched grey lower segment labeled variable, taking up sixty percent of the height, and a solid accent-colored upper segment labeled contribution, taking up the other forty percent. To the right of that bar, and deliberately not touching it, sits a separate wide low box with a dashed outline, labeled fixed. It is drawn wide and short so it reads as a container for a month of overhead rather than as anything belonging to one unit. An arrow runs from the contribution segment down into that box, and the bottom of the box is partly filled with small solid tiles, each one the same color as the contribution segment. The tiles say that the contribution from each sale is what fills the fixed box, and that filling it up is what break-even means. In the right panel, marked with a cross, the same price bar has been divided three ways instead of two: the same hatched variable segment at the bottom, then a segment above it labeled fixed share drawn with the same dashed outline as the fixed box in the left panel, so the eye can see it is that box carved up and pushed inside the unit, and finally a solid accent-colored sliver at the very top that is only a sixteenth as tall as the contribution segment in the left panel. There is no separate fixed box standing beside the right-hand bar, because it has been dissolved into it. The picture shows that the same product yields a large contribution or a tiny one depending only on whether the fixed cost was allowed inside the bar.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Every number on this page is verified, and you can check them.&lt;/strong&gt; The worked example is one product's month, six inputs, shown in full below. Every contribution figure, break-even, discount scenario and leverage number was computed in SQLite and cross-checked in pandas before it went on the page, so you can check any line on a calculator and it will agree.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Fixed and variable, and the test that sorts them
&lt;/h2&gt;

&lt;p&gt;Your factory rent is $60,000 a month. You sell one more grinder. How much more rent do you pay?&lt;/p&gt;

&lt;p&gt;None. That is the whole test, and it is the only test that matters here. &lt;strong&gt;Ask what happens to the cost if you sell exactly one more unit.&lt;/strong&gt; If the cost goes up, it is variable. If it does not move, it is fixed.&lt;/p&gt;

&lt;p&gt;Variable costs on a grinder: the parts, the packaging, the payment processing fee, the shipping, the piece of assembly labour that only happens because this unit exists. Fixed costs: the rent, the salaried staff, the software subscriptions, the insurance. Those arrive whether you sell nine hundred units or none.&lt;/p&gt;

&lt;p&gt;Two things make this harder than it sounds, and both are worth knowing before you sort a real cost list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fixed only holds inside a range.&lt;/strong&gt; Rent is fixed until you need a second warehouse. A salaried team is fixed until the volume needs another hire. Accountants call that range the relevant range, and the honest version of "fixed" is "fixed across the volumes we are actually considering." Break-even arithmetic is only valid inside it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Some costs are both.&lt;/strong&gt; A phone bill with a monthly line rental and a per-minute charge is part fixed, part variable. A salesperson on base plus commission is the same shape. Split those two ways rather than forcing them into one pile, and if you cannot split them, say which way you rounded.&lt;/p&gt;

&lt;p&gt;What makes this split worth the trouble is that it answers a question the income statement cannot. A profit and loss account sorts costs by what they are: cost of goods, then operating expense. That tells you nothing about what one more sale does, because both of those buckets contain a mix of fixed and variable. Sorting by behaviour instead of by category is the entire move.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Contribution margin, per unit and as a ratio
&lt;/h2&gt;

&lt;p&gt;A grinder sells for $200 and costs $120 in parts, packaging and shipping. How much of that $200 is left to pay the rent?&lt;/p&gt;

&lt;p&gt;Eighty dollars. That is the contribution margin: price minus variable cost, per unit. The name says exactly what it is. It is the amount each sale &lt;em&gt;contributes&lt;/em&gt; toward the fixed costs, and once those are covered, toward profit.&lt;/p&gt;

&lt;p&gt;Notice what it is not. It is not profit per unit. There is no such thing as profit per unit until you know how many units there are, because the fixed costs have to be paid out of the pile of contributions and the size of the pile depends on the count. Contribution margin is the only per-unit number that is true at every volume, which is why it is the one to build on.&lt;/p&gt;

&lt;p&gt;The same thing as a percentage is the contribution margin ratio: contribution divided by price. Here that is 80 divided by 200, which is 40%. Forty cents of every sales dollar survives the variable costs.&lt;/p&gt;

&lt;p&gt;Use the per-unit version when you are counting units and the ratio when you are working in revenue. A sales manager forecasting units wants $80. A finance director looking at a revenue plan wants 40%, because 40% of any revenue number is the contribution that revenue throws off.&lt;/p&gt;

&lt;p&gt;Here is the whole product, and everything else on this page comes out of these six numbers.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Price per unit&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Variable cost per unit&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fixed costs per month&lt;/td&gt;
&lt;td&gt;60,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Units sold per month&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Contribution margin per unit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;80&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Contribution margin ratio&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;40.0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  3. Break-even, and the worked model
&lt;/h2&gt;

&lt;p&gt;Fixed costs are $60,000 a month and each unit contributes $80. Work out how many units cover the fixed costs before you read on.&lt;/p&gt;

&lt;p&gt;Seven hundred and fifty. Break-even in units is fixed costs divided by contribution margin per unit: 60,000 divided by 80. Each sale drops $80 into the fixed-cost box, so it takes 750 of them to fill a $60,000 box, and unit 751 is the first one that adds profit.&lt;/p&gt;

&lt;p&gt;Break-even in revenue is the same answer in dollars, and there are two ways to get it. Multiply 750 units by the $200 price to get $150,000. Or divide the fixed costs by the contribution ratio: 60,000 divided by 0.40, which is also $150,000. Use the second one when you do not have a single price, because a product line with mixed prices still has a blended contribution ratio.&lt;/p&gt;

&lt;p&gt;Check it by building the month at 750 units. Revenue is 750 times 200, which is $150,000. Variable cost is 750 times 120, which is $90,000. Add the $60,000 of fixed cost and total cost is $150,000. Revenue minus total cost is exactly zero, which is what break-even means.&lt;/p&gt;

&lt;p&gt;Now the real month, at 800 units.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Line&lt;/th&gt;
&lt;th&gt;Amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Revenue (800 × 200)&lt;/td&gt;
&lt;td&gt;160,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Variable cost (800 × 120)&lt;/td&gt;
&lt;td&gt;96,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Contribution (800 × 80)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;64,000&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fixed costs&lt;/td&gt;
&lt;td&gt;60,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Profit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4,000&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The gap between 800 units and 750 is the margin of safety: 50 units, or 6.25% of current volume. That is how far sales can fall before the product starts losing money, and 6.25% is thin. Say why a product can be profitable and still be 6.25% away from losing money, in your own words, before reading on.&lt;/p&gt;

&lt;p&gt;Here is the same month at other volumes, which is the table worth putting in front of anyone who asks "what if sales drop."&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Units&lt;/th&gt;
&lt;th&gt;Revenue&lt;/th&gt;
&lt;th&gt;Variable cost&lt;/th&gt;
&lt;th&gt;Fixed cost&lt;/th&gt;
&lt;th&gt;Total cost&lt;/th&gt;
&lt;th&gt;Profit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;60,000&lt;/td&gt;
&lt;td&gt;60,000&lt;/td&gt;
&lt;td&gt;-60,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;40,000&lt;/td&gt;
&lt;td&gt;24,000&lt;/td&gt;
&lt;td&gt;60,000&lt;/td&gt;
&lt;td&gt;84,000&lt;/td&gt;
&lt;td&gt;-44,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;80,000&lt;/td&gt;
&lt;td&gt;48,000&lt;/td&gt;
&lt;td&gt;60,000&lt;/td&gt;
&lt;td&gt;108,000&lt;/td&gt;
&lt;td&gt;-28,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;600&lt;/td&gt;
&lt;td&gt;120,000&lt;/td&gt;
&lt;td&gt;72,000&lt;/td&gt;
&lt;td&gt;60,000&lt;/td&gt;
&lt;td&gt;132,000&lt;/td&gt;
&lt;td&gt;-12,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;750&lt;/td&gt;
&lt;td&gt;150,000&lt;/td&gt;
&lt;td&gt;90,000&lt;/td&gt;
&lt;td&gt;60,000&lt;/td&gt;
&lt;td&gt;150,000&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;td&gt;160,000&lt;/td&gt;
&lt;td&gt;96,000&lt;/td&gt;
&lt;td&gt;60,000&lt;/td&gt;
&lt;td&gt;156,000&lt;/td&gt;
&lt;td&gt;4,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;200,000&lt;/td&gt;
&lt;td&gt;120,000&lt;/td&gt;
&lt;td&gt;60,000&lt;/td&gt;
&lt;td&gt;180,000&lt;/td&gt;
&lt;td&gt;20,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,200&lt;/td&gt;
&lt;td&gt;240,000&lt;/td&gt;
&lt;td&gt;144,000&lt;/td&gt;
&lt;td&gt;60,000&lt;/td&gt;
&lt;td&gt;204,000&lt;/td&gt;
&lt;td&gt;36,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every row moves by $80 per unit, in both directions. That is what makes the numbers around break-even feel so violent. Ten percent more units, from 800 to 880, takes profit from $4,000 to $10,400, which is 160% more profit for 10% more sales. Ten percent fewer, from 800 to 720, takes it to a $2,400 loss, which is 160% the other way.&lt;/p&gt;

&lt;p&gt;That multiplier has a name and a formula. Operating leverage is contribution divided by profit: 64,000 over 4,000, which is 16. One percent on volume moves profit sixteen percent, and it does it in whichever direction the volume went. High fixed costs buy you that multiplier. It is the same multiplier on the way down.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The 12,000-unit mistake
&lt;/h2&gt;

&lt;p&gt;Accounting tells you this grinder costs $195 to make, fully loaded. It sells for $200. Work out what that says the break-even is, then hold that number.&lt;/p&gt;

&lt;p&gt;Here is where the $195 comes from. Fixed costs of $60,000 spread across the 800 units the product currently sells is $75 a unit. Add that to the $120 of genuine variable cost and you get $195. That is a real and legitimate number for some purposes, and it is called fully loaded or fully absorbed cost, because the fixed costs have been absorbed into the unit.&lt;/p&gt;

&lt;p&gt;Use it in a break-even calculation and everything falls apart. Price 200 minus loaded cost 195 leaves $5 of apparent margin. Divide $60,000 of fixed costs by $5 and break-even is 12,000 units.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Correct&lt;/th&gt;
&lt;th&gt;Fully loaded&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cost subtracted from price&lt;/td&gt;
&lt;td&gt;120 variable only&lt;/td&gt;
&lt;td&gt;195, variable plus fixed share&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Margin per unit&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Break-even units&lt;/td&gt;
&lt;td&gt;750&lt;/td&gt;
&lt;td&gt;12,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Sixteen times the right answer, and 12,000 units is fifteen times the volume the product actually sells. A manager handed that number kills the product. The product is fine. It made $4,000 last month.&lt;/p&gt;

&lt;p&gt;The reason it goes so wrong is that the fixed costs get counted twice. They are inside the $195, taking $75 off every unit's margin, and they are still sitting in the $60,000 numerator waiting to be covered. One pile of money, subtracted on both sides of the same division.&lt;/p&gt;

&lt;p&gt;There is a second problem underneath, and it is the one that makes the mistake impossible to patch. The loaded cost depends on the volume you assumed to compute it, and volume is what you were trying to find. Watch the answer move.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Volume assumed&lt;/th&gt;
&lt;th&gt;Fixed per unit&lt;/th&gt;
&lt;th&gt;Loaded cost&lt;/th&gt;
&lt;th&gt;"Break-even" it produces&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;150.00&lt;/td&gt;
&lt;td&gt;270.00&lt;/td&gt;
&lt;td&gt;Impossible, cost exceeds the $200 price&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;td&gt;75.00&lt;/td&gt;
&lt;td&gt;195.00&lt;/td&gt;
&lt;td&gt;12,000 units&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,200&lt;/td&gt;
&lt;td&gt;50.00&lt;/td&gt;
&lt;td&gt;170.00&lt;/td&gt;
&lt;td&gt;2,000 units&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three different break-evens for one product, decided entirely by a number you had to guess first. The correct calculation gives 750 at every volume, because contribution margin does not depend on how many units there are. That is not a convenience. It is the reason the split exists.&lt;/p&gt;

&lt;p&gt;Picture your own company's cost-per-unit figure, the one in the pricing spreadsheet or the ERP. Does it have overhead baked into it? Most of them do, because that number was built for valuing inventory, not for deciding what one more sale is worth. Using it for the second job is the most common version of this mistake.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The query, and why break-even rounds up
&lt;/h2&gt;

&lt;p&gt;Fixed costs $60,000, contribution $79 per unit. Divide them. What do you do with the 0.49 of a unit at the end?&lt;/p&gt;

&lt;p&gt;Round up, always. You cannot sell half a grinder, and a fraction of a unit short of break-even is still short. 60,000 divided by 79 is 759.4937, and 759 units is not break-even. It generates 759 times 79, which is $59,961 of contribution, against $60,000 of fixed cost. The month loses $39. It takes 760 units, which throw off $60,040 and clear the base by $40.&lt;/p&gt;

&lt;p&gt;SQL will not do that for you. Whole number divided by whole number truncates, so &lt;code&gt;60000 / 79&lt;/code&gt; returns 759, which is the wrong direction. &lt;code&gt;CAST(... AS INT)&lt;/code&gt; also truncates. The function that rounds up is &lt;code&gt;CEIL&lt;/code&gt;, spelled &lt;code&gt;CEILING&lt;/code&gt; in SQL Server, and some older SQLite builds ship without it.&lt;/p&gt;

&lt;p&gt;Here is the query, with a ceiling built from the remainder rather than from &lt;code&gt;CEIL&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT product,
       price,
       variable_cost,
       price - variable_cost                             AS cm_per_unit,
       ROUND(100.0 * (price - variable_cost) / price, 1) AS cm_ratio_pct,
       fixed_costs / (price - variable_cost)
         + CASE WHEN fixed_costs % (price - variable_cost) &amp;gt; 0
                THEN 1 ELSE 0 END                        AS breakeven_units
FROM product_economics;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The last expression is worth reading slowly, because it is a small trick that saves an argument later. &lt;code&gt;fixed_costs / (price - variable_cost)&lt;/code&gt; is the truncated whole number, 759. &lt;code&gt;fixed_costs % (price - variable_cost)&lt;/code&gt; is the remainder left over, and the &lt;code&gt;CASE&lt;/code&gt; adds 1 when there is a remainder and 0 when there is not. Add them and you have rounded up, with no &lt;code&gt;CEIL&lt;/code&gt; and no floating-point arithmetic anywhere. On a division that comes out exact, like 60,000 over 80, the remainder is 0 and the answer stays 750.&lt;/p&gt;

&lt;p&gt;You will also see this written as &lt;code&gt;fixed_costs / cm + (fixed_costs % cm &amp;gt; 0)&lt;/code&gt;, leaning on the comparison itself being 1 or 0. That is shorter and it is not portable. It runs in SQLite and MySQL, where a comparison is a number. PostgreSQL has a real true-or-false type that will not add to an integer, and SQL Server has no such value to add at all. Oracle spells the remainder &lt;code&gt;MOD(fixed_costs, cm)&lt;/code&gt; rather than &lt;code&gt;%&lt;/code&gt;. The &lt;code&gt;CASE&lt;/code&gt; above is the version that survives the move, which is why it is the one printed here.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;100.0 *&lt;/code&gt; in the ratio line is the other arithmetic guard. Without the decimal point, &lt;code&gt;100 * 80 / 200&lt;/code&gt; is fine but &lt;code&gt;80 / 200 * 100&lt;/code&gt; is 0, because the division happens first and 80 divided by 200 truncates to zero. The same trap runs through every ratio in SQL and is worked at length in &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/gross-vs-operating-vs-net-margin/" rel="noopener noreferrer"&gt;gross vs operating vs net margin&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. What a 10% discount actually costs
&lt;/h2&gt;

&lt;p&gt;You cut the price 10%, from $200 to $180. By what percent does the contribution fall?&lt;/p&gt;

&lt;p&gt;Twenty-five percent. The discount comes entirely out of the contribution, because the variable cost does not care what you charged. Contribution goes from $80 to $60, and $20 off $80 is a quarter of it.&lt;/p&gt;

&lt;p&gt;This is the most useful thing on the page for anyone who sits near a sales team. &lt;strong&gt;A discount is not a small percentage off the price. It is a large percentage off the only part of the price that was yours.&lt;/strong&gt; The lower the contribution ratio, the more brutal it gets. At a 40% contribution ratio, a 10% discount takes 25% of the contribution. At a 20% ratio it would take half.&lt;/p&gt;

&lt;p&gt;What that does downstream, on the same product.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measure&lt;/th&gt;
&lt;th&gt;At $200&lt;/th&gt;
&lt;th&gt;At $180&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Contribution per unit&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;-25%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contribution ratio&lt;/td&gt;
&lt;td&gt;40.0%&lt;/td&gt;
&lt;td&gt;33.3%&lt;/td&gt;
&lt;td&gt;-6.7 points&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Break-even units&lt;/td&gt;
&lt;td&gt;750&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;+33%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Units to hold the same $4,000 profit&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;td&gt;1,067&lt;/td&gt;
&lt;td&gt;+33%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read the last row again, because it is the sentence to take into the meeting: &lt;strong&gt;a 10% discount needs 33% more units just to make the same money.&lt;/strong&gt; Check it if you like. 1,067 units at $60 of contribution is $64,020, minus $60,000 of fixed cost, which is $4,020, near enough the $4,000 the product made before.&lt;/p&gt;

&lt;p&gt;That is the number that should sit next to any discount proposal. Not "we lose 10% of revenue," which is what a price cut looks like on a revenue line, but "we need a third more volume to stand still," which is what it looks like in the only unit that matters. If the sales team is confident of a third more volume, the discount is a good idea. Often the discussion has never been framed that way, and this table is the whole contribution of the analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. The full before and after
&lt;/h2&gt;

&lt;p&gt;Same product, same month, two ways of reporting it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before
&lt;/h3&gt;

&lt;p&gt;"The Pro Grinder made $4,000 last month on 800 units. Unit cost is $195 against a $200 price, so we are making $5 a unit. Margin is too thin; recommend a price rise or a product review." Every number in that paragraph is arithmetically correct. The recommendation is built on the fully loaded $195, so it double-counts the fixed costs, and it puts a product with a 40% contribution ratio on a kill list.&lt;/p&gt;

&lt;h3&gt;
  
  
  After
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measure&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Read&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Price&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Variable cost&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;Parts, packaging, shipping, processing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contribution per unit&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;What one more sale is worth&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contribution ratio&lt;/td&gt;
&lt;td&gt;40.0%&lt;/td&gt;
&lt;td&gt;Of every sales dollar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fixed costs&lt;/td&gt;
&lt;td&gt;60,000&lt;/td&gt;
&lt;td&gt;Per month, unchanged by volume&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Break-even&lt;/td&gt;
&lt;td&gt;750 units, 150,000 revenue&lt;/td&gt;
&lt;td&gt;Where the month turns positive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Current volume&lt;/td&gt;
&lt;td&gt;800 units&lt;/td&gt;
&lt;td&gt;Profit 4,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Margin of safety&lt;/td&gt;
&lt;td&gt;50 units, 6.25%&lt;/td&gt;
&lt;td&gt;How far sales can fall&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operating leverage&lt;/td&gt;
&lt;td&gt;16.0&lt;/td&gt;
&lt;td&gt;1% on volume moves profit 16%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The same month, and now a completely different recommendation. The product is not thin on margin, it is thin on volume, and those need opposite responses. Its contribution ratio of 40% is healthy. It is sitting 50 units above break-even, and its leverage of 16 means an extra 50 units would double the profit while 50 fewer would erase it. The finding: &lt;strong&gt;the Pro Grinder is 6.25% of volume away from losing money and 40 cents of every dollar goes to profit once it clears, so this is a volume problem, not a pricing one.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Edge cases, including the order that looks like a loss
&lt;/h2&gt;

&lt;p&gt;A customer offers to buy 150 grinders at $140 each, well under the $195 it "costs" to make one. Take it or leave it?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Take it, on these numbers.&lt;/strong&gt; The variable cost is $120, so each of those units contributes $20. On 150 units that is $3,000 of extra contribution, and because the fixed costs are already covered by the regular 800 units, all $3,000 is profit. The month goes from $4,000 to $7,000. The floor on any special order is the variable cost, $120, and everything above it is worth having. This is the single most valuable use of contribution margin, and it is unavailable to anyone working from a loaded unit cost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The conditions on that answer are real and they matter.&lt;/strong&gt; It holds only if the discounted order does not eat capacity you would have sold at full price, and only if the low price does not leak into your regular customers' expectations. Both of those are judgement calls rather than arithmetic, and both belong in the same email as the $3,000.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Break-even is a monthly question or a yearly one, never a floating one.&lt;/strong&gt; $60,000 of fixed cost is per month here, so 750 units is a monthly break-even. Mixing an annual fixed cost with a monthly volume is a twelvefold error and it is easy to make when the two numbers come from different systems. Put the period in the column header.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multiple products need a weighted contribution.&lt;/strong&gt; Break-even for a range of products depends on the mix, because a unit is not a unit any more. Use the blended contribution ratio and the revenue version of the formula, and state the mix you assumed, because the answer changes when the mix does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contribution margin is not gross margin.&lt;/strong&gt; They look similar and they are cut differently. Gross margin subtracts cost of goods sold, which usually contains some fixed manufacturing overhead. Contribution margin subtracts only what varies with the unit. Two numbers, two purposes, and using one where the other belongs is exactly the mistake in section four wearing better clothes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A negative contribution margin is a different conversation entirely.&lt;/strong&gt; If the price is below the variable cost, every sale makes the loss bigger, and there is no volume that fixes it. Break-even does not exist, the formula returns a negative or an error, and the answer is to change the price or stop selling it. Guard the division so the report says that in words rather than printing a negative unit count.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this works
&lt;/h2&gt;

&lt;p&gt;The split between fixed and variable is not just an accounting convention, it is a decision rule with a formal basis. Charnes, Cooper and Ijiri set out break-even and cost-volume-profit analysis as a constrained optimisation problem and showed the conditions the linear version depends on (Charnes, Cooper, &amp;amp; Ijiri, 1963, &lt;em&gt;Journal of Accounting Research&lt;/em&gt; , 1(1), 16–43). Their conditions are the relevant range from section one, stated precisely: the arithmetic holds while the cost behaviour holds, and not outside it.&lt;/p&gt;

&lt;p&gt;The reason the loaded unit cost in section four is so persistent has been measured too. Cooper and Kaplan documented how absorption systems, built to value inventory for external reporting, get reused for pricing and product decisions they were never designed for, and produce exactly the distortion on this page (Cooper &amp;amp; Kaplan, 1988, &lt;em&gt;Accounting Horizons&lt;/em&gt; , 2(3), 61–66). The number is not wrong. It is answering a different question, and nothing on the report says so.&lt;/p&gt;

&lt;p&gt;The question at the top of each section is deliberate. Attempting an answer before receiving one improves learning of that specific material across sixty-four studies (Bisra, Liu, Nesbit, Salimi, &amp;amp; Winne, 2018, &lt;em&gt;Educational Psychology Review&lt;/em&gt; , 30(3), 703–725). Working out 60,000 divided by 80 yourself, before section three gave you 750, is why the formula will come back when you need it. The same evidence says the cheat sheet below works best covered and recalled rather than reread (Roediger &amp;amp; Karpicke, 2006, &lt;em&gt;Psychological Science&lt;/em&gt; , 17(3), 249–255).&lt;/p&gt;

&lt;h2&gt;
  
  
  Using this on your own numbers
&lt;/h2&gt;

&lt;p&gt;Re-sorting every cost in the business into two piles is a project, and it is not one anyone will fund. Do this instead, in order.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pick one product and sort only its costs.&lt;/strong&gt; Two columns, fixed and variable, and the one-more-unit test from section one to decide each line. An hour on one product beats a quarter on all of them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check whether your existing unit cost has overhead in it.&lt;/strong&gt; Ask whoever owns it. If the answer is yes, or nobody knows, you cannot use it for contribution margin and section four is why.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compute contribution per unit and the ratio.&lt;/strong&gt; Two numbers, and they are the two that answer every question in this article.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put break-even and the margin of safety on the product's regular report.&lt;/strong&gt; Break-even alone is a fact. Break-even next to current volume is a warning system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Round break-even up, and check it.&lt;/strong&gt; Multiply your answer by the contribution and confirm it clears the fixed costs. Section five has the ceiling to use when your database has no &lt;code&gt;CEIL&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build the discount table before the next pricing conversation.&lt;/strong&gt; Contribution, break-even, and units needed to hold profit, at the current price and the proposed one. It is four numbers and it changes how the meeting goes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If there is paper nearby, draw this once and it stays. Draw the two bars from the top of this page from memory: a price bar split into variable and contribution with the fixed box standing separately beside it, then the same bar with the fixed share pushed inside and the contribution reduced to a sliver. Mark which one is right. If you draw the fixed box touching the bar, the page is still open.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This page is one of five.&lt;/strong&gt; The Financial analysis set on the &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/" rel="noopener noreferrer"&gt;guides index&lt;/a&gt; is this page plus &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/gross-vs-operating-vs-net-margin/" rel="noopener noreferrer"&gt;Gross vs Operating vs Net Margin&lt;/a&gt;, &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/liquidity-and-leverage-ratios/" rel="noopener noreferrer"&gt;Current Ratio vs Quick Ratio&lt;/a&gt;, &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/net-present-value-npv/" rel="noopener noreferrer"&gt;Net Present Value (NPV)&lt;/a&gt; and &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/budget-vs-actual-variance/" rel="noopener noreferrer"&gt;Budget vs Actual Variance&lt;/a&gt;. They share a worked company, so the numbers carry across. The index also holds every other how-to: SQL, Excel, Tableau, and the working habits around them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The whole thing on one screen
&lt;/h2&gt;

&lt;p&gt;Cover the right column, then work down. Answer before you look, especially where you are sure.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The sorting test&lt;/td&gt;
&lt;td&gt;Sell one more unit. If the cost moves, it is variable.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Relevant range&lt;/td&gt;
&lt;td&gt;Fixed only holds across the volumes you are actually considering.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contribution margin&lt;/td&gt;
&lt;td&gt;Price minus variable cost. 200 − 120 = $80 per unit.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contribution ratio&lt;/td&gt;
&lt;td&gt;Contribution ÷ price. 80 ÷ 200 = 40%.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Not profit per unit&lt;/td&gt;
&lt;td&gt;There is no profit per unit until you know the count.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Break-even units&lt;/td&gt;
&lt;td&gt;Fixed ÷ contribution per unit. 60,000 ÷ 80 = 750.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Break-even revenue&lt;/td&gt;
&lt;td&gt;Fixed ÷ contribution ratio. 60,000 ÷ 0.40 = $150,000.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Margin of safety&lt;/td&gt;
&lt;td&gt;Current volume minus break-even. 800 − 750 = 50 units, 6.25%.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operating leverage&lt;/td&gt;
&lt;td&gt;Contribution ÷ profit. 64,000 ÷ 4,000 = 16. Cuts both ways.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The loaded-cost error&lt;/td&gt;
&lt;td&gt;Putting fixed inside the unit gave 12,000 units instead of 750.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Why it is 16x wrong&lt;/td&gt;
&lt;td&gt;The fixed costs are counted twice, once in the unit and once in the total.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Why it cannot be patched&lt;/td&gt;
&lt;td&gt;The loaded cost depends on the volume you were trying to find.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rounding&lt;/td&gt;
&lt;td&gt;Break-even rounds up. 759 units leaves the month $39 short.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The ceiling with no CEIL&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;fixed/cm + CASE WHEN fixed % cm &amp;gt; 0 THEN 1 ELSE 0 END&lt;/code&gt;. The shorter version without the CASE only runs in SQLite and MySQL.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The discount rule&lt;/td&gt;
&lt;td&gt;10% off the price took 25% off the contribution.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What a discount needs&lt;/td&gt;
&lt;td&gt;33% more units to make the same $4,000.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The special order&lt;/td&gt;
&lt;td&gt;$140 beats the $120 variable cost, so 150 units added $3,000 of profit.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The price floor&lt;/td&gt;
&lt;td&gt;Variable cost. Below it, volume makes the loss bigger.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The one habit to keep.&lt;/strong&gt; The habit worth keeping. Never subtract a fixed cost from a price. Fixed costs go in the denominator of break-even, never inside the unit, and every version of this mistake starts with someone dividing overhead by a volume. If a spreadsheet fights back in a way this page does not cover, there is a general &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/technical-tenacity/" rel="noopener noreferrer"&gt;diagnosis loop for being stuck&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Last thing, and this one I am genuinely asking. The first product I ever recommended killing had a fully loaded cost a dollar under its price, and it was throwing off a 40-something percent contribution the whole time. What is a decision you have watched get made on a number that was answering a different question?&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Charnes, A., Cooper, W. W., &amp;amp; Ijiri, Y. (1963). Breakeven budgeting and programming to goals. &lt;em&gt;Journal of Accounting Research&lt;/em&gt; , 1(1), 16–43.&lt;/li&gt;
&lt;li&gt;Cooper, R., &amp;amp; Kaplan, R. S. (1988). Measure costs right: Make the right decisions. &lt;em&gt;Accounting Horizons&lt;/em&gt; , 2(3), 61–66.&lt;/li&gt;
&lt;li&gt;Bisra, K., Liu, Q., Nesbit, J. C., Salimi, F., &amp;amp; Winne, P. H. (2018). Inducing self-explanation: A meta-analysis. &lt;em&gt;Educational Psychology Review&lt;/em&gt; , 30(3), 703–725.&lt;/li&gt;
&lt;li&gt;Roediger, H. L., &amp;amp; Karpicke, J. D. (2006). Test-enhanced learning: Taking memory tests improves long-term retention. &lt;em&gt;Psychological Science&lt;/em&gt; , 17(3), 249–255.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on Analyst Prep Kit: &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/contribution-margin-break-even/" rel="noopener noreferrer"&gt;Contribution Margin and Break-Even: The Fixed Cost That Does Not Belong in the Unit&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Visit the site for more beginner data analysis guides and free resources: &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/" rel="noopener noreferrer"&gt;the full guide archive&lt;/a&gt; covers SQL, Excel, Power BI, Tableau, Python and statistics, and the &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/" rel="noopener noreferrer"&gt;practice kits&lt;/a&gt; run in your browser with nothing to install.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If it was useful: &lt;a href="https://buymeacoffee.com/michaelnocito" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dataanalysis</category>
      <category>career</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>SUM vs SUMX in DAX: Why a Row-by-Row Calculation Needs an Iterator</title>
      <dc:creator>Michael Nocito</dc:creator>
      <pubDate>Sun, 06 Sep 2026 13:00:23 +0000</pubDate>
      <link>https://dev.to/michaelnocito/sum-vs-sumx-in-dax-why-a-row-by-row-calculation-needs-an-iterator-468a</link>
      <guid>https://dev.to/michaelnocito/sum-vs-sumx-in-dax-why-a-row-by-row-calculation-needs-an-iterator-468a</guid>
      <description>&lt;p&gt;By &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/about.html" rel="noopener noreferrer"&gt;Michael Nocito&lt;/a&gt;, data analyst · Published August 10, 2026&lt;/p&gt;

&lt;p&gt;By the end of this page you can look at any DAX aggregation and say which of the two it needs, before you write it. You will also be able to spot the wrong one in somebody else's model, including the version that returns a number close enough to pass. It takes about twenty minutes. Every figure below is printed next to the arithmetic that produced it, so you can check any of them on paper.&lt;/p&gt;

&lt;p&gt;Here is the move. If the number you want to add up already sits in a column, use &lt;code&gt;SUM(Orders[Units])&lt;/code&gt;. If it has to be worked out on each row first, use &lt;code&gt;SUMX(Orders, Orders[Units] * Orders[UnitPrice])&lt;/code&gt;: a table, then the calculation to run on every row of it.&lt;/p&gt;

&lt;p&gt;The short version: SUM adds a column that already exists. SUMX builds a value on each row, then adds those up.&lt;/p&gt;

&lt;p&gt;The mistake that follows from missing this is worth seeing as a picture, because the arithmetic explains itself once you have.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The original carries a diagram here. In words: Two square grids sit side by side, each one sixteen cells wide and sixteen cells tall, one cell for every possible pairing of an order's quantity with an order's price. The left grid, labeled SUM times SUM, is completely filled in: all two hundred and fifty six cells are solid, because multiplying one total by another total combines every quantity with every price, including the pairs that came off different order lines and never occurred together. The right grid, labeled SUMX, is almost entirely empty outlines, with a single unbroken line of sixteen solid cells running corner to corner from top left to bottom right. Those sixteen are the pairings where the quantity and the price came off the same order line. The left grid is a solid block; the right grid is a thin diagonal stripe on an empty field.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Every number on this page is checkable.&lt;/strong&gt; It runs on the sixteen-order fixture used across these Power BI guides, and every result is printed beside its arithmetic in the form 4 × 220 = 880. Every DAX behavior described is quoted from Microsoft's own reference pages in the "why this works" section rather than inferred. If measures against calculated columns is still fuzzy, &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/powerbi-measures-vs-columns/" rel="noopener noreferrer"&gt;columns vs measures&lt;/a&gt; comes first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Orders&lt;/strong&gt; , the fact table. Sixteen rows. The unit price is stamped on the line, which is what an order table does so that a later price change does not rewrite last year's sales.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;OrderID&lt;/th&gt;
&lt;th&gt;Region&lt;/th&gt;
&lt;th&gt;Product&lt;/th&gt;
&lt;th&gt;Units&lt;/th&gt;
&lt;th&gt;UnitPrice&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1001&lt;/td&gt;
&lt;td&gt;North&lt;/td&gt;
&lt;td&gt;Desk&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;220&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1002&lt;/td&gt;
&lt;td&gt;South&lt;/td&gt;
&lt;td&gt;Chair&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1003&lt;/td&gt;
&lt;td&gt;East&lt;/td&gt;
&lt;td&gt;Desk&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;220&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1004&lt;/td&gt;
&lt;td&gt;North&lt;/td&gt;
&lt;td&gt;Lamp&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1005&lt;/td&gt;
&lt;td&gt;South&lt;/td&gt;
&lt;td&gt;Desk&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;220&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1006&lt;/td&gt;
&lt;td&gt;East&lt;/td&gt;
&lt;td&gt;Chair&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1007&lt;/td&gt;
&lt;td&gt;North&lt;/td&gt;
&lt;td&gt;Chair&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1008&lt;/td&gt;
&lt;td&gt;West&lt;/td&gt;
&lt;td&gt;Lamp&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1009&lt;/td&gt;
&lt;td&gt;South&lt;/td&gt;
&lt;td&gt;Lamp&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1010&lt;/td&gt;
&lt;td&gt;East&lt;/td&gt;
&lt;td&gt;Desk&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;220&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1011&lt;/td&gt;
&lt;td&gt;North&lt;/td&gt;
&lt;td&gt;Desk&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;220&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1012&lt;/td&gt;
&lt;td&gt;West&lt;/td&gt;
&lt;td&gt;Chair&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1013&lt;/td&gt;
&lt;td&gt;East&lt;/td&gt;
&lt;td&gt;Lamp&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1014&lt;/td&gt;
&lt;td&gt;North&lt;/td&gt;
&lt;td&gt;Chair&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1015&lt;/td&gt;
&lt;td&gt;South&lt;/td&gt;
&lt;td&gt;Desk&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;220&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1016&lt;/td&gt;
&lt;td&gt;West&lt;/td&gt;
&lt;td&gt;Desk&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;220&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Products&lt;/strong&gt; , the dimension table, joined one-to-many to Orders on Product.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Product&lt;/th&gt;
&lt;th&gt;ListPrice&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Furniture&lt;/td&gt;
&lt;td&gt;Desk&lt;/td&gt;
&lt;td&gt;220&lt;/td&gt;
&lt;td&gt;140&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Furniture&lt;/td&gt;
&lt;td&gt;Chair&lt;/td&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;td&gt;52&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lighting&lt;/td&gt;
&lt;td&gt;Lamp&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There are 101 units in total, and no revenue column anywhere. Revenue is the number you have to build.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The fork: is the number already in a column?
&lt;/h2&gt;

&lt;p&gt;Before the explanation, one question worth answering from memory. Think of the last measure you wrote. Say whether the values it added up were already stored somewhere, or whether the model had to work each one out first.&lt;/p&gt;

&lt;p&gt;That question decides which function you reach for, and it has two answers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Answer one: the number is already in a column.&lt;/strong&gt; Units, Revenue, Amount, Headcount. Somebody stored it, one value per row, and adding it up is the whole job. If that is true you want &lt;code&gt;SUM&lt;/code&gt;, and you hand it the column.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Answer two: the number does not exist yet.&lt;/strong&gt; Line revenue when the table only has units and a price. Margin when the cost lives on another table. Weighted score, extended amount, anything with an operator in the middle of it. If that is true, something has to visit each row, work the value out there, and only then add them up. That something is &lt;code&gt;SUMX&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What decides between them is not how complicated the formula looks. It is whether you could point at a column in the Fields pane and say "that one". If you can, it is SUM. If you have to describe a calculation instead, it is SUMX.&lt;/p&gt;

&lt;p&gt;It matters because picking the wrong one does not always produce an error. Sometimes it produces a number, and the next three sections are about what that number is and why it survives.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. What SUM will and will not accept
&lt;/h2&gt;

&lt;p&gt;Microsoft's reference page for SUM gives it one parameter, and describes it in six words: "The column that contains the numbers to sum." Not an expression. A column.&lt;/p&gt;

&lt;p&gt;So this does not work, and it is the single most common thing a new Power BI user types:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Revenue = SUM ( Orders[Units] * Orders[UnitPrice] )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Power BI refuses it with a message that is unusually direct for a formula engine: &lt;em&gt;The SUM function only accepts a column reference as an argument.&lt;/em&gt; Nothing is stored in the model called &lt;code&gt;Units * UnitPrice&lt;/code&gt;, so there is no column to hand over.&lt;/p&gt;

&lt;p&gt;That refusal is the helpful outcome. It stops you at the point of the mistake. The damaging version is the one that compiles, which is what happens when somebody works around the error by summing each column on its own:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Revenue = SUM ( Orders[Units] ) * SUM ( Orders[UnitPrice] )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Two columns, two legal SUMs, one multiplication. Power BI accepts it without complaint, because there is nothing wrong with it as DAX. It is only wrong as arithmetic.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The failure: 2,630 that reads 12,995
&lt;/h2&gt;

&lt;p&gt;Take the first four orders and do it by hand. Four rows is small enough to check every step, and the shape of the error is identical at sixteen rows or sixteen million.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;OrderID&lt;/th&gt;
&lt;th&gt;Units&lt;/th&gt;
&lt;th&gt;UnitPrice&lt;/th&gt;
&lt;th&gt;Line revenue&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1001&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;220&lt;/td&gt;
&lt;td&gt;880 (4 × 220)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1002&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;td&gt;850 (10 × 85)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1003&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;220&lt;/td&gt;
&lt;td&gt;660 (3 × 220)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1004&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;240 (6 × 40)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The right answer is &lt;strong&gt;2,630&lt;/strong&gt; , because 880 + 850 + 660 + 240 = 2,630. That is what SUMX returns: it works out each line, then adds the four results.&lt;/p&gt;

&lt;p&gt;Now the version that compiles. SUM(Units) is 4 + 10 + 3 + 6 = 23. SUM(UnitPrice) is 220 + 85 + 220 + 40 = 565. Multiply them and you get &lt;strong&gt;12,995&lt;/strong&gt; , because 23 × 565 = 12,995.&lt;/p&gt;

&lt;p&gt;Before reading on, say why 12,995 is so much bigger than 2,630. It is not rounding and it is not a doubled join. Say what those extra 10,365 are made of.&lt;/p&gt;

&lt;p&gt;They are pairings that never happened. Multiplying two totals is the same as adding up every quantity crossed with every price: order 1001's four units at order 1002's price of 85, order 1004's six units at the desk price of 220, and so on through all sixteen combinations. Four rows produce 4 × 4 = 16 pairings. Only four of them are real, the ones where the quantity and the price came off the same line, and those four are the 2,630.&lt;/p&gt;

&lt;p&gt;The gap is the other twelve: 12,995 minus 2,630 = 10,365.&lt;/p&gt;

&lt;p&gt;Run the same thing on the full sixteen orders and the numbers get louder. SUMX returns &lt;strong&gt;9,890&lt;/strong&gt;. SUM(Units) is 101, SUM(UnitPrice) is 2,125, and 101 × 2,125 = &lt;strong&gt;214,625&lt;/strong&gt;. Sixteen rows means 16 × 16 = 256 pairings, and 240 of them are fiction.&lt;/p&gt;

&lt;p&gt;Here is the working measure.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Total Revenue =
SUMX ( Orders, Orders[Units] * Orders[UnitPrice] )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Read it as two arguments doing two jobs. &lt;code&gt;Orders&lt;/code&gt; is the table to walk. &lt;code&gt;Orders[Units] * Orders[UnitPrice]&lt;/code&gt; is the calculation to run while standing on each row of it. SUMX adds the sixteen results.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Why nobody catches it: slice to one row and both agree
&lt;/h2&gt;

&lt;p&gt;Before the explanation, a prediction. You have a table visual showing revenue, and you drill all the way down to a single order to check the measure. Say whether the broken version and the working version show the same figure in that cell.&lt;/p&gt;

&lt;p&gt;They do, exactly, and this is the part worth carrying away from the page.&lt;/p&gt;

&lt;p&gt;Filter the visual to order 1001 alone. SUMX walks one row and returns 4 × 220 = 880. The broken measure computes SUM(Units) over that one row, which is 4, and SUM(UnitPrice) over that one row, which is 220, then multiplies: 4 × 220 = 880. Identical.&lt;/p&gt;

&lt;p&gt;That is not a coincidence about this fixture. With one row there is only one pairing available, so "every pairing" and "the real pairing" are the same set. The two measures cannot disagree.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What the visual is sliced to&lt;/th&gt;
&lt;th&gt;Broken measure&lt;/th&gt;
&lt;th&gt;Working measure&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Order 1001 alone&lt;/td&gt;
&lt;td&gt;880&lt;/td&gt;
&lt;td&gt;880&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Order 1013 alone&lt;/td&gt;
&lt;td&gt;600 (15 × 40)&lt;/td&gt;
&lt;td&gt;600 (15 × 40)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lamps, four orders&lt;/td&gt;
&lt;td&gt;6,400 (40 × 160)&lt;/td&gt;
&lt;td&gt;1,600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;All sixteen orders&lt;/td&gt;
&lt;td&gt;214,625&lt;/td&gt;
&lt;td&gt;9,890&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The lamp row needs its own line, because it is the one that shows where the agreement stops. Lamps are four orders of 6, 12, 7 and 15 units, all at 40. SUMX gives 240 + 480 + 280 + 600 = 1,600. The broken measure gives SUM(Units) = 40 and SUM(UnitPrice) = 160, and 40 × 160 = 6,400. Four times too big, because four rows means sixteen pairings and only four are real.&lt;/p&gt;

&lt;p&gt;So the test that proves the measure works is the test that cannot fail. A single-row check passes on a measure that is wrong by a factor of sixteen at the total. This is the same shape as the Excel fill whose first row is right in both the working and the broken version, and it is why the habit at the end of this page is about totals rather than about being careful.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. What SUMX is actually doing on each row
&lt;/h2&gt;

&lt;p&gt;Microsoft calls SUMX an &lt;strong&gt;iterator function&lt;/strong&gt; , and defines the category in one sentence: "A DAX function that enumerates all rows of a given table and evaluate a given expression for each row."&lt;/p&gt;

&lt;p&gt;The word that matters underneath that is &lt;strong&gt;row context&lt;/strong&gt;. Row context is the model knowing which single row it is standing on, which is what makes &lt;code&gt;Orders[Units]&lt;/code&gt; mean a number rather than a whole column. Microsoft's glossary puts it plainly: row context "represents the 'current row', and is used to evaluate calculated column formulas and expressions used by table iterators."&lt;/p&gt;

&lt;p&gt;So the sequence inside &lt;code&gt;SUMX(Orders, Orders[Units] * Orders[UnitPrice])&lt;/code&gt; is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Take the rows of Orders that survived the filters on the visual.&lt;/li&gt;
&lt;li&gt;Stand on the first one. &lt;code&gt;Orders[Units]&lt;/code&gt; is 4 and &lt;code&gt;Orders[UnitPrice]&lt;/code&gt; is 220, so the expression is 880.&lt;/li&gt;
&lt;li&gt;Move to the next. Repeat. Sixteen times.&lt;/li&gt;
&lt;li&gt;Add up the sixteen results and return one number.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Nothing is stored. There is no revenue column at the end of it, which is the difference between this and a &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/powerbi-measures-vs-columns/" rel="noopener noreferrer"&gt;calculated column&lt;/a&gt;. A calculated column would compute 880 once, save it in the file, and use memory forever. SUMX computes it when a visual asks and throws it away.&lt;/p&gt;

&lt;p&gt;One thing to know rather than discover: a row context does not filter anything. Standing on row 1001 does not restrict other tables to that order. Referencing a measure inside the iterator changes that, and it changes it silently, which is the trapdoor covered in &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/powerbi-calculate/" rel="noopener noreferrer"&gt;CALCULATE and filter context&lt;/a&gt;. Read that one before you write &lt;code&gt;SUMX(Orders, [Some Measure])&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now picture this running on a table you actually own. Take your own fact table, find one number on a report that had to be worked out rather than looked up, and say out loud which table SUMX would walk and what the expression on each row would be. Two pieces, that is the whole call.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. The rest of the X family
&lt;/h2&gt;

&lt;p&gt;SUMX is not special. Every aggregation in DAX has an X twin, and they all take the same two arguments in the same order: a table, then an expression to evaluate on each row of it. Learning SUMX teaches you all of them at once.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plain version&lt;/th&gt;
&lt;th&gt;Iterator&lt;/th&gt;
&lt;th&gt;Reach for the iterator when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SUM(col)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SUMX(table, expr)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The amount has to be calculated per row first.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AVERAGE(col)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AVERAGEX(table, expr)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;You want the mean of a per-row calculation.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MAX(col)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MAXX(table, expr)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The biggest calculated value, not the biggest stored one.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MIN(col)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MINX(table, expr)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Same, in the other direction.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;COUNT(col)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;COUNTX(table, expr)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Counting rows where an expression produces a number.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;&lt;code&gt;RANKX(table, expr)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ranking on something the model works out per row.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CONCATENATEX(table, expr, delim)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Turning rows into one readable string for a card.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;CONCATENATEX is the one worth trying first, because it makes the row-by-row idea visible. It takes an optional third argument, the separator, and it is how a report shows "Desk, Chair, Lamp" in a title instead of a number.&lt;/p&gt;

&lt;p&gt;AVERAGEX has one behavior worth knowing before it surprises you in a matrix. Microsoft states it directly: "When there are no rows to aggregate, the function returns a blank. When there are rows, but none of them meet the specified criteria, then the function returns 0." A blank cell and a zero cell mean different things, and only one of them draws a bar.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. The wrong number that looks right: an average of percentages
&lt;/h2&gt;

&lt;p&gt;Before this one: a report needs the overall margin percentage. Somebody writes a margin percent for each order and averages it. Say whether that gives the same answer as total margin divided by total revenue.&lt;/p&gt;

&lt;p&gt;It does not, and unlike the revenue example the gap here is small enough to ship.&lt;/p&gt;

&lt;p&gt;Cost lives on Products, not on Orders, so margin is a number that exists nowhere and has to be built. &lt;code&gt;RELATED&lt;/code&gt; is what lets a row of Orders read its own product's row on the other side of the relationship.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Total Margin =
SUMX (
    Orders,
    Orders[Units] * ( Orders[UnitPrice] - RELATED ( Products[Cost] ) )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Per unit that is 220 - 140 = 80 on a desk, 85 - 52 = 33 on a chair, and 40 - 22 = 18 on a lamp. Across the sixteen orders there are 23 desk units, 38 chair units and 40 lamp units, so total margin is 23 × 80 + 38 × 33 + 40 × 18 = 1,840 + 1,254 + 720 = &lt;strong&gt;3,814&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Revenue is 9,890. So the blended margin is 3,814 / 9,890 = &lt;strong&gt;38.56%&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now the version that averages. Every desk order runs at 80 / 220 = 36.36%, every chair at 33 / 85 = 38.82%, every lamp at 18 / 40 = 45.00%, whatever the quantity. There are seven desk orders, five chair orders and four lamp orders, so:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Average Margin % =
AVERAGEX ( Orders, DIVIDE ( [Order Margin], [Order Revenue] ) )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Product&lt;/th&gt;
&lt;th&gt;Margin %&lt;/th&gt;
&lt;th&gt;Orders&lt;/th&gt;
&lt;th&gt;Contribution to the average&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Desk&lt;/td&gt;
&lt;td&gt;36.36% (80 / 220)&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;7 × 36.36 = 254.55&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chair&lt;/td&gt;
&lt;td&gt;38.82% (33 / 85)&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5 × 38.82 = 194.12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lamp&lt;/td&gt;
&lt;td&gt;45.00% (18 / 40)&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4 × 45.00 = 180.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;16&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;628.66 / 16 = 39.29%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;39.29% against a true 38.56%. Wrong by 0.73 percentage points, and entirely believable in a card on a dashboard.&lt;/p&gt;

&lt;p&gt;The reason is worth saying in one line, because it is the whole class of error. The average gives every order one vote. Lamps are 4 of the 16 orders, so they carry 25% of the vote, but they are only 1,600 of the 9,890 in revenue, which is 16.18%. Lamps also have the highest margin percentage, so overweighting them pulls the average up.&lt;/p&gt;

&lt;p&gt;A ratio of totals is not the total of ratios. The fix is to divide once, at the end:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Margin % = DIVIDE ( [Total Margin], [Total Revenue] )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The rule that comes out of this: SUMX the numerator, SUMX the denominator, and divide those two. Never average a percentage that was already a ratio, unless somebody has told you in writing that they want each order to count equally.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. When SUM is the correct answer
&lt;/h2&gt;

&lt;p&gt;Nothing above is an argument for using iterators everywhere. Microsoft's own guidance, on the SUMX page itself, is the opposite: "If you do not need to filter the column, use the SUM function."&lt;/p&gt;

&lt;p&gt;SUM is right whenever the value is already sitting in the column, which covers most of a normal model. &lt;code&gt;SUM(Orders[Units])&lt;/code&gt; is 101 and there is nothing an iterator would add to that. A fact table that stores an extended amount per line should be summed, not re-derived.&lt;/p&gt;

&lt;p&gt;There is a second case worth naming, because it looks like laziness and is not. If your fact table already has a stored revenue column, using it is more accurate than recomputing units times price, because the stored value is what was actually invoiced. Rounding, discounts and price overrides all live in that stored number and none of them are in the multiplication.&lt;/p&gt;

&lt;p&gt;And if you find yourself writing &lt;code&gt;SUMX(Orders, Orders[Units])&lt;/code&gt;, with a bare column as the expression and no calculation in it, that is a SUM with extra syntax. Write the SUM.&lt;/p&gt;

&lt;h2&gt;
  
  
  The full before and after
&lt;/h2&gt;

&lt;p&gt;Same model, same visual, same sixteen rows. One function apart.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Before: &lt;code&gt;SUM(Units) * SUM(UnitPrice)&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;After: &lt;code&gt;SUMX(Orders, Units * UnitPrice)&lt;/code&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Does it compile&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One order (1001)&lt;/td&gt;
&lt;td&gt;880&lt;/td&gt;
&lt;td&gt;880&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lamps, four orders&lt;/td&gt;
&lt;td&gt;6,400 (40 × 160)&lt;/td&gt;
&lt;td&gt;1,600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Desks, seven orders&lt;/td&gt;
&lt;td&gt;35,420 (23 × 1,540)&lt;/td&gt;
&lt;td&gt;5,060 (23 × 220)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;All sixteen orders&lt;/td&gt;
&lt;td&gt;214,625 (101 × 2,125)&lt;/td&gt;
&lt;td&gt;9,890&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What it is adding&lt;/td&gt;
&lt;td&gt;All 256 quantity-and-price pairings&lt;/td&gt;
&lt;td&gt;The 16 pairings that happened&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What a reviewer checks first&lt;/td&gt;
&lt;td&gt;A single row, which matches&lt;/td&gt;
&lt;td&gt;A single row, which matches&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last line is the point of the page. Both versions pass the check most people run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Edge cases that return a number instead of an error
&lt;/h2&gt;

&lt;p&gt;These are the ones that produce output rather than a red squiggle.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Averaging a percentage.&lt;/strong&gt; Section 7 in one line: &lt;code&gt;AVERAGEX&lt;/code&gt; over a ratio weights every row equally, so the answer drifts by however unbalanced your rows are. Divide two totals instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blanks are not zeros.&lt;/strong&gt; The SUMX reference says only numbers are counted and "blanks, logical values, and text are ignored". A row with a blank price contributes nothing rather than erroring, so a partly-loaded column produces a total that is quietly low.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A measure inside the iterator.&lt;/strong&gt; &lt;code&gt;SUMX(Products, [Total Revenue])&lt;/code&gt; does not do what the syntax suggests, because referencing a measure in a row context triggers context transition automatically. That is a whole mechanism, and it is worked through on the &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/powerbi-calculate/" rel="noopener noreferrer"&gt;CALCULATE&lt;/a&gt; page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iterating the wrong table.&lt;/strong&gt; &lt;code&gt;SUMX(Products, ...)&lt;/code&gt; walks three rows and &lt;code&gt;SUMX(Orders, ...)&lt;/code&gt; walks sixteen. Both compile. The first argument is a decision, not boilerplate, and if it is a dimension table you are almost always on the wrong one. There is more on which table is which in &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/powerbi-star-schema/" rel="noopener noreferrer"&gt;star schema in Power BI&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nesting an iterator inside an iterator.&lt;/strong&gt; Legal, and it multiplies the work: a SUMX over 16 rows containing a SUMX over 16 rows evaluates 256 times. Fine here, and not fine on a fact table with ten million rows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Using it where a calculated column belongs.&lt;/strong&gt; If the per-row value is needed as a slicer or an axis, a measure cannot give you that. That decision has its own page: &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/powerbi-measures-vs-columns/" rel="noopener noreferrer"&gt;calculated column vs measure&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this works
&lt;/h2&gt;

&lt;p&gt;The design reason is the useful part. DAX separates the two functions because they answer different questions, and Microsoft's reference pages say so in their opening lines. SUM "adds all the numbers in a column". SUMX "returns the sum of an expression evaluated for each row in a table". One reads storage, the other runs a calculation.&lt;/p&gt;

&lt;p&gt;The failure is worth taking seriously because it is a specific case of something general: a calculation done on totals is not the same calculation done on the individual rows and then totalled. That is not a Power BI quirk. W. S. Robinson demonstrated the same thing for correlations in 1950, showing that relationships computed from grouped census data did not match the relationships among the individuals inside those groups, which is the finding that became known as the ecological fallacy (Robinson, 1950, &lt;em&gt;American Sociological Review&lt;/em&gt; , 15(3), 351–357). The arithmetic here is simpler than his, but the mistake is the same one: aggregate first and the row-level relationship is gone before you use it.&lt;/p&gt;

&lt;p&gt;The reason it survives a code review is arithmetic rather than psychology. Every check on a single row returns identical results from both measures, as shown in section 4. So the reviewer's evidence is real, and it is evidence about nothing. The only test that separates the two measures is one that spans more than one row, which means a total you predicted before you looked at it.&lt;/p&gt;

&lt;p&gt;One note on the cheat sheet below. It is built to be covered and recalled rather than read, because testing yourself on material transfers to new situations better than restudying it, which matters here since you will be applying this to models that look nothing like the one above (Butler, 2010, &lt;em&gt;Journal of Experimental Psychology: Learning, Memory, and Cognition&lt;/em&gt; , 36(5), 1118–1133).&lt;/p&gt;

&lt;h2&gt;
  
  
  Using this on your own model
&lt;/h2&gt;

&lt;p&gt;Auditing every measure in an inherited file is miserable and you will stop on the second table. Do this instead, in order.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Search the measure list for&lt;code&gt;) * SUM&lt;/code&gt; and &lt;code&gt;) / SUM&lt;/code&gt;.&lt;/strong&gt; Two aggregations combined with an operator between them is the pattern. Most of the hits will be fine. The ones that are not are all here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For each hit, ask whether the two columns come from the same row.&lt;/strong&gt; Units and price on the same line are a row-level pair, and multiplying their totals is wrong. Revenue and headcount from different tables usually are not, and dividing their totals is right.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check a total, never a row.&lt;/strong&gt; Take the filter off, read the grand total, and compare it to something you know. A revenue figure twenty times your annual sales fails in one second.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check a group with more than one row in it.&lt;/strong&gt; If a single-row slice is all you tested, you tested nothing. Pick the product with the most orders behind it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Find every averaged percentage.&lt;/strong&gt; Anything named "Average X %" or "Avg Margin" is worth opening. If it averages a ratio, replace it with DIVIDE of two totals and tell whoever owns the report that the number will move.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write the intended arithmetic in the measure description.&lt;/strong&gt; "Sum of units times unit price, per line" is one sentence, it sits in the model, and it makes the next person's review possible.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you have paper nearby, one optional sketch fixes this permanently. Draw a four-by-four grid of boxes. Write four quantities down the side and four prices across the top. Shade the four boxes where a quantity meets its own price. What you have shaded is SUMX, and the whole grid is what multiplying two totals gives you. You will not need to look this up again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More detail on this, and more like it.&lt;/strong&gt; Every how-to sits in one place on the &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/" rel="noopener noreferrer"&gt;guides index&lt;/a&gt;: Power BI, SQL, Excel, and the working habits around them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The whole thing on one screen
&lt;/h2&gt;

&lt;p&gt;This is the retrieval sheet. Cover the right column, work down the left, and say each answer out loud before you check it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Thing&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SUM(col)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Adds a column that already exists. One argument, and it must be a column.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SUMX(table, expr)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Evaluates the expression on every row, then adds the results.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The question to ask&lt;/td&gt;
&lt;td&gt;Can I point at a column in the Fields pane? If not, it is SUMX.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Iterator&lt;/td&gt;
&lt;td&gt;A function that walks every row of a table and evaluates an expression there.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Row context&lt;/td&gt;
&lt;td&gt;The model knowing which single row it is standing on.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Row context does not&lt;/td&gt;
&lt;td&gt;Filter anything. Referencing a measure is what changes that.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SUM(a) * SUM(b)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Adds every pairing of a with b. 16 rows means 256 pairings.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Why it passes review&lt;/td&gt;
&lt;td&gt;Sliced to one row, both measures return the same number.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The test that works&lt;/td&gt;
&lt;td&gt;A total you predicted before you read it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The X family&lt;/td&gt;
&lt;td&gt;AVERAGEX, MAXX, MINX, COUNTX, RANKX, CONCATENATEX. Same two arguments.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Average of a ratio&lt;/td&gt;
&lt;td&gt;Weights every row equally. 39.29% where the truth is 38.56%.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fix for a ratio&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;DIVIDE([Total Margin], [Total Revenue])&lt;/code&gt;. Divide once, at the end.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reading another table&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;RELATED&lt;/code&gt;, from the many side to the one side.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blanks in SUMX&lt;/td&gt;
&lt;td&gt;Ignored, not zero. A half-loaded column totals quietly low.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SUMX(t, t[col])&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A SUM with extra syntax. Write the SUM.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When SUM is right&lt;/td&gt;
&lt;td&gt;The value is stored. Microsoft: "If you do not need to filter the column, use the SUM function."&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The one habit to keep.&lt;/strong&gt; Predict the grand total before you read it. Sixteen orders at these prices is roughly ten thousand, so 214,625 is wrong before you have opened a single measure. A rough number you committed to in advance catches this class of mistake faster than reading DAX will, and it costs about four seconds.&lt;/p&gt;

&lt;p&gt;One last thought, and I would genuinely like other people's answers. The worst version I have seen was an average discount percentage on an executive card. It was built as an average of per-order discounts, it ran about two points under the real blended figure, and it had been quoted in board papers for three quarters before anyone divided the two totals. What is the longest one of these has gone unnoticed in a model you inherited?&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Robinson, W. S. (1950). Ecological correlations and the behavior of individuals. &lt;em&gt;American Sociological Review&lt;/em&gt; , 15(3), 351–357.&lt;/li&gt;
&lt;li&gt;Butler, A. C. (2010). Repeated testing produces superior transfer of learning relative to repeated studying. &lt;em&gt;Journal of Experimental Psychology: Learning, Memory, and Cognition&lt;/em&gt; , 36(5), 1118–1133.&lt;/li&gt;
&lt;li&gt;Microsoft. SUM function (DAX), SUMX function (DAX), AVERAGEX function (DAX), CONCATENATEX function (DAX) and DAX glossary. Microsoft Learn. All quotations above are taken from these reference pages.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on Analyst Prep Kit: &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/powerbi-sum-vs-sumx/" rel="noopener noreferrer"&gt;SUM vs SUMX in DAX: Why a Row-by-Row Calculation Needs an Iterator&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Visit the site for more beginner data analysis guides and free resources: &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/" rel="noopener noreferrer"&gt;the full guide archive&lt;/a&gt; covers SQL, Excel, Power BI, Tableau, Python and statistics, and the &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/" rel="noopener noreferrer"&gt;practice kits&lt;/a&gt; run in your browser with nothing to install.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If it was useful: &lt;a href="https://buymeacoffee.com/michaelnocito" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>powerbi</category>
      <category>dataanalysis</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Row-Level Security in Power BI: Who Gets Filtered and Who Does Not</title>
      <dc:creator>Michael Nocito</dc:creator>
      <pubDate>Sun, 06 Sep 2026 13:00:15 +0000</pubDate>
      <link>https://dev.to/michaelnocito/row-level-security-in-power-bi-who-gets-filtered-and-who-does-not-33b7</link>
      <guid>https://dev.to/michaelnocito/row-level-security-in-power-bi-who-gets-filtered-and-who-does-not-33b7</guid>
      <description>&lt;p&gt;By &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/about.html" rel="noopener noreferrer"&gt;Michael Nocito&lt;/a&gt;, data analyst · Published August 10, 2026&lt;/p&gt;

&lt;p&gt;By the end of this page you can set up row-level security, and, more usefully, predict what any given person will actually see before they open the report. You will know the one setting that switches the whole thing off without any warning, why adding somebody to a second role gives them more data rather than less, and what the built-in test proves and what it quietly does not. It takes about twenty-five minutes. Every number below is printed next to the arithmetic that produced it.&lt;/p&gt;

&lt;p&gt;Here is the move. You write a rule in Power BI Desktop under &lt;strong&gt;Modeling › Manage roles&lt;/strong&gt; , something like &lt;code&gt;[Region] = "East"&lt;/code&gt;. You publish. Then you go to the Power BI Service, open the semantic model's &lt;strong&gt;Security&lt;/strong&gt; page, and add people to that role. Two tools, two steps, and skipping the second one is the most common reason RLS appears to do nothing.&lt;/p&gt;

&lt;p&gt;The short version: a role is a filter that answers TRUE or FALSE for every row, and rows that answer FALSE are removed. Whether it binds a particular person depends on their workspace role, not on the filter.&lt;/p&gt;

&lt;p&gt;That second sentence is where almost all the damage lives, so it gets the picture.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The original carries a diagram here. In words: On the left is a tall stack of sixteen identical narrow bars, drawn in outline, standing for every row in the model. Two routes lead away from that stack to the right. The upper route runs into a funnel, wide where it enters and narrow where it leaves, and comes out the far side as a short stack of only four solid bars, marked Viewer. The lower route leaves the same stack, curves underneath the funnel without touching it, and arrives on the right as a full stack of all sixteen solid bars, marked Admin, Member, Contributor. Same model, same rule, same starting rows. The only difference between the two routes is whether they meet the funnel at all, and the route that misses it delivers everything.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Every number on this page is checkable.&lt;/strong&gt; It runs on the sixteen-order fixture used across these Power BI guides, and every total is printed beside its arithmetic. Every behavior described is quoted from Microsoft's own documentation in the "why this works" section rather than inferred, because this is the one topic on the site where being approximately right is the same as being wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Orders&lt;/strong&gt; , the fact table, with revenue already worked out per line. Sixteen rows, four regions.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;OrderID&lt;/th&gt;
&lt;th&gt;Region&lt;/th&gt;
&lt;th&gt;Product&lt;/th&gt;
&lt;th&gt;Units&lt;/th&gt;
&lt;th&gt;Revenue&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1001&lt;/td&gt;
&lt;td&gt;North&lt;/td&gt;
&lt;td&gt;Desk&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;880&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1002&lt;/td&gt;
&lt;td&gt;South&lt;/td&gt;
&lt;td&gt;Chair&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;850&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1003&lt;/td&gt;
&lt;td&gt;East&lt;/td&gt;
&lt;td&gt;Desk&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;660&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1004&lt;/td&gt;
&lt;td&gt;North&lt;/td&gt;
&lt;td&gt;Lamp&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;240&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1005&lt;/td&gt;
&lt;td&gt;South&lt;/td&gt;
&lt;td&gt;Desk&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;660&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1006&lt;/td&gt;
&lt;td&gt;East&lt;/td&gt;
&lt;td&gt;Chair&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;680&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1007&lt;/td&gt;
&lt;td&gt;North&lt;/td&gt;
&lt;td&gt;Chair&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;425&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1008&lt;/td&gt;
&lt;td&gt;West&lt;/td&gt;
&lt;td&gt;Lamp&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;480&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1009&lt;/td&gt;
&lt;td&gt;South&lt;/td&gt;
&lt;td&gt;Lamp&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;280&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1010&lt;/td&gt;
&lt;td&gt;East&lt;/td&gt;
&lt;td&gt;Desk&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;1,100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1011&lt;/td&gt;
&lt;td&gt;North&lt;/td&gt;
&lt;td&gt;Desk&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;440&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1012&lt;/td&gt;
&lt;td&gt;West&lt;/td&gt;
&lt;td&gt;Chair&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;765&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1013&lt;/td&gt;
&lt;td&gt;East&lt;/td&gt;
&lt;td&gt;Lamp&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1014&lt;/td&gt;
&lt;td&gt;North&lt;/td&gt;
&lt;td&gt;Chair&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;510&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1015&lt;/td&gt;
&lt;td&gt;South&lt;/td&gt;
&lt;td&gt;Desk&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;880&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1016&lt;/td&gt;
&lt;td&gt;West&lt;/td&gt;
&lt;td&gt;Desk&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;440&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Four regional totals, and they are the four numbers the rest of the page checks against.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Region&lt;/th&gt;
&lt;th&gt;Orders&lt;/th&gt;
&lt;th&gt;Revenue&lt;/th&gt;
&lt;th&gt;Arithmetic&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;North&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;2,495&lt;/td&gt;
&lt;td&gt;880 + 240 + 425 + 440 + 510&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;South&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2,670&lt;/td&gt;
&lt;td&gt;850 + 660 + 280 + 880&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;East&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;3,040&lt;/td&gt;
&lt;td&gt;660 + 680 + 1,100 + 600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;West&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1,685&lt;/td&gt;
&lt;td&gt;480 + 765 + 440&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;All&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;16&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;9,890&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2,495 + 2,670 + 3,040 + 1,685&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  1. The fork: can this person edit the model, or only read it?
&lt;/h2&gt;

&lt;p&gt;Before the explanation, one question worth answering from memory. Think of the last Power BI report you shared with somebody. Say whether that person could open the underlying model and change a measure, or whether they could only look at what you built.&lt;/p&gt;

&lt;p&gt;That question, not the rule you wrote, decides whether row-level security does anything at all. There are two answers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Answer one: they can only read.&lt;/strong&gt; Their workspace role is Viewer, or they got the report through a published app. If that is true, every rule you wrote runs against every query they make, and they see only the rows their role permits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Answer two: they can edit.&lt;/strong&gt; Their workspace role is Admin, Member or Contributor. If that is true, row-level security does not apply to them at all. Not partially, not with a warning. They see every row in the model, and the report gives them no sign that a rule exists.&lt;/p&gt;

&lt;p&gt;What decides between them is a dropdown in the workspace access panel, which is usually set by whoever added the person, often months earlier, and usually not by whoever wrote the security rule. That gap between the two decisions is where this fails.&lt;/p&gt;

&lt;p&gt;It matters because the failure is silent on both sides. The person seeing too much has no idea, and the person who built the model tested it and watched it work.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. What a role actually is
&lt;/h2&gt;

&lt;p&gt;A role is a name plus one or more filters, and each filter is attached to a table. The filter is a DAX expression that gets asked about every single row and has to answer TRUE or FALSE. Microsoft's wording is worth having exactly: "A DAX filter evaluates TRUE/FALSE for each row. Only rows that return TRUE are visible. Everything else is completely removed."&lt;/p&gt;

&lt;p&gt;Completely removed is the part to hold on to. This is not a report filter that a user can clear, and it is not a default someone can override in the Filters pane. The rows are gone before the visual is drawn.&lt;/p&gt;

&lt;p&gt;The simplest useful rule, typed into the DAX editor under Modeling › Manage roles, is a comparison:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Region] = "East"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Say out loud what that returns for order 1004, which is a North lamp. FALSE, so the row is dropped. Order 1013 is an East lamp, so TRUE, so the row stays. Sixteen questions, four answers of TRUE, and the model an East role member queries has four rows in Orders and totals 3,040.&lt;/p&gt;

&lt;p&gt;One naming rule that catches people, and it is documented rather than folklore: a role name cannot contain a comma. &lt;code&gt;London,ParisRole&lt;/code&gt; is rejected.&lt;/p&gt;

&lt;p&gt;Filters land on one table and then travel. RLS filters propagate through the model's relationships the same way any other filter does, which is why it is usually better to put the rule on a small dimension table and let it flow to the fact table. That is one more argument for a proper &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/powerbi-star-schema/" rel="noopener noreferrer"&gt;star schema&lt;/a&gt;, and Microsoft's guidance says so directly: "it's often more efficient to enforce RLS filters on dimension tables, and not fact tables."&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Two tools, two steps: where roles live and where people do
&lt;/h2&gt;

&lt;p&gt;This trips up more exam candidates than any other part of the topic, and the reason is that the job is split across two different products.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Where&lt;/th&gt;
&lt;th&gt;What you do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. Define the role&lt;/td&gt;
&lt;td&gt;Power BI Desktop&lt;/td&gt;
&lt;td&gt;Modeling › Manage roles. Name it, pick a table, write the DAX filter.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. Publish&lt;/td&gt;
&lt;td&gt;Desktop to the Service&lt;/td&gt;
&lt;td&gt;The role definitions travel with the model. The membership does not, because it does not exist yet.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. Assign people&lt;/td&gt;
&lt;td&gt;Power BI Service&lt;/td&gt;
&lt;td&gt;Semantic model › Security. Add users or security groups to each role.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. Validate&lt;/td&gt;
&lt;td&gt;Either&lt;/td&gt;
&lt;td&gt;View As in Desktop, or Test as role in the Service.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You cannot assign users to a role in Desktop. The option is not hidden or buried, it is not there, because Desktop has no idea who is in your organization. And the Security page in the Service only appears for models that already have roles defined, so a model with no roles gives you nothing to click and no explanation.&lt;/p&gt;

&lt;p&gt;Two smaller facts that come up. Only the semantic model's owner or a workspace admin can add members to roles. And Microsoft 365 groups cannot be used for role membership at all; security groups, distribution groups and mail-enabled groups can.&lt;/p&gt;

&lt;p&gt;Anyone who has published a model but not assigned anybody gets the outcome that looks like a bug and is not. A user in no role at all typically sees nothing rather than everything, because the rule is enforced and no role permits any row.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The failure: the East manager who sees 9,890
&lt;/h2&gt;

&lt;p&gt;Here it is worked end to end, and it is the version that actually happens.&lt;/p&gt;

&lt;p&gt;Priya runs the East region. You build the model, write &lt;code&gt;[Region] = "East"&lt;/code&gt; into a role called East, publish it, add Priya to the role in the Service, and test it. The test passes: four orders, 3,040.&lt;/p&gt;

&lt;p&gt;Some weeks earlier, though, Priya was added to the workspace so she could open the other three reports the team keeps there. She was given &lt;strong&gt;Member&lt;/strong&gt;. Nobody connected the two decisions, because they were made by different people for different reasons on different days.&lt;/p&gt;

&lt;p&gt;Priya opens her regional card. It reads &lt;strong&gt;9,890&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What was intended&lt;/th&gt;
&lt;th&gt;What she sees&lt;/th&gt;
&lt;th&gt;Gap&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;3,040, the four East orders&lt;/td&gt;
&lt;td&gt;9,890, all sixteen orders&lt;/td&gt;
&lt;td&gt;6,850 (9,890 − 3,040)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Before reading on, say why the report gives her no clue. The rule is still there, the role is still assigned, and she is still a member of it.&lt;/p&gt;

&lt;p&gt;Because there is nothing to show. RLS does not grey anything out or add a banner. It removes rows before the query returns, so a report with no rows removed is identical to a report that never had a rule. Priya's card is not displaying an error. It is displaying the correct total for the rows she was given, and she was given all of them.&lt;/p&gt;

&lt;p&gt;The fix is one dropdown, and it is not in the model. In the workspace access panel, change Priya from Member to &lt;strong&gt;Viewer&lt;/strong&gt;. She keeps the reports, loses nothing she uses, and the rule starts running. If she needs to build her own reports on top of the model, give her Viewer plus Build permission on the semantic model, because RLS still applies to Viewers who have Build. Microsoft states that case explicitly, including for Analyze in Excel.&lt;/p&gt;

&lt;p&gt;Now picture this on a workspace you actually own. Open the access list and read the roles rather than the names. Say out loud how many people on it could edit, and whether every one of them is supposed to see every row. That count is the real scope of your security rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Two roles is a union, not an intersection
&lt;/h2&gt;

&lt;p&gt;Before the explanation, a prediction. Somebody is in a role that permits East, and you also add them to a role that permits lamps. Say whether they end up seeing East lamps only, or something larger.&lt;/p&gt;

&lt;p&gt;Something larger, and it is the answer most people get wrong because every other permission system they have used behaves the other way.&lt;/p&gt;

&lt;p&gt;Microsoft's guidance is unambiguous: "When a report user is assigned to multiple roles, RLS filters become additive. It means report users can see table rows that represent the union of those filters." And the sentence right after it is the one to memorize, because it names the assumption being broken: "unlike permissions applied to SQL Server database objects (and other permission models), the 'once denied always denied' principle doesn't apply."&lt;/p&gt;

&lt;p&gt;Worked on the fixture. The East role permits orders 1003, 1006, 1010 and 1013. A Lamps role permits every lamp order, which is 1004, 1008, 1009 and 1013.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you get&lt;/th&gt;
&lt;th&gt;Orders&lt;/th&gt;
&lt;th&gt;Revenue&lt;/th&gt;
&lt;th&gt;Arithmetic&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;East role alone&lt;/td&gt;
&lt;td&gt;1003, 1006, 1010, 1013&lt;/td&gt;
&lt;td&gt;3,040&lt;/td&gt;
&lt;td&gt;660 + 680 + 1,100 + 600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lamps role alone&lt;/td&gt;
&lt;td&gt;1004, 1008, 1009, 1013&lt;/td&gt;
&lt;td&gt;1,600&lt;/td&gt;
&lt;td&gt;240 + 480 + 280 + 600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What people expect from both&lt;/td&gt;
&lt;td&gt;1013&lt;/td&gt;
&lt;td&gt;600&lt;/td&gt;
&lt;td&gt;The one East lamp&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;What both actually give&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;seven orders&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4,040&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;660 + 240 + 680 + 480 + 280 + 1,100 + 600&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Expected 600, delivered 4,040, and 4,040 minus 600 = 3,440 of rows nobody meant to hand over. Adding a second role widened the access, which is the opposite of what "adding a restriction" sounds like.&lt;/p&gt;

&lt;p&gt;Microsoft's own illustration of this is starker than mine and worth keeping in your head as the extreme case. A Workers role restricts a Payroll table with the rule &lt;code&gt;FALSE()&lt;/code&gt;, so it returns no rows at all. A Managers role uses &lt;code&gt;TRUE()&lt;/code&gt;, so it returns everything. Somebody who maps to both roles sees the entire Payroll table. The deny does not win. There is no deny.&lt;/p&gt;

&lt;p&gt;The fix is a design rule rather than a setting: &lt;strong&gt;one person, one role&lt;/strong&gt;. If a person needs East and lamps together, build a single role that carries both filters, one on each table, so both conditions apply at once. Microsoft's guidance recommends exactly this, and the reason it gives is worth noting: a user can end up in a second role indirectly, through a security group, without anybody deciding to put them there.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Dynamic RLS, and the typo that opens the whole table
&lt;/h2&gt;

&lt;p&gt;Writing one role per region does not scale past a handful. Dynamic RLS is one role for everybody, where the rule compares a column to whoever is signed in.&lt;/p&gt;

&lt;p&gt;You need a small mapping table, one row per person, with their sign-in address and what they are allowed to see. Then the rule goes on that table:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[UserEmail] = USERPRINCIPALNAME()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;USERPRINCIPALNAME takes no arguments and returns the user principal name at connection time, which looks like an email address. The filter lands on the mapping table, the mapping table is related to the region, and the filter travels down that relationship to Orders. One role, one rule, and adding a new manager becomes a row in a table rather than a change to the model.&lt;/p&gt;

&lt;p&gt;There is an older function, USERNAME, and the difference is a real trap rather than trivia. In Power BI Desktop, USERNAME returns &lt;code&gt;DOMAIN\User&lt;/code&gt;. In the Power BI Service, USERNAME and USERPRINCIPALNAME both return the user principal name. So a rule built and tested against a domain-style value in Desktop can behave differently once published. Use USERPRINCIPALNAME and store matching values in the mapping table.&lt;/p&gt;

&lt;p&gt;Now the part worth the whole section. This pattern, which reads perfectly sensibly, hands over the entire table to a typo:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IF (
    USERNAME () = "Worker",
    [Type] = "Internal",
    TRUE ()
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Workers see internal rows. Everybody else, meaning managers, sees everything. But "everybody else" is not a list of managers. It is every value that is not exactly the string &lt;code&gt;Worker&lt;/code&gt;, so &lt;code&gt;Wrker&lt;/code&gt; falls into the else branch and returns TRUE for every row. Microsoft's guidance uses this exact example, and its fix is to test for each expected value and make the fall-through deny:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IF (
    USERNAME () = "Worker",
    [Type] = "Internal",
    IF (
        USERNAME () = "Manager",
        TRUE (),
        FALSE ()
    )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The habit underneath it: in a security rule, the last branch is the one that runs when you have not thought of the case. Make it return nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. What View As proves, and what it does not
&lt;/h2&gt;

&lt;p&gt;Desktop has View As on the Modeling ribbon, and the Service has Test as role on the model's Security page. Both are genuinely useful and both prove less than people think.&lt;/p&gt;

&lt;p&gt;What they prove: your DAX filter is valid, it lands on the table you meant, and it propagates through the relationships you expect. That is most of the work, and testing every role this way is not optional.&lt;/p&gt;

&lt;p&gt;What they do not prove, and this is documented rather than folklore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;They cannot tell you a person's workspace role.&lt;/strong&gt; The whole failure in section 4 is invisible to both tools, because both simulate the rule and neither simulates the bypass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For dynamic RLS, the test uses your own identity.&lt;/strong&gt; USERPRINCIPALNAME returns your sign-in name, not the one you are trying to simulate. So a passing test tells you the rule works for you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not every surface is covered.&lt;/strong&gt; Test as role does not work for paginated reports, and it does not validate Q&amp;amp;A visuals, Quick insights or Copilot. It also does not work for DirectQuery models with single sign-on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The test that closes the gap is unglamorous and takes two minutes: put a card on the report showing &lt;code&gt;USERPRINCIPALNAME()&lt;/code&gt;, publish, and have one real person in each audience open it and tell you what it says next to their totals. Microsoft's troubleshooting guidance suggests the same measure, named something like "Who Am I". A real user's session is the only thing that exercises both halves at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Four things RLS does not do
&lt;/h2&gt;

&lt;p&gt;Each of these is somebody's disappointed afternoon.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It does not hide columns or measures.&lt;/strong&gt; Microsoft is direct about it: RLS "filters table rows. They can't be configured to restrict access to model objects, including tables, columns, or measures." If a person can see a row, they can see every column on that row, salary included. The feature for hiding a column is object-level security, and it is separate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It does not let people see a total without the detail.&lt;/strong&gt; Asked whether RLS can hide detailed rows while still showing them in a summary, the answer is no: you secure rows, and users can see either the details or the summary of what they have. Getting a company-wide total next to a personal one needs a separate summary table built for that purpose, not a cleverer rule.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It cannot be overridden by DAX, in either direction.&lt;/strong&gt; This one cuts both ways and is worth knowing. No measure can escape RLS, and Microsoft goes further: a DAX expression "can't even determine that RLS is enforced". So there is no writing a smarter measure to work around it, and equally no accidentally punching a hole in it with ALL or REMOVEFILTERS. If you want the mechanics of what a measure can and cannot see, that is &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/powerbi-calculate/" rel="noopener noreferrer"&gt;CALCULATE and filter context&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It does not follow the model everywhere.&lt;/strong&gt; Live connections to Analysis Services enforce security in that model rather than in Power BI, and the Security option does not even appear. Publish to web does not work with RLS at all. Service principals cannot be added to a role, so an app authenticating that way is not filtered by one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The full before and after
&lt;/h2&gt;

&lt;p&gt;Same model, same rule, same person. One dropdown apart.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Before: Priya is a workspace Member&lt;/th&gt;
&lt;th&gt;After: Priya is a Viewer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Role exists in the model&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Priya is assigned to it&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;View As in Desktop&lt;/td&gt;
&lt;td&gt;Passes, shows 3,040&lt;/td&gt;
&lt;td&gt;Passes, shows 3,040&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test as role in the Service&lt;/td&gt;
&lt;td&gt;Passes, shows 3,040&lt;/td&gt;
&lt;td&gt;Passes, shows 3,040&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What Priya actually sees&lt;/td&gt;
&lt;td&gt;9,890, all sixteen orders&lt;/td&gt;
&lt;td&gt;3,040, her four orders&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can she still open the reports&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can she still build her own&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes, with Build permission, still filtered&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anything on screen that says so&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last two lines are the point of the page. Every test you can run from your own desk passes in both columns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Edge cases that return the wrong rows quietly
&lt;/h2&gt;

&lt;p&gt;These produce data rather than an error, which is the only kind that matters here.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;An inactive relationship.&lt;/strong&gt; RLS filters only travel along active relationships. A model with a second, inactive date relationship will not carry the security filter down it, so a visual built on that path can show rows the role never permitted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A bi-directional relationship you assumed was enough.&lt;/strong&gt; Security filtering runs one way by default, whether or not the relationship itself is bi-directional. Propagating security both ways is a separate checkbox on the relationship, "Apply security filter in both directions", and it is off until you tick it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A user in a security group you forgot about.&lt;/strong&gt; The second role does not have to be assigned deliberately. Group membership puts people into roles, roles are additive, and the resulting access is the union.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A changed name.&lt;/strong&gt; Dynamic RLS matches a stored string against a live sign-in name. Somebody's account changes and their row stops matching, so they abruptly see nothing, and it looks like an outage rather than a mapping problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An import model whose source already has security.&lt;/strong&gt; Importing does not bring the source's security rules with it: on import you must define RLS in Power BI. With DirectQuery the source's own rules apply, which is a genuinely different arrangement to reason about.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A calculated column doing the filtering job.&lt;/strong&gt; Rules belong in the role. Building the same logic into a column and hoping it filters is a category error, and which of the two you should be writing is decided in &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/powerbi-measures-vs-columns/" rel="noopener noreferrer"&gt;calculated column vs measure&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this works
&lt;/h2&gt;

&lt;p&gt;The design reason for the bypass is defensible once you say it plainly: people who can edit the model can already see the data, because editing a model means reading it. A Contributor can open the semantic model, write their own measure and read any number in it. Filtering their report while leaving the model open to them would be a lock on a door with no wall attached. Microsoft states the boundary in one line: "RLS only restricts data access for users with Viewer permissions. It doesn't apply to workspace Admin, Member, or Contributor roles."&lt;/p&gt;

&lt;p&gt;So the control is real, and its scope is a workspace role. That makes this an instance of the oldest rule in the field. Saltzer and Schroeder set out eight design principles for protecting information, and the one this page keeps running into is least privilege: every user should operate with the smallest set of privileges the job needs (Saltzer &amp;amp; Schroeder, 1975, &lt;em&gt;Proceedings of the IEEE&lt;/em&gt; , 63(9), 1278–1308). Priya was given edit rights so she could read four reports. The extra privilege was not the point of the decision and it silently outranked the security rule.&lt;/p&gt;

&lt;p&gt;Their other principle worth naming here is fail-safe defaults: base access on permission rather than exclusion, so the default outcome is no access. That is exactly the shape of the additive-roles behavior. A rule returning FALSE grants nothing; it does not deny. Union it with a rule that grants everything and everything is what you get, which is why the fix is one role per person rather than a carefully layered stack of them.&lt;/p&gt;

&lt;p&gt;One note on the cheat sheet below. It is built to be covered and recalled rather than read, because testing yourself on material transfers to new situations better than restudying it, which matters here since you will be applying this to workspaces that look nothing like the one above (Butler, 2010, &lt;em&gt;Journal of Experimental Psychology: Learning, Memory, and Cognition&lt;/em&gt; , 36(5), 1118–1133).&lt;/p&gt;

&lt;h2&gt;
  
  
  Using this on your own model
&lt;/h2&gt;

&lt;p&gt;Auditing every role in an inherited tenant is miserable and you will stop at the second workspace. Do this instead, in order.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Open the workspace access list before you open the model.&lt;/strong&gt; Read the roles, not the names. Everyone listed as Admin, Member or Contributor sees every row, whatever your rules say. That list is the answer to "who can see everything", and it takes thirty seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Move every consumer to Viewer.&lt;/strong&gt; If somebody needs to build on the model, Viewer plus Build permission does that and stays filtered. Editing rights are for people who edit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Count roles per person, not people per role.&lt;/strong&gt; Anybody in two roles is getting the union. Expand the security groups, because that is where the second role hides.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the last branch of every dynamic rule.&lt;/strong&gt; If the fall-through is TRUE, an unexpected sign-in name returns the whole table. Make it FALSE and test with a value you know is wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publish a "Who am I" card.&lt;/strong&gt; One card with &lt;code&gt;USERPRINCIPALNAME()&lt;/code&gt; next to the totals, and one real person from each audience telling you what it says. This is the only test that covers the workspace role and the rule at the same time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write down who each role is for.&lt;/strong&gt; One sentence per role, stored with the model. Roles outlive the person who wrote them, and a role nobody can explain is a role nobody will dare remove.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you have paper nearby, one optional sketch makes the additive rule stick. Draw two overlapping circles, label one East and one Lamps, and shade the small piece where they overlap. That is what most people expect two roles to give. Now shade both whole circles. That is what two roles actually give. You will not get this one wrong again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More detail on this, and more like it.&lt;/strong&gt; Every how-to sits in one place on the &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/" rel="noopener noreferrer"&gt;guides index&lt;/a&gt;: Power BI, SQL, Excel, and the working habits around them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The whole thing on one screen
&lt;/h2&gt;

&lt;p&gt;This is the retrieval sheet. Cover the right column, work down the left, and say each answer out loud before you check it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Thing&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What a role is&lt;/td&gt;
&lt;td&gt;A DAX filter asked of every row. TRUE stays, FALSE is removed completely.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where roles are defined&lt;/td&gt;
&lt;td&gt;Power BI Desktop, Modeling › Manage roles.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where members are assigned&lt;/td&gt;
&lt;td&gt;The Power BI Service, semantic model › Security. Never in Desktop.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who RLS applies to&lt;/td&gt;
&lt;td&gt;Viewers, and app consumers. That is the whole list.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who it does not apply to&lt;/td&gt;
&lt;td&gt;Workspace Admin, Member and Contributor. By design, with no warning.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Viewer plus Build&lt;/td&gt;
&lt;td&gt;Still filtered, including through Analyze in Excel.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two roles&lt;/td&gt;
&lt;td&gt;A union. More data, not less. "Once denied always denied" does not apply.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The design rule&lt;/td&gt;
&lt;td&gt;One person, one role. Put both filters in the same role instead.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A user in no role&lt;/td&gt;
&lt;td&gt;Usually sees nothing, because no role permits any row.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Static rule&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[Region] = "East"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dynamic rule&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;[UserEmail] = USERPRINCIPALNAME()&lt;/code&gt;, against a mapping table.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;USERNAME vs USERPRINCIPALNAME&lt;/td&gt;
&lt;td&gt;In Desktop USERNAME gives DOMAIN\User. In the Service both give the UPN.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The dangerous else&lt;/td&gt;
&lt;td&gt;A fall-through of TRUE hands the table to any typo. End on FALSE.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What View As proves&lt;/td&gt;
&lt;td&gt;The rule and its propagation. Not the workspace role, not another person's identity.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filter propagation&lt;/td&gt;
&lt;td&gt;Active relationships only. Security both ways is a separate checkbox.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where to put the rule&lt;/td&gt;
&lt;td&gt;On the dimension table, and let it flow to the fact table.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What RLS cannot hide&lt;/td&gt;
&lt;td&gt;Columns and measures. That is object-level security.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Role names&lt;/td&gt;
&lt;td&gt;No commas allowed.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The one habit to keep.&lt;/strong&gt; Read the workspace access list before you trust the rule. Anyone on it above Viewer sees every row, and no amount of correct DAX changes that. It is the first thing to check and the last thing anyone thinks of, and it takes thirty seconds.&lt;/p&gt;

&lt;p&gt;One last thought, and I would genuinely like other people's answers. The version of this I found hardest to explain afterwards was a model where the rules were perfect and three people had been added to the workspace as Contributors during a handover, for two weeks, eighteen months earlier. Nobody had taken them out and nobody had noticed, because nothing about the reports looked different. What is the longest one of these has been open in a tenant you inherited?&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Saltzer, J. H., &amp;amp; Schroeder, M. D. (1975). The protection of information in computer systems. &lt;em&gt;Proceedings of the IEEE&lt;/em&gt; , 63(9), 1278–1308.&lt;/li&gt;
&lt;li&gt;Butler, A. C. (2010). Repeated testing produces superior transfer of learning relative to repeated studying. &lt;em&gt;Journal of Experimental Psychology: Learning, Memory, and Cognition&lt;/em&gt; , 36(5), 1118–1133.&lt;/li&gt;
&lt;li&gt;Microsoft. Row-level security (RLS) with Power BI, and Row-level security (RLS) guidance in Power BI Desktop, and USERPRINCIPALNAME function (DAX). Microsoft Learn. All quotations above are taken from these pages.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on Analyst Prep Kit: &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/powerbi-row-level-security/" rel="noopener noreferrer"&gt;Row-Level Security in Power BI: Who Gets Filtered and Who Does Not&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Visit the site for more beginner data analysis guides and free resources: &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/" rel="noopener noreferrer"&gt;the full guide archive&lt;/a&gt; covers SQL, Excel, Power BI, Tableau, Python and statistics, and the &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/" rel="noopener noreferrer"&gt;practice kits&lt;/a&gt; run in your browser with nothing to install.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If it was useful: &lt;a href="https://buymeacoffee.com/michaelnocito" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>powerbi</category>
      <category>dataanalysis</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Absolute vs Relative References in Excel: What the Dollar Sign Locks</title>
      <dc:creator>Michael Nocito</dc:creator>
      <pubDate>Sun, 06 Sep 2026 13:00:06 +0000</pubDate>
      <link>https://dev.to/michaelnocito/absolute-vs-relative-references-in-excel-what-the-dollar-sign-locks-25bf</link>
      <guid>https://dev.to/michaelnocito/absolute-vs-relative-references-in-excel-what-the-dollar-sign-locks-25bf</guid>
      <description>&lt;p&gt;By &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/about.html" rel="noopener noreferrer"&gt;Michael Nocito&lt;/a&gt;, data analyst · Published August 10, 2026&lt;/p&gt;

&lt;p&gt;By the end of this page you can copy a formula anywhere in a sheet and say in advance which parts of it will move. You will be able to pin a rate so a whole column can use it, fill a nine-cell grid from a single formula, and recognize the copied column that is wrong even though its first row is right. It takes about twenty minutes. Every number below is printed next to the arithmetic that produced it, so you can check any of them.&lt;/p&gt;

&lt;p&gt;Here is the move. When a formula points at a cell that must not move as the formula is copied, put a dollar sign in front of both halves of that address: &lt;code&gt;=D2*$G$1&lt;/code&gt;. Click the reference in the formula bar and press F4 to put the signs in. That is the whole mechanic. A dollar sign means "this part stays where it is".&lt;/p&gt;

&lt;p&gt;The short version: Excel does not store the address you typed. It stores the distance from your formula to the cell it points at. A dollar sign replaces that distance with a fixed address.&lt;/p&gt;

&lt;p&gt;That is the idea everything else on the page rests on, so it gets the picture.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The original carries a diagram here. In words: Two diagrams sit side by side. Each one shows a short column of four formula cells on the left and a matching column of four target cells on the right, with arrows running between them. The target cells are drawn identically in both diagrams: the top one is shaded because it holds a value, and the three below it have dashed empty outlines because nothing is stored in them. The only difference between the two diagrams is where the arrows go. Above the left diagram the formula reads equals D2 times G1, with no dollar signs, and its four arrows run straight across in parallel, each one reaching a different target cell, so three of the four land on empty outlines. Above the right diagram the same formula appears with dollar signs, reading equals D2 times dollar G dollar one, and its four arrows all curve upward and meet at the single shaded cell at the top, leaving the three empty ones untouched. Four separate landings on the left, one shared landing on the right.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Every number on this page is checkable.&lt;/strong&gt; The worked example uses the sixteen-row orders table that runs through this set of guides, and every result is printed beside its arithmetic in the form 1,200 × 0.08 = 96.00. If a number here does not match the arithmetic next to it, I got it wrong and you caught it.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The fork: should this part move, or stay?
&lt;/h2&gt;

&lt;p&gt;Before the explanation, one question worth answering from memory. Think of a formula you have dragged down a column. Say which parts of it needed to point somewhere new on each row, and which parts needed to keep pointing at the same place.&lt;/p&gt;

&lt;p&gt;That question is the whole decision, and it gets asked once per reference in the formula. There are two possible answers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Answer one: it should move.&lt;/strong&gt; The reference points at something that belongs to this row. The amount for this order, the date of this order, the name on this line. If that is true, you want the reference to shift as the formula is copied, and you leave it alone. This is called a &lt;strong&gt;relative&lt;/strong&gt; reference, and it is what you get by default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Answer two: it should stay.&lt;/strong&gt; The reference points at one thing that every row shares. A commission rate, a tax percentage, an exchange rate, the top-left corner of a lookup table. If that is true, you want every copy of the formula to ask the same cell, and you write it as &lt;code&gt;$G$1&lt;/code&gt;. This is an &lt;strong&gt;absolute&lt;/strong&gt; reference.&lt;/p&gt;

&lt;p&gt;What decides between them is not the formula and not the cell. It is the sentence you would say out loud about that reference. "The amount on this row" moves. "The rate" stays.&lt;/p&gt;

&lt;p&gt;It matters because getting it backwards does not usually produce an error message. It produces a number, and the next section is about why that number is often believable.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. What a relative reference actually stores
&lt;/h2&gt;

&lt;p&gt;Here is the fact that makes the rest of the page obvious rather than memorized. Type &lt;code&gt;=D2&lt;/code&gt; into cell E2. Excel does not remember "D2". It remembers "one cell to my left".&lt;/p&gt;

&lt;p&gt;Copy that formula to E5 and it still means one cell to my left, which is now D5. The formula did not know it said D2. It only ever knew the distance.&lt;/p&gt;

&lt;p&gt;Say it out loud, because it is the sentence the whole topic hangs off: a reference is a direction and a distance, not an address.&lt;/p&gt;

&lt;p&gt;That is exactly why filling a column works at all. You write one formula for row 2 and copy it to row 17, and each copy quietly points at its own row, because "one cell to my left" is true on every row.&lt;/p&gt;

&lt;p&gt;A dollar sign switches off that behavior for the half of the address it sits in front of. &lt;code&gt;$G&lt;/code&gt; means column G, always, no matter where this formula ends up. &lt;code&gt;$1&lt;/code&gt; means row 1, always. Put both in and the reference stops being a distance and becomes a place.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The rate in one cell, and the 513.60 it cost
&lt;/h2&gt;

&lt;p&gt;Here is the failure, worked end to end on real numbers.&lt;/p&gt;

&lt;p&gt;The orders table sits in A1:D17. Column D holds the amount. Off to the side is a small block of assumptions, which is where a careful person puts rates so they are visible and editable instead of buried inside formulas.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cell&lt;/th&gt;
&lt;th&gt;Label&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;G1&lt;/td&gt;
&lt;td&gt;Commission rate&lt;/td&gt;
&lt;td&gt;0.08&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;G2&lt;/td&gt;
&lt;td&gt;Tax rate&lt;/td&gt;
&lt;td&gt;0.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;G3&lt;/td&gt;
&lt;td&gt;Uplift factor&lt;/td&gt;
&lt;td&gt;1.05&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now commission per order goes in column E. In E2 you write &lt;code&gt;=D2*G1&lt;/code&gt; and fill it down to E17.&lt;/p&gt;

&lt;p&gt;The first row is right. E2 reads 96.00, and 1,200 × 0.08 = 96.00. So the formula gets a nod and the fill gets trusted.&lt;/p&gt;

&lt;p&gt;Here is what the next three rows did.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cell&lt;/th&gt;
&lt;th&gt;What it became&lt;/th&gt;
&lt;th&gt;Arithmetic&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;E2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;=D2*G1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1,200 × 0.08&lt;/td&gt;
&lt;td&gt;96.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;=D3*G2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;950 × 0.20&lt;/td&gt;
&lt;td&gt;190.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;=D4*G3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1,480 × 1.05&lt;/td&gt;
&lt;td&gt;1,554.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E5&lt;/td&gt;
&lt;td&gt;&lt;code&gt;=D5*G4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;720 × nothing&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E6:E17&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;=D6*G5&lt;/code&gt; and down&lt;/td&gt;
&lt;td&gt;each amount × nothing&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The column totals &lt;strong&gt;1,840.00&lt;/strong&gt;. It should total &lt;strong&gt;1,326.40&lt;/strong&gt; , because the sixteen amounts add up to 16,580 and 16,580 × 0.08 = 1,326.40. The sheet is over by 513.60, and 1,840.00 minus 1,326.40 = 513.60.&lt;/p&gt;

&lt;p&gt;Before reading on, say why row 2 came out right. Getting this in your own words is worth more than the fix itself.&lt;/p&gt;

&lt;p&gt;Row 2 is right because row 2 is the row you wrote the formula for. The distance from E2 to G1 is up one, right two. On row 3 that same distance lands on G2, which is the tax rate. On row 4 it lands on the uplift factor. From row 5 down it lands in empty space, and an empty cell is treated as zero in arithmetic.&lt;/p&gt;

&lt;p&gt;Two of those wrong numbers, 190.00 and 1,554.00, look like money. That is the part worth remembering. This mistake does not announce itself, and the one row a reviewer usually checks is the one row that is correct.&lt;/p&gt;

&lt;p&gt;The fix is one keystroke. In E2, click on the &lt;code&gt;G1&lt;/code&gt; and press F4, which turns it into &lt;code&gt;$G$1&lt;/code&gt;, then fill down again.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=D2*$G$1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now every row asks G1, the column totals 1,326.40, and changing the rate in G1 updates all sixteen rows at once. That second part is the real reward. The rate is in a cell someone can type into, which is what makes the sheet a model instead of a printout.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. F4, and the four states in order
&lt;/h2&gt;

&lt;p&gt;You almost never type dollar signs. You put the cursor on a reference and press F4, which cycles through the four possible states. Microsoft documents the order, and it is worth knowing because it means one extra press gets you where you want rather than starting over.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Press&lt;/th&gt;
&lt;th&gt;Looks like&lt;/th&gt;
&lt;th&gt;What is locked&lt;/th&gt;
&lt;th&gt;Use it when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;$A$1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Column and row&lt;/td&gt;
&lt;td&gt;One cell every copy must ask. Rates, thresholds, a lookup range.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;A$1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Row only&lt;/td&gt;
&lt;td&gt;Filling across and down, where the anchor lives in one row.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;$A1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Column only&lt;/td&gt;
&lt;td&gt;Filling across and down, where the anchor lives in one column.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;A1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Back to a plain relative reference.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On a Mac the same toggle is Cmd+T, and F4 also works if your function keys are set up for it.&lt;/p&gt;

&lt;p&gt;One thing that confuses people on Windows: F4 only cycles references when the cursor is on a reference inside a formula. Anywhere else it repeats your last action, which is a genuinely useful key for a different job.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Mixed: lock one half, free the other
&lt;/h2&gt;

&lt;p&gt;Before the explanation, a prediction. If &lt;code&gt;$G$1&lt;/code&gt; pins both halves and &lt;code&gt;G1&lt;/code&gt; pins neither, say what you think &lt;code&gt;G$1&lt;/code&gt; pins.&lt;/p&gt;

&lt;p&gt;The row. &lt;code&gt;G$1&lt;/code&gt; is free to move sideways and stuck on row 1. Its mirror, &lt;code&gt;$G1&lt;/code&gt;, is stuck in column G and free to move up and down.&lt;/p&gt;

&lt;p&gt;These are called mixed references, and they exist for one situation: a formula that gets copied in two directions at once. When you only ever fill down a column, relative and fully absolute cover everything you need. The moment you fill across as well, you usually want one half of the address pinned and the other half loose.&lt;/p&gt;

&lt;p&gt;The rule of thumb is short. Pin the part that must not change in the direction you are copying.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your anchors sit in a &lt;strong&gt;row&lt;/strong&gt; across the top, and you copy down: lock the row, &lt;code&gt;G$1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Your anchors sit in a &lt;strong&gt;column&lt;/strong&gt; down the side, and you copy across: lock the column, &lt;code&gt;$D2&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Both at once is the grid in the next section, and it needs one of each.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Nine answers from one formula
&lt;/h2&gt;

&lt;p&gt;Finance wants the commission bill at three possible rates, for the first three orders, so they can pick one. That is nine numbers. It is also one formula.&lt;/p&gt;

&lt;p&gt;Put the three rates across the top, in J1, K1 and L1: 0.06, 0.08 and 0.10. The amounts are already down column D. In J2 write this, then fill it right to L2 and down to row 4.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=$D2*J$1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Read it as two decisions rather than as punctuation. &lt;code&gt;$D2&lt;/code&gt; is locked to column D and free on the row, because the amount is always in D but changes row by row. &lt;code&gt;J$1&lt;/code&gt; is locked to row 1 and free on the column, because the rate is always in row 1 but changes column by column.&lt;/p&gt;

&lt;p&gt;Here is what the nine cells hold, with the arithmetic.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Amount&lt;/th&gt;
&lt;th&gt;At 0.06&lt;/th&gt;
&lt;th&gt;At 0.08&lt;/th&gt;
&lt;th&gt;At 0.10&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1,200&lt;/td&gt;
&lt;td&gt;72.00 (1,200 × 0.06)&lt;/td&gt;
&lt;td&gt;96.00 (1,200 × 0.08)&lt;/td&gt;
&lt;td&gt;120.00 (1,200 × 0.10)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;950&lt;/td&gt;
&lt;td&gt;57.00 (950 × 0.06)&lt;/td&gt;
&lt;td&gt;76.00 (950 × 0.08)&lt;/td&gt;
&lt;td&gt;95.00 (950 × 0.10)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,480&lt;/td&gt;
&lt;td&gt;88.80 (1,480 × 0.06)&lt;/td&gt;
&lt;td&gt;118.40 (1,480 × 0.08)&lt;/td&gt;
&lt;td&gt;148.00 (1,480 × 0.10)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Nine correct answers, one formula, typed once. If you get a grid like this wrong, the symptom is recognizable: the whole first row or the whole first column is right and everything else is nonsense, because the one copy you checked was the one that needed no shifting.&lt;/p&gt;

&lt;p&gt;Now picture this on your own numbers. Take a table you actually use, put two or three scenario values across the top of a blank area, and imagine which half of each address you would pin. You do not have to build it. Just decide, for one formula, which two dollar signs you would place and where.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Four things the dollar sign does not do
&lt;/h2&gt;

&lt;p&gt;Each of these catches somebody, and three of them are only visible after the damage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It does not change the answer where you typed it.&lt;/strong&gt; &lt;code&gt;=D2*G1&lt;/code&gt; and &lt;code&gt;=D2*$G$1&lt;/code&gt; return exactly the same number in E2. The difference appears only when the formula is copied somewhere else. That is why testing one cell proves nothing about a fill.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It does not protect the cell from editing.&lt;/strong&gt; Locking a reference and locking a cell are unrelated. Stopping people from typing over G1 is sheet protection, which is a different feature in a different menu.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It does not stop a reference changing when the target physically moves.&lt;/strong&gt; This one surprises people who think absolute means permanent. Insert a row above row 1, and every &lt;code&gt;$G$1&lt;/code&gt; in the workbook quietly rewrites itself to &lt;code&gt;$G$2&lt;/code&gt;, because Excel is following the value you pointed at. Absolute means "does not shift when the formula is copied", not "never changes".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It does not survive a cut.&lt;/strong&gt; Cutting the anchor cell with Ctrl+X and pasting it elsewhere drags every reference to it along, dollar signs and all. Copying it does not. If a model breaks the day someone tidied the layout, this is usually why.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. When you need no dollar sign at all
&lt;/h2&gt;

&lt;p&gt;There are two ways to get the same protection without any punctuation, and both read better afterwards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Name the cell.&lt;/strong&gt; Select G1, type &lt;code&gt;CommissionRate&lt;/code&gt; into the Name Box beside the formula bar, and press Enter. Now the formula is &lt;code&gt;=D2*CommissionRate&lt;/code&gt;, which is already absolute, and which says what it means to anyone who opens the file next year. Defined names do not shift when a formula is copied.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Put the data in a table.&lt;/strong&gt; Inside an &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/excel-tables/" rel="noopener noreferrer"&gt;Excel Table created with Ctrl+T&lt;/a&gt;, a column reference is written &lt;code&gt;[@Amount]&lt;/code&gt; and it means "the amount on this row" by name rather than by distance. There is nothing to lock and nothing to get backwards, and the reference keeps working when columns are inserted. This is one of the better reasons to press Ctrl+T on a range you are about to write formulas against.&lt;/p&gt;

&lt;p&gt;Neither one removes the need to understand the dollar sign, because you will read other people's workbooks for the rest of your career. But for the sheets you build, a named rate is easier to be right about than a pinned address.&lt;/p&gt;

&lt;h2&gt;
  
  
  The full before and after
&lt;/h2&gt;

&lt;p&gt;Same table, same rate, same fill. One character apart.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Before: &lt;code&gt;=D2*G1&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;After: &lt;code&gt;=D2*$G$1&lt;/code&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Row 2&lt;/td&gt;
&lt;td&gt;96.00&lt;/td&gt;
&lt;td&gt;96.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Row 3&lt;/td&gt;
&lt;td&gt;190.00 (used the tax rate)&lt;/td&gt;
&lt;td&gt;76.00 (950 × 0.08)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Row 4&lt;/td&gt;
&lt;td&gt;1,554.00 (used the uplift)&lt;/td&gt;
&lt;td&gt;118.40 (1,480 × 0.08)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rows 5 to 17&lt;/td&gt;
&lt;td&gt;0.00 (pointed at empty cells)&lt;/td&gt;
&lt;td&gt;Each amount × 0.08&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Column total&lt;/td&gt;
&lt;td&gt;1,840.00&lt;/td&gt;
&lt;td&gt;1,326.40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Change the rate in G1&lt;/td&gt;
&lt;td&gt;Three rows react, thirteen do not&lt;/td&gt;
&lt;td&gt;All sixteen rows react&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What a reviewer sees first&lt;/td&gt;
&lt;td&gt;A correct first row&lt;/td&gt;
&lt;td&gt;A correct first row&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last line is the point of the whole page. Both versions pass the check most people run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Edge cases that break a copied formula quietly
&lt;/h2&gt;

&lt;p&gt;These are the ones that produce a number rather than an error.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A lookup range with no dollar signs.&lt;/strong&gt; &lt;code&gt;=VLOOKUP(A2,H2:I50,2,FALSE)&lt;/code&gt; filled down becomes &lt;code&gt;H3:I51&lt;/code&gt;, then &lt;code&gt;H4:I52&lt;/code&gt;. The range slides off the bottom of your lookup table and the last rows return not-found for no visible reason. Lock it: &lt;code&gt;$H$2:$I$50&lt;/code&gt;. There is more on this in &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/vlookup-vs-xlookup/" rel="noopener noreferrer"&gt;VLOOKUP vs XLOOKUP&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filling right when you only tested down.&lt;/strong&gt; A column-only test passes, then somebody drags the same formula sideways and the row reference was never pinned. Test a fill in both directions if the formula might be copied in both.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A mixed reference on the wrong axis.&lt;/strong&gt; &lt;code&gt;$D2&lt;/code&gt; where you meant &lt;code&gt;D$2&lt;/code&gt; still returns numbers. They are just the wrong numbers, taken from a plausible neighbor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rows inserted inside a locked range.&lt;/strong&gt; &lt;code&gt;$H$2:$I$50&lt;/code&gt; expands to &lt;code&gt;$H$2:$I$51&lt;/code&gt; when a row is inserted inside it, which is usually what you want. Rows added at the bottom, outside the range, are not picked up at all. A table reference avoids the whole question.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conditional formatting uses the same mechanism.&lt;/strong&gt; A formula rule is written once for the top-left cell of the selection and filled across the rest, which is why &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/excel-conditional-formatting/" rel="noopener noreferrer"&gt;one dollar sign turns a highlighted cell into a highlighted row&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The value that never moves anyway.&lt;/strong&gt; If a rate appears in exactly one formula and nowhere else, a dollar sign is not the fix for anything. The fix is putting the rate in a cell in the first place, so it is visible and editable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this works
&lt;/h2&gt;

&lt;p&gt;The mechanism itself is a design decision with an obvious payoff: storing an offset instead of an address is what lets one typed formula serve sixteen rows. Everything in this topic, including the failure, comes out of that single choice.&lt;/p&gt;

&lt;p&gt;The failure is worth taking seriously, because reference mistakes are one of the plainest examples of a general finding about spreadsheets. They are common, they produce plausible output, and inspection catches fewer of them than people expect. When undergraduate students inspected a spreadsheet seeded with deliberate errors, working alone for forty-five minutes, they found 63% of them on average, and groups of three working together found 83% (Panko, 1999, &lt;em&gt;Journal of Management Information Systems&lt;/em&gt; , 16(2), 159–176). A single reviewer glancing at one row is a long way below either number. The broader review of this literature is worth reading if you build models anybody relies on (Panko, 1998, &lt;em&gt;Journal of End User Computing&lt;/em&gt; , 10(2), 15–21).&lt;/p&gt;

&lt;p&gt;That is the argument for the habit at the end of this page rather than for being more careful. Care is not a control. A total you predicted before you looked is a control, and it is the reason this page keeps printing the arithmetic beside every number.&lt;/p&gt;

&lt;p&gt;One note on the cheat sheet below. It is built to be covered and recalled rather than read, because testing yourself on material transfers to new situations better than restudying it, which matters here since you will be applying this to sheets that look nothing like the one above (Butler, 2010, &lt;em&gt;Journal of Experimental Psychology: Learning, Memory, and Cognition&lt;/em&gt; , 36(5), 1118–1133).&lt;/p&gt;

&lt;h2&gt;
  
  
  Using this on your own workbook
&lt;/h2&gt;

&lt;p&gt;Auditing every reference in an inherited file is miserable and you will give up on the second sheet. Do this instead, in order.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Find the numbers typed inside formulas.&lt;/strong&gt; Anything like &lt;code&gt;*0.08&lt;/code&gt; or &lt;code&gt;*1.2&lt;/code&gt; sitting in a formula is a rate with nowhere to live. Move it to a labeled cell first. The dollar sign question does not even arise until the value has a home.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Say the sentence out loud for each reference.&lt;/strong&gt; "The amount on this row" moves. "The rate" stays. If you cannot say which one it is, that reference is the one to check.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use F4 rather than typing the signs.&lt;/strong&gt; Fewer keystrokes and no chance of putting one in front of the wrong half.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the last row, not the first.&lt;/strong&gt; The first row of a fill is the one that is right in both the working and the broken version. Scroll to the bottom and read one formula there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Predict the column total before you look at it.&lt;/strong&gt; Sixteen orders at eight percent should land near 1,300. A total of 1,840 fails that test in one second, and there is more on where these checks belong in &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/excel-check-your-work/" rel="noopener noreferrer"&gt;checking your work before anyone else does&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Name the anchors you keep reusing.&lt;/strong&gt; A rate used in four formulas is worth a name. A rate used once is not.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you have paper nearby, one optional sketch locks this in for good. Draw two columns of four boxes. On the left write one formula at the top and draw an arrow from each box to where it points when copied down. Do it once with the arrows fanning out to four different boxes, and once with all four arrows meeting at the top box. Then label which picture needed the dollar signs. You will not need to look this up again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More detail on this, and more like it.&lt;/strong&gt; Every how-to sits in one place on the &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/" rel="noopener noreferrer"&gt;guides index&lt;/a&gt;: Excel, SQL, Python, and the working habits around them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The whole thing on one screen
&lt;/h2&gt;

&lt;p&gt;This is the retrieval sheet. Cover the right column, work down the left, and say each answer out loud before you check it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Thing&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What a reference stores&lt;/td&gt;
&lt;td&gt;A direction and a distance, not an address.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;A1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Relative. Moves with the copy, both ways.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;$A$1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Absolute. Every copy asks the same cell.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;A$1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Row locked, column free. Anchors sitting in one row.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;$A1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Column locked, row free. Anchors sitting in one column.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F4 order&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;$A$1&lt;/code&gt;, &lt;code&gt;A$1&lt;/code&gt;, &lt;code&gt;$A1&lt;/code&gt;, &lt;code&gt;A1&lt;/code&gt;. Mac: Cmd+T.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The question to ask&lt;/td&gt;
&lt;td&gt;"The amount on this row" moves. "The rate" stays.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grid, filled both ways&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;=$D2*J$1&lt;/code&gt;. One of each, one formula, nine answers.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Why it survives review&lt;/td&gt;
&lt;td&gt;The first row is correct in the broken version too.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Row 3 reading 190.00&lt;/td&gt;
&lt;td&gt;The reference walked onto the next assumption down.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rows reading 0.00&lt;/td&gt;
&lt;td&gt;The reference walked into empty cells. Empty is zero.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inserting a row&lt;/td&gt;
&lt;td&gt;Rewrites &lt;code&gt;$G$1&lt;/code&gt; to &lt;code&gt;$G$2&lt;/code&gt;. Absolute is not permanent.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cutting the anchor&lt;/td&gt;
&lt;td&gt;Drags every reference with it. Copying does not.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lookup range&lt;/td&gt;
&lt;td&gt;Lock it, or it slides off the bottom of the table.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No dollar sign needed&lt;/td&gt;
&lt;td&gt;A defined name, or &lt;code&gt;[@Amount]&lt;/code&gt; inside a table.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Which row to check&lt;/td&gt;
&lt;td&gt;The last one. The first one is right either way.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The one habit to keep.&lt;/strong&gt; Predict the total before you read it. Sixteen orders at eight percent is roughly thirteen hundred, so 1,840.00 is wrong before you have looked at a single formula. A rough number you committed to in advance catches reference mistakes faster than reading the formulas will, and it costs about four seconds.&lt;/p&gt;

&lt;p&gt;One last thought, and I would genuinely like other people's answers. The most expensive version of this I have seen was a forecast where the growth rate was pinned correctly and the base year was not, so every column quietly grew from the wrong starting point and the shape of the chart still looked right. What is the longest one of these has gone unnoticed in a file you inherited?&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Panko, R. R. (1999). Applying code inspection to spreadsheet testing. &lt;em&gt;Journal of Management Information Systems&lt;/em&gt; , 16(2), 159–176.&lt;/li&gt;
&lt;li&gt;Panko, R. R. (1998). What we know about spreadsheet errors. &lt;em&gt;Journal of End User Computing&lt;/em&gt; , 10(2), 15–21.&lt;/li&gt;
&lt;li&gt;Butler, A. C. (2010). Repeated testing produces superior transfer of learning relative to repeated studying. &lt;em&gt;Journal of Experimental Psychology: Learning, Memory, and Cognition&lt;/em&gt; , 36(5), 1118–1133.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on Analyst Prep Kit: &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/excel-absolute-vs-relative-references/" rel="noopener noreferrer"&gt;Absolute vs Relative References in Excel: What the Dollar Sign Locks&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Visit the site for more beginner data analysis guides and free resources: &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/" rel="noopener noreferrer"&gt;the full guide archive&lt;/a&gt; covers SQL, Excel, Power BI, Tableau, Python and statistics, and the &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/" rel="noopener noreferrer"&gt;practice kits&lt;/a&gt; run in your browser with nothing to install.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If it was useful: &lt;a href="https://buymeacoffee.com/michaelnocito" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>excel</category>
      <category>dataanalysis</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Tableau Hierarchies: One Drag Puts a Drill-Down on Every Chart</title>
      <dc:creator>Michael Nocito</dc:creator>
      <pubDate>Sat, 05 Sep 2026 13:00:14 +0000</pubDate>
      <link>https://dev.to/michaelnocito/tableau-hierarchies-one-drag-puts-a-drill-down-on-every-chart-32hb</link>
      <guid>https://dev.to/michaelnocito/tableau-hierarchies-one-drag-puts-a-drill-down-on-every-chart-32hb</guid>
      <description>&lt;p&gt;By &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/about.html" rel="noopener noreferrer"&gt;Michael Nocito&lt;/a&gt;, data analyst · Published August 9, 2026&lt;/p&gt;

&lt;p&gt;By the end of this page you can build a hierarchy in one drag, put it in the right order first time, and say exactly how it differs from a group, a set and a bin. Those four get confused constantly, and one sentence tells them apart for good. It's about twelve minutes.&lt;/p&gt;

&lt;p&gt;Here's the thing to do today. In the Data pane, drag Sub-Category and drop it directly on top of Category. Tableau asks for a name, and from that moment every view built on Category carries a small plus icon. Click it and the chart expands into sub-categories, in place, without you building a second sheet.&lt;/p&gt;

&lt;p&gt;The short version: a hierarchy organizes fields into levels. A group, a set and a bin organize the values inside one field. That's the whole distinction.&lt;/p&gt;

&lt;p&gt;What a level actually costs you is the part people are surprised by, so it gets the picture.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The original carries a diagram here. In words: Three tiers stacked vertically and widening as they descend. The top tier shows three wide boxes labeled Category. The middle tier shows nine narrower boxes labeled Sub-Category, with lines fanning out from each box above to three below it. The bottom tier shows a long row of many very thin boxes labeled Product, again fanning out from each box above. A plus symbol sits at the left of the top tier and a minus symbol at the left of the bottom tier, marking the drill controls that move between them. The widening shape shows that every step down the hierarchy multiplies the number of marks in the view, so the bottom level holds far more rows than the top.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;This is on the certification.&lt;/strong&gt; Hierarchies sit in Section 2, Exploring and Analyzing Data, which is 37% of the Tableau Desktop Foundations exam and the largest section on it. The &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/tableau-cert/" rel="noopener noreferrer"&gt;certification kit&lt;/a&gt; drills them alongside groups, sets and bins, which is where the wrong answers usually come from.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. What a hierarchy actually is
&lt;/h2&gt;

&lt;p&gt;Before the explanation: you build a hierarchy from Category and Sub-Category. What happens to the two original fields?&lt;/p&gt;

&lt;p&gt;Nothing. They're both still there, still usable on their own, now sitting inside a named hierarchy in the Data pane. A hierarchy doesn't consume the fields it's made of.&lt;/p&gt;

&lt;p&gt;That's the first thing to hold onto. A hierarchy is an arrangement of existing fields into levels, ordered from broad at the top to specific at the bottom. It creates no new field and changes no data. It records a relationship you already knew about: every sub-category belongs to exactly one category.&lt;/p&gt;

&lt;p&gt;What you get in exchange is a control. Once a hierarchy field is in a view, Tableau puts a small plus or minus icon on the pill, and clicking it adds or removes a level of detail in place. The chart you already built becomes a chart the reader can open up.&lt;/p&gt;

&lt;p&gt;That is a bigger deal than it sounds, because the alternative is building a second sheet. Without a hierarchy, "sales by category" and "sales by sub-category" are two views someone has to navigate between. With one, they're the same view at two depths.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The sentence to remember.&lt;/strong&gt; A hierarchy organizes fields into levels and hands the reader a drill control. It does not change the fields, and it does not change the data.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Building one, and getting the order right
&lt;/h2&gt;

&lt;p&gt;Before the steps: you drop Category onto Product instead of the other way round. What does the reader get?&lt;/p&gt;

&lt;p&gt;A drill that starts specific and gets vaguer, which reads as broken even though nothing errored. Order is the only decision in building a hierarchy, and it's the one people get wrong.&lt;/p&gt;

&lt;p&gt;Building it, per Tableau's documentation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the Data pane, drag a field and drop it directly on top of another field.&lt;/li&gt;
&lt;li&gt;Tableau asks you to name the hierarchy. Give it a name a reader would recognize.&lt;/li&gt;
&lt;li&gt;Drag additional fields into the hierarchy as needed, to add more levels.&lt;/li&gt;
&lt;li&gt;Re-order fields in the hierarchy by dragging them to a new position.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the field you want lives inside a folder, the drag is awkward, so use the other route: right-click the field and select Create Hierarchy.&lt;/p&gt;

&lt;p&gt;Now the order rule, and it's one line. &lt;strong&gt;Coarse at the top, fine at the bottom.&lt;/strong&gt; Each level down must be contained by the level above it.&lt;/p&gt;

&lt;p&gt;Say out loud why containment is the test, before reading on. It's because drilling means "show me what this is made of". Category contains sub-categories, so opening a category to reveal its sub-categories makes sense. Product does not contain categories, so the reverse drill has nothing sensible to reveal, and Tableau will still let you build it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hierarchy&lt;/th&gt;
&lt;th&gt;Order&lt;/th&gt;
&lt;th&gt;Works because&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Product&lt;/td&gt;
&lt;td&gt;Category → Sub-Category → Product Name&lt;/td&gt;
&lt;td&gt;Each level is contained by the one above&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Location&lt;/td&gt;
&lt;td&gt;Country → State → City → Postcode&lt;/td&gt;
&lt;td&gt;Same containment, and the one everyone recognizes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Date&lt;/td&gt;
&lt;td&gt;Year → Quarter → Month → Day&lt;/td&gt;
&lt;td&gt;Tableau builds this one for you on any date field&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Org&lt;/td&gt;
&lt;td&gt;Region → Manager → Rep&lt;/td&gt;
&lt;td&gt;Contained, as long as a rep reports to one manager&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That last row carries the caveat worth knowing. Containment has to be true in your data, not just in the org chart. If a rep is shared across two managers, drilling will show their sales under both, and the levels will not add up to the total. Check before you build.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Hierarchy against group, set and bin
&lt;/h2&gt;

&lt;p&gt;Before the table: hierarchies, groups, sets and bins all sound like ways of organizing data. What single question separates them?&lt;/p&gt;

&lt;p&gt;Whether they work on fields or on the values inside one field. A hierarchy is the only one of the four that arranges several fields. The other three reshape the values inside a single field, and they do it in three different ways.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Works on&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;What you get&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hierarchy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Several fields&lt;/td&gt;
&lt;td&gt;Orders them into levels&lt;/td&gt;
&lt;td&gt;A drill control. Nothing changes until someone clicks.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Group&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One field's members&lt;/td&gt;
&lt;td&gt;Combines several members into one bigger member&lt;/td&gt;
&lt;td&gt;Fewer, coarser categories&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Set&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One field's members&lt;/td&gt;
&lt;td&gt;Labels every member IN or OUT&lt;/td&gt;
&lt;td&gt;Two groups you can compare&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bin&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One measure's values&lt;/td&gt;
&lt;td&gt;Cuts a continuous range into equal-width buckets&lt;/td&gt;
&lt;td&gt;A dimension you can count, like a histogram&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two of those pairs get confused more than the others, so here are both in a sentence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hierarchy against group.&lt;/strong&gt; They feel similar because both make things coarser. A group makes Sub-Category coarser by merging its members. A hierarchy leaves Sub-Category alone and lets a reader step up to Category, which was already a separate field. If the coarser level already exists as a field, you want a hierarchy. If it doesn't exist and you have to invent it, you want a group.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Set against group.&lt;/strong&gt; A group combines members into bigger members and everything stays in the view. A &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/tableau-sets/" rel="noopener noreferrer"&gt;set&lt;/a&gt; splits members into IN and OUT so you can compare the two halves. Different jobs entirely, which the sets guide goes through properly.&lt;/p&gt;

&lt;p&gt;Picture your own data pane for a moment. How many of your dimensions are really levels of one thing that nobody has connected yet? Region and Country. Manager and Rep. Those pairs are hierarchies waiting to be dragged together.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. What the plus and minus controls do
&lt;/h2&gt;

&lt;p&gt;Before the explanation: you click the plus icon on a hierarchy pill. Does the view replace the current level or add to it?&lt;/p&gt;

&lt;p&gt;It adds. Tableau's wording is that you can "drill up or down in the hierarchy to add or subtract more levels of detail", and the key word is add. Category stays on the shelf and Sub-Category joins it, so you get sub-categories nested inside their categories rather than a flat list of sub-categories.&lt;/p&gt;

&lt;p&gt;That distinction is worth a moment. Drilling down gives you both levels, with the parent still grouping the children. If you wanted only sub-categories, you don't drill, you swap the field.&lt;/p&gt;

&lt;p&gt;Two practical consequences follow from the picture at the top of this page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Marks multiply, they don't shift.&lt;/strong&gt; Three categories drilling into nine sub-categories is three times the marks. Drilling again into thirty-nine products is thirteen times the original. That's fine on a bar chart and painful on a map or a scatter plot, so check what the chart does at the bottom level before you ship the drill.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build the view at the level you want people to land on.&lt;/strong&gt; The default state is whatever you saved, and most readers never click anything. So save it at the top level, and let the drill be for the people who want more.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Details people miss
&lt;/h2&gt;

&lt;p&gt;Before the list: someone says removing a hierarchy will delete the fields inside it. Are they right?&lt;/p&gt;

&lt;p&gt;No, and this is the fact that stops people experimenting. Right-click the hierarchy and select Remove Hierarchy. Tableau's own description: "The fields in the hierarchy are removed from the hierarchy and the hierarchy disappears from the Data pane." The fields go back to being ordinary fields. Nothing is lost, so a hierarchy is safe to try.&lt;/p&gt;

&lt;p&gt;Five more worth knowing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Date fields get a hierarchy automatically.&lt;/strong&gt; Year, quarter, month, day. You don't build it, and the plus and minus on a date pill are doing exactly what they do on one you made yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A hierarchy belongs to the data source, not the sheet.&lt;/strong&gt; Build it once and every worksheet on that data source can use it. This is the same arrangement as sets, and it's the reason both live in the Data pane.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can drill into one member instead of all of them.&lt;/strong&gt; Clicking the plus on the pill opens every category. Clicking the plus on a single header opens just that one, which is usually what a reader actually wanted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Levels can be reordered after the fact.&lt;/strong&gt; Drag a field to a new position inside the hierarchy. You do not have to remove it and start again, which is what most people do the first time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A hierarchy does not enforce anything.&lt;/strong&gt; It records a containment you believe is true. If your data has a sub-category appearing under two categories, Tableau will show it under both without complaint, and your levels will not sum to the total. That's a data problem the hierarchy will faithfully display rather than catch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this works
&lt;/h2&gt;

&lt;p&gt;Drilling isn't a Tableau invention, it's a general answer to a problem every large dataset has: the view that shows everything shows nothing. Shneiderman set out the pattern that most interactive tools still follow, summarized as overview first, zoom and filter, then details on demand (Shneiderman, 1996, &lt;em&gt;Proceedings of the 1996 IEEE Symposium on Visual Languages&lt;/em&gt; , 336–343). A hierarchy is that pattern made into one control. The overview is the top level, and the detail arrives only when someone asks for it.&lt;/p&gt;

&lt;p&gt;The reason this beats building two sheets is about where the effort sits. Two sheets ask the reader to notice that a second view exists, find it, and hold the first one in memory while looking at it. One view with a drill asks them to click a plus. The information is the same and the work of connecting the two levels has moved from the reader to the tool.&lt;/p&gt;

&lt;p&gt;The product behavior on this page comes from Tableau's own documentation, which is the authority on it. Create hierarchies (Tableau Help, current version) is the source for the drag-and-drop creation step, the ability to add and re-order fields, the plus and minus drill controls, the Create Hierarchy route for fields inside folders, and the statement about what Remove Hierarchy does to the fields. Where a secondary write-up disagrees with that page, the page wins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using this on your own workbook
&lt;/h2&gt;

&lt;p&gt;Open your Data pane and read the dimension list. How many pairs are levels of the same thing, sitting next to each other unconnected?&lt;/p&gt;

&lt;p&gt;Doing this to a whole workbook at once is dull and you'll stop. Take one, in this order.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Find a pair where one contains the other.&lt;/strong&gt; Region and country. Category and sub-category. Manager and rep. If neither contains the other, it isn't a hierarchy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the containment in the data, not the org chart.&lt;/strong&gt; Count the distinct parents per child. More than one means drilling will double-count, and you have a data question before you have a hierarchy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drag the finer field onto the coarser one.&lt;/strong&gt; Fine onto coarse. Getting this backwards is the whole failure mode.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Name it for a reader, not for you.&lt;/strong&gt; "Product hierarchy" is fine. The name shows up in the Data pane and other people will read it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Save the view at the top level.&lt;/strong&gt; Most readers never drill, so the state you save is the state almost everyone sees.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Click down to the bottom level once, and look.&lt;/strong&gt; If the chart is unreadable at full depth, either the chart type is wrong or that level does not belong in the hierarchy.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you have paper nearby and five spare minutes, there's one drawing worth doing and it's optional. Write your three levels as three rows, then write the number of distinct values in each row next to it. Seeing 3, 9 and 39 in your own handwriting is what makes the bottom-level chart problem obvious before you build it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The whole thing on one screen
&lt;/h2&gt;

&lt;p&gt;This is the retrieval sheet. Cover the right column, work down the left, and say each answer out loud before you check it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hierarchy&lt;/td&gt;
&lt;td&gt;Orders several fields into levels and hands the reader a drill control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How to build one&lt;/td&gt;
&lt;td&gt;Drag a field and drop it directly on top of another field in the Data pane&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Field inside a folder&lt;/td&gt;
&lt;td&gt;Right-click the field and select Create Hierarchy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adding levels&lt;/td&gt;
&lt;td&gt;Drag more fields into the hierarchy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reordering levels&lt;/td&gt;
&lt;td&gt;Drag a field to a new position inside the hierarchy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Order rule&lt;/td&gt;
&lt;td&gt;Coarse at the top, fine at the bottom. Each level contained by the one above.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plus and minus icons&lt;/td&gt;
&lt;td&gt;Drill up or down, adding or subtracting a level of detail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drilling adds&lt;/td&gt;
&lt;td&gt;The parent level stays on the shelf. You get both, nested.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Removing a hierarchy&lt;/td&gt;
&lt;td&gt;Right-click, Remove Hierarchy. The fields survive as ordinary fields.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where it lives&lt;/td&gt;
&lt;td&gt;The Data pane, so it belongs to the data source and works on every sheet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Date fields&lt;/td&gt;
&lt;td&gt;Get year, quarter, month and day automatically. No build needed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hierarchy against group&lt;/td&gt;
&lt;td&gt;Hierarchy arranges fields. A group merges members inside one field.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hierarchy against set&lt;/td&gt;
&lt;td&gt;A set labels members IN or OUT of one field. No levels involved.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hierarchy against bin&lt;/td&gt;
&lt;td&gt;A bin cuts a continuous measure into equal-width buckets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What it does not do&lt;/td&gt;
&lt;td&gt;Enforce containment. Bad data drills into double counting silently.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The one habit to keep.&lt;/strong&gt; Before you build a hierarchy, count the distinct parents for each child. One parent means it's a hierarchy. More than one means drilling will double-count, and you've found a data problem worth more than the drill control was.&lt;/p&gt;

&lt;p&gt;One last thought, and I'd like other people's answers. What made these click for me was the fields-against-values line: a hierarchy is the only one of the four that touches more than one field, and once that landed I stopped mixing it up with groups. What's the pair of Tableau features you had to keep looking up before something finally separated them?&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Tableau. Create hierarchies. &lt;em&gt;Tableau Desktop and Web Authoring Help&lt;/em&gt; , current version. help.tableau.com. The authority for the product behavior described here.&lt;/li&gt;
&lt;li&gt;Shneiderman, B. (1996). The eyes have it: A task by data type taxonomy for information visualizations. &lt;em&gt;Proceedings of the 1996 IEEE Symposium on Visual Languages&lt;/em&gt; , 336–343.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on Analyst Prep Kit: &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/tableau-hierarchies/" rel="noopener noreferrer"&gt;Tableau Hierarchies: One Drag Puts a Drill-Down on Every Chart&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Visit the site for more beginner data analysis guides and free resources: &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/" rel="noopener noreferrer"&gt;the full guide archive&lt;/a&gt; covers SQL, Excel, Power BI, Tableau, Python and statistics, and the &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/" rel="noopener noreferrer"&gt;practice kits&lt;/a&gt; run in your browser with nothing to install.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If it was useful: &lt;a href="https://buymeacoffee.com/michaelnocito" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>tableau</category>
      <category>datavisualization</category>
      <category>dataanalysis</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Tableau Dashboard Extensions: What They Add, and What They Can Read</title>
      <dc:creator>Michael Nocito</dc:creator>
      <pubDate>Sat, 05 Sep 2026 13:00:06 +0000</pubDate>
      <link>https://dev.to/michaelnocito/tableau-dashboard-extensions-what-they-add-and-what-they-can-read-45jg</link>
      <guid>https://dev.to/michaelnocito/tableau-dashboard-extensions-what-they-add-and-what-they-can-read-45jg</guid>
      <description>&lt;p&gt;By &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/about.html" rel="noopener noreferrer"&gt;Michael Nocito&lt;/a&gt;, data analyst · Published August 9, 2026&lt;/p&gt;

&lt;p&gt;By the end of this page you can add an extension to a dashboard, tell the two hosting kinds apart, and read the permission box well enough to know what you're agreeing to. You'll also know the one behavior that surprises people after publishing, which is what an extension looks like in a PDF. It's about twelve minutes.&lt;/p&gt;

&lt;p&gt;Here's what to do before you add your first one. Find out where it runs. An extension you drop onto a dashboard is a web application, and some of them are hosted on Tableau-managed servers while others are hosted by whoever built them. That single fact decides how much thought the rest of the decision needs.&lt;/p&gt;

&lt;p&gt;The short version: an extension is a third-party web application running inside a dashboard object, and one of the two permission levels gives it your full underlying data along with table and field names.&lt;/p&gt;

&lt;p&gt;Where the code actually runs is the thing the panel doesn't show you, so it gets the picture.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The original carries a diagram here. In words: A large rectangle labeled your dashboard contains four panels that all look alike. Three of them are shaded the same and marked as ordinary views. The fourth, in the lower right and outlined in a warning color, is labeled extension. A line runs from that fourth panel, crosses the boundary of the dashboard rectangle, and continues out to a separate box drawn outside and to the right labeled third-party host. The three ordinary views have no lines leaving the rectangle. The drawing shows that the extension panel sits inside the dashboard visually while its code and its data traffic reach outside it, which the other three panels never do.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. What an extension actually is
&lt;/h2&gt;

&lt;p&gt;Before the explanation: you drop an extension onto a dashboard and it draws a chart type Tableau doesn't have. Where did that chart come from?&lt;/p&gt;

&lt;p&gt;From a web application, written by somebody else, running inside a panel on your dashboard. Tableau's own description is that extensions "let you add unique features to dashboards or directly integrate them with applications outside Tableau," and that they are "web applications created by third-party developers."&lt;/p&gt;

&lt;p&gt;That's a bigger idea than a plugin. A worksheet is Tableau drawing your data. An extension panel is a separate application that Tableau has given a rectangle to, and which can be given access to the data behind the dashboard.&lt;/p&gt;

&lt;p&gt;The things people use them for are genuinely useful and genuinely outside what a worksheet does: write-back to a database from inside the dashboard, chart types Tableau doesn't ship, integration with a planning or ticketing tool, or a control that changes several sheets at once in a way parameters can't.&lt;/p&gt;

&lt;p&gt;Extensions are added as a dashboard object, which puts them in the same family as a text box, an image or a web page object rather than in the same family as a sheet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The sentence to remember.&lt;/strong&gt; An extension is somebody else's application borrowing a rectangle on your dashboard. Everything else on this page follows from taking that literally.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The two kinds, and where the code runs
&lt;/h2&gt;

&lt;p&gt;Before the distinction: two extensions do the same job and look identical on the dashboard. What could make one a much easier decision than the other?&lt;/p&gt;

&lt;p&gt;Who is hosting it. Tableau splits extensions by exactly that, and it's the first thing to establish about any one you're considering.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Kind&lt;/th&gt;
&lt;th&gt;Where it runs&lt;/th&gt;
&lt;th&gt;What that means for you&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tableau Trusted&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tableau-managed hosts. Reviewed by Tableau, and includes ones built by Tableau and by Exchange Partners through Tableau's review service.&lt;/td&gt;
&lt;td&gt;Someone with a stake in it has looked at the code, and the hosting is not a stranger's&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Network-enabled&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A third-party host. The developer manages delivery without Tableau in the middle.&lt;/td&gt;
&lt;td&gt;Your judgment about that developer is the whole safeguard&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There's also a sandboxed option, which runs an extension in an isolated environment with no access to the network outside. It's the tightest of the choices, and it needs Tableau Server 2019.4 or later.&lt;/p&gt;

&lt;p&gt;Say out loud why hosting matters more than what the extension displays, before reading on. It's because the panel's appearance is authored by the same people you'd be trusting. A polished panel is evidence of design effort and nothing else. Where it runs is a fact about the software rather than a claim by it.&lt;/p&gt;

&lt;p&gt;None of that makes network-enabled extensions a bad choice. Plenty of good tools are delivered that way, and being outside Tableau's review service is not a mark against a developer. It means the question moves to you, and the question is the ordinary one you'd ask about any vendor handling company data.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. What the permission box is really asking
&lt;/h2&gt;

&lt;p&gt;Before the answer: an extension asks for permission and you click allow. What did you just agree to?&lt;/p&gt;

&lt;p&gt;It depends which of two things it asked for, and the difference is large. Tableau's model has you "allow or deny the dashboard extension access to data in the workbook," and a network-enabled extension may request the higher level, called Full Data Access.&lt;/p&gt;

&lt;p&gt;Full Data Access includes access to full underlying data, plus table and field names from the data sources. Read that as a sentence about your database rather than about your chart. Field and table names are the shape of your systems, and underlying data is the rows themselves rather than the totals on screen.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What it can reach&lt;/th&gt;
&lt;th&gt;Summary data&lt;/th&gt;
&lt;th&gt;Full Data Access&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The aggregated numbers shown in the view&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The underlying rows behind those numbers&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Table and field names from the data source&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The practical rule falls out of the table. An extension that draws a chart of what's already on screen has no need for the underlying rows. If one asks for Full Data Access and its job is drawing a picture of a summary, that mismatch is the thing to ask about, and asking is reasonable rather than paranoid.&lt;/p&gt;

&lt;p&gt;If you change your mind later, Tableau has Reset Permissions, so an early allow is not permanent. Worth knowing before you spend an afternoon worrying about a click you already made.&lt;/p&gt;

&lt;p&gt;Picture the dashboards you've built at work. If one of their extensions could read every underlying row, whose data would that be, and would they know? That question is the whole of this section.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Adding one, and what it needs to work
&lt;/h2&gt;

&lt;p&gt;Before the steps: your extension panel is blank on a colleague's machine and fine on yours. What's the most likely cause?&lt;/p&gt;

&lt;p&gt;Usually one of two things, and both are in this section. Adding an extension is three steps.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open a dashboard sheet in a Tableau workbook.&lt;/li&gt;
&lt;li&gt;Drag an Extension object from the Objects section onto the dashboard.&lt;/li&gt;
&lt;li&gt;Either search for an extension, or browse locally for a &lt;code&gt;.trex&lt;/code&gt; file you've already downloaded.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That &lt;code&gt;.trex&lt;/code&gt; file is the manifest, meaning it's the small file that tells Tableau where the extension lives and what it's called. It isn't the extension itself, which is the point people miss when they wonder why such a small file added such a large feature.&lt;/p&gt;

&lt;p&gt;Three requirements, each of which produces a blank or missing panel when unmet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JavaScript must be enabled in Tableau Desktop.&lt;/strong&gt; It's a dashboard setting, and an extension cannot run without it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An administrator can disable extensions entirely.&lt;/strong&gt; On Tableau Server and Tableau Cloud this is a setting someone else controls, so an extension that works on your machine can be blocked in the environment you publish to. Check before you design a dashboard around one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sandboxed extensions need Tableau Server 2019.4 or later.&lt;/strong&gt; An older server is a hard stop rather than a degraded experience.&lt;/p&gt;

&lt;p&gt;Then the one that catches people after publishing. On Tableau Server and Tableau Cloud, extension objects appear blank in prints, PDFs and images. So a dashboard that gets exported for a monthly pack has a blank rectangle where the extension was, and nobody finds out until the pack is circulated.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. When to reach for one, and when not to
&lt;/h2&gt;

&lt;p&gt;Before the list: an extension would solve your problem in ten minutes. What's worth checking before you use it?&lt;/p&gt;

&lt;p&gt;Whether Tableau already does it, because a native answer costs no permission decision, no hosting question and no blank box in the PDF.&lt;/p&gt;

&lt;p&gt;Extensions earn their place when the job is genuinely outside Tableau: writing data back to a source, a chart type that isn't in the Show Me options, or connecting the dashboard to another system your team works in. That's real capability and there's no native substitute.&lt;/p&gt;

&lt;p&gt;Three cases where the native route is the better answer, and all three are common.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you want&lt;/th&gt;
&lt;th&gt;Native answer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;One control that drives several sheets&lt;/td&gt;
&lt;td&gt;A parameter, or a dashboard action&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Readers changing which group is highlighted&lt;/td&gt;
&lt;td&gt;A &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/tableau-sets/" rel="noopener noreferrer"&gt;set action&lt;/a&gt;, which updates set membership from the dashboard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drill-down from summary to detail&lt;/td&gt;
&lt;td&gt;A &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/tableau-hierarchies/" rel="noopener noreferrer"&gt;hierarchy&lt;/a&gt;, which puts the control on the pill for free&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two more things worth deciding before you commit, both about the dashboard rather than the extension. An extension panel occupies space, and space on a dashboard is the scarce thing: the &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/tableau-sheets-to-dashboard/" rel="noopener noreferrer"&gt;two or three views&lt;/a&gt; guidance applies to a panel that isn't a view either. And an extension is a dependency, so a dashboard built around one stops working properly if the developer stops maintaining it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the permission box is the hard part
&lt;/h2&gt;

&lt;p&gt;The technical steps here are easy and the judgment is not, and that split is worth naming because it's where the real risk sits. The problem isn't that the permission box is hidden. It's shown, clearly, at the moment you add the extension. The problem is that permission prompts are a weak way to get a good decision out of anyone.&lt;/p&gt;

&lt;p&gt;Felt and colleagues studied exactly this on Android, where users see a permission list before installing an application. Their conclusion was that the permission warnings did not help most users make correct security decisions, while a notable minority did both notice them and understand them reasonably well (Felt, Ha, Egelman, Haney, Chin, &amp;amp; Wagner, 2012, &lt;em&gt;Proceedings of the Eighth Symposium on Usable Privacy and Security&lt;/em&gt;). The finding generalises past Android because the mechanism is the same: a prompt arrives when someone has already decided they want the thing, and it asks them to weigh a cost they can't see against a benefit they came for.&lt;/p&gt;

&lt;p&gt;The fix that follows is not to concentrate harder at the prompt. It's to move the decision earlier, to a moment when you haven't yet committed. Establish where an extension is hosted and what it needs access to before you drag it onto the dashboard, and the box at the end becomes a confirmation of something you already decided rather than the decision itself.&lt;/p&gt;

&lt;p&gt;The product behavior on this page comes from Tableau's own documentation, which is the authority on it. Use dashboard extensions (Tableau Help, current version) is the source for the definition of an extension, the trusted and network-enabled distinction, the Full Data Access description, Reset Permissions, the three steps for adding one, the JavaScript requirement, the administrator control, the Tableau Server 2019.4 requirement for sandboxed extensions, and the statement that extension objects appear blank in prints, PDFs and images. Where a secondary write-up disagrees with that page, the page wins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using this on your own dashboard
&lt;/h2&gt;

&lt;p&gt;Think about any dashboard you've published with an extension on it. Could you say today who hosts that extension and what data it was granted?&lt;/p&gt;

&lt;p&gt;Do this per extension rather than as a project, in this order.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ask whether Tableau already does it.&lt;/strong&gt; Parameters, dashboard actions, set actions and hierarchies cover more of the common asks than people expect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Establish the hosting before you install.&lt;/strong&gt; Tableau Trusted, network-enabled or sandboxed. This is a fact you can look up, not a judgment call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decide what level of access the job actually needs&lt;/strong&gt; , then compare it with what's requested. A mismatch is a question, not necessarily a problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check your environment allows extensions at all&lt;/strong&gt; , and check the server version if you need a sandboxed one. Both are hard stops and both are cheap to check first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Export the dashboard to PDF before you call it finished.&lt;/strong&gt; The blank rectangle is much better discovered by you than by the person who circulates the monthly pack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write down what you granted and why&lt;/strong&gt; , next to the workbook. Six months later, nobody remembers, and Reset Permissions is a much easier conversation with a note beside it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you have paper and five spare minutes, there's one sketch worth doing and it's optional. Draw your dashboard as boxes, then draw a line out of the page from every box whose contents come from outside your organization. Most dashboards produce no lines at all, which is exactly why the one that does deserves a minute of thought.&lt;/p&gt;

&lt;h2&gt;
  
  
  The whole thing on one screen
&lt;/h2&gt;

&lt;p&gt;This is the retrieval sheet. Cover the right column, work down the left, and say each answer out loud before you check it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dashboard extension&lt;/td&gt;
&lt;td&gt;A third-party web application running in a dashboard object&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What it adds&lt;/td&gt;
&lt;td&gt;Features outside Tableau, or integration with an outside application&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tableau Trusted&lt;/td&gt;
&lt;td&gt;Reviewed by Tableau and deployed on Tableau-managed hosts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network-enabled&lt;/td&gt;
&lt;td&gt;Hosted by a third party, delivered without Tableau in the middle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sandboxed&lt;/td&gt;
&lt;td&gt;Runs isolated, with no outside network access. Needs Tableau Server 2019.4 or later.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Permission choice&lt;/td&gt;
&lt;td&gt;Allow or deny the extension access to data in the workbook&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full Data Access&lt;/td&gt;
&lt;td&gt;Full underlying data, plus table and field names from the data sources&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Changed your mind&lt;/td&gt;
&lt;td&gt;Reset Permissions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adding one&lt;/td&gt;
&lt;td&gt;Drag an Extension object from Objects, then search or browse for a .trex file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.trex file&lt;/td&gt;
&lt;td&gt;The manifest. It points at the extension, it is not the extension.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requirement&lt;/td&gt;
&lt;td&gt;JavaScript enabled in Tableau Desktop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Administrator control&lt;/td&gt;
&lt;td&gt;Extensions can be disabled entirely on Server and Cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;In a PDF or image&lt;/td&gt;
&lt;td&gt;Extension objects appear blank on Server and Cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reach for native first&lt;/td&gt;
&lt;td&gt;Parameters, dashboard actions, set actions, hierarchies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The standing cost&lt;/td&gt;
&lt;td&gt;A dependency on someone else continuing to maintain it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The one habit to keep.&lt;/strong&gt; Decide where an extension is hosted and what access it needs before you drag it onto the dashboard. Making the call while you still have nothing invested is the only reliable way to make it well, and it turns the permission box into a confirmation instead of a decision.&lt;/p&gt;

&lt;p&gt;One last thought, and I'd like other people's answers. The detail that changed how I think about these is the blank rectangle in a PDF, because it means the dashboard behaves differently depending on how someone consumes it, and nothing warns you. What's the extension you've actually found worth the dependency?&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Tableau. Use dashboard extensions. &lt;em&gt;Tableau Desktop and Web Authoring Help&lt;/em&gt; , current version. help.tableau.com. The authority for the product behavior described here.&lt;/li&gt;
&lt;li&gt;Felt, A. P., Ha, E., Egelman, S., Haney, A., Chin, E., &amp;amp; Wagner, D. (2012). Android permissions: User attention, comprehension, and behavior. &lt;em&gt;Proceedings of the Eighth Symposium on Usable Privacy and Security (SOUPS '12)&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on Analyst Prep Kit: &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/tableau-extensions/" rel="noopener noreferrer"&gt;Tableau Dashboard Extensions: What They Add, and What They Can Read&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Visit the site for more beginner data analysis guides and free resources: &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/" rel="noopener noreferrer"&gt;the full guide archive&lt;/a&gt; covers SQL, Excel, Power BI, Tableau, Python and statistics, and the &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/" rel="noopener noreferrer"&gt;practice kits&lt;/a&gt; run in your browser with nothing to install.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If it was useful: &lt;a href="https://buymeacoffee.com/michaelnocito" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>tableau</category>
      <category>datavisualization</category>
      <category>dataanalysis</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Tableau Desktop Foundations Exam: 40 Questions, and 20 Right Passes It</title>
      <dc:creator>Michael Nocito</dc:creator>
      <pubDate>Fri, 04 Sep 2026 13:00:31 +0000</pubDate>
      <link>https://dev.to/michaelnocito/tableau-desktop-foundations-exam-40-questions-and-20-right-passes-it-5gh3</link>
      <guid>https://dev.to/michaelnocito/tableau-desktop-foundations-exam-40-questions-and-20-right-passes-it-5gh3</guid>
      <description>&lt;p&gt;By &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/about.html" rel="noopener noreferrer"&gt;Michael Nocito&lt;/a&gt;, data analyst · Published August 9, 2026&lt;/p&gt;

&lt;p&gt;By the end of this page you'll know exactly what the Salesforce Certified Tableau Desktop Foundations exam asks, how it's scored, which version of Tableau it tests, and which of its four sections is worth more than double another. Every figure comes from Salesforce's own exam guide, checked on 9 August 2026. It takes about twelve minutes.&lt;/p&gt;

&lt;p&gt;Here's what to do with it today. Before you open another lesson, put the four section weights next to your calendar and split your remaining study hours in the same proportion. Section 2 is 37% of the exam, which works out to about 15 of the 40 scored questions. You need 20 correct to pass. That one section is three quarters of a pass on its own.&lt;/p&gt;

&lt;p&gt;The short version: 40 scored questions, 70 minutes, and 48% passes. The four sections are not the same size, and the published weights tell you where your hours belong.&lt;/p&gt;

&lt;p&gt;The sizes are the whole reason to read this, so they get the picture.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The original carries a diagram here. In words: Four horizontal bars, one per exam section, each drawn to the length of its published percentage weight. From top to bottom: Connecting to and Preparing Data at 23 percent, Exploring and Analyzing Data at 37 percent and clearly the longest bar, Sharing Insights at 25 percent, and Understanding Tableau Concepts at 15 percent and clearly the shortest. The Exploring bar is roughly two and a half times the length of the Understanding bar. Seen side by side at true scale, the four sections are visibly unequal, which is the point: an hour of study is worth more in some of them than in others.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Where these numbers come from.&lt;/strong&gt; Salesforce publishes the exam guide as help article 005298988, and that page is the authority. Third-party summaries of this exam disagree with it. One widely repeated version says 45 questions in 60 minutes with a 750 out of 1000 passing score, and all three of those figures are wrong. Check the Salesforce page yourself before you trust any number about this exam, including the ones on this page.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The format, in numbers you can plan around
&lt;/h2&gt;

&lt;p&gt;Before the table: 70 minutes and 40 questions. How long is that per question, and does it feel tight to you?&lt;/p&gt;

&lt;p&gt;It's about a minute and three quarters each, and for most people it isn't tight at all. Here is the full format.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Thing&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Scored questions&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unscored questions&lt;/td&gt;
&lt;td&gt;Up to 5 more, mixed in unmarked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Question types&lt;/td&gt;
&lt;td&gt;Multiple choice and multiple select&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time&lt;/td&gt;
&lt;td&gt;70 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Passing score&lt;/td&gt;
&lt;td&gt;48% on the English version, 55% on the Japanese version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Registration fee&lt;/td&gt;
&lt;td&gt;US$75, plus local tax&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retake fee&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prerequisite&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tableau version tested&lt;/td&gt;
&lt;td&gt;2022.3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two of those rows do real work on exam day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The unscored questions are invisible.&lt;/strong&gt; You could see 45 questions and only 40 of them count, and nothing on screen tells you which five are which. So a question that feels strange or unusually hard may simply not be scored. Answer it and move on. There's no way to spot them and no benefit to trying.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every question is multiple choice or multiple select.&lt;/strong&gt; There is nothing to build. You will not be dropped into a live copy of Tableau and asked to make a chart. That changes how you should practice, and it's the single biggest planning fact on this page. Reading and recognizing beats building, for this exam only.&lt;/p&gt;

&lt;p&gt;On timing, work the arithmetic yourself rather than trusting the feeling. 70 minutes is 4,200 seconds. Divide by 45, the worst case where every unscored question shows up, and you get 93 seconds a question. Divide by 40 and you get 105 seconds. Either way you have well over a minute for a question with four short options.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Where the marks are, converted into questions
&lt;/h2&gt;

&lt;p&gt;Before the numbers: the four sections are weighted 23, 37, 25 and 15 percent. Which one would you study first?&lt;/p&gt;

&lt;p&gt;Percentages are hard to plan against, so turn them into questions. Multiply each weight by the 40 scored questions.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Section&lt;/th&gt;
&lt;th&gt;Weight&lt;/th&gt;
&lt;th&gt;Arithmetic&lt;/th&gt;
&lt;th&gt;Scored questions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. Connecting to and Preparing Data&lt;/td&gt;
&lt;td&gt;23%&lt;/td&gt;
&lt;td&gt;0.23 × 40 = 9.2&lt;/td&gt;
&lt;td&gt;about 9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. Exploring and Analyzing Data&lt;/td&gt;
&lt;td&gt;37%&lt;/td&gt;
&lt;td&gt;0.37 × 40 = 14.8&lt;/td&gt;
&lt;td&gt;about 15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. Sharing Insights&lt;/td&gt;
&lt;td&gt;25%&lt;/td&gt;
&lt;td&gt;0.25 × 40 = 10&lt;/td&gt;
&lt;td&gt;about 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. Understanding Tableau Concepts&lt;/td&gt;
&lt;td&gt;15%&lt;/td&gt;
&lt;td&gt;0.15 × 40 = 6&lt;/td&gt;
&lt;td&gt;about 6&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Those four add to 40, which is the check that the conversion is right. Salesforce doesn't promise an exact count per section, so treat these as the shape rather than a guarantee.&lt;/p&gt;

&lt;p&gt;Now the sentence worth carrying into your study plan. Section 2 is about 15 questions and you need 20 to pass, so section 2 alone is three quarters of a pass. Section 4 is about 6 questions, which is less than a third of one.&lt;/p&gt;

&lt;p&gt;Say out loud what that means for an hour of your time, before reading on. An hour spent on exploring and analyzing is worth roughly two and a half hours spent on Tableau concepts, because 37 divided by 15 is about 2.5. This is the hinge of the whole page, and it's the reason to read a weighting table at all.&lt;/p&gt;

&lt;p&gt;What section 2 covers is the daily work of the tool: filtering, sorting, grouping, &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/tableau-sets/" rel="noopener noreferrer"&gt;sets&lt;/a&gt;, &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/tableau-hierarchies/" rel="noopener noreferrer"&gt;hierarchies&lt;/a&gt;, calculations, table calculations, and mapping. If you want a single place to start, start there. That is also why four of the six Tableau guides on this site sit inside that section.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. What a 48% pass mark actually buys you
&lt;/h2&gt;

&lt;p&gt;Before the answer: 48% of 40 questions. How many can you get wrong and still pass?&lt;/p&gt;

&lt;p&gt;Twenty. The arithmetic is 0.48 × 40 = 19.2, and since there is no such thing as a fifth of a question, you need 20 correct. That leaves 20 you can miss.&lt;/p&gt;

&lt;p&gt;That is a genuinely forgiving mark, and it's worth knowing for one reason only: it should stop you postponing the booking. People delay a certification for months waiting to feel ready, and 48% is not a mark that requires feeling ready. It requires knowing half the material.&lt;/p&gt;

&lt;p&gt;What it should not do is turn into a plan to skip a section. Twenty wrong sounds like a wide margin until you notice that multiple-select questions usually need every box right to score, and that the questions you feel confident about are the ones you're most likely to misread. Aim well above the line, and treat the 48% as permission to book rather than permission to prepare less.&lt;/p&gt;

&lt;p&gt;The retake is free, which changes the risk further. A first attempt costs $75 and a second costs nothing, so the real cost of being wrong about your readiness is time, not money.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The version it tests, and why that saves you time
&lt;/h2&gt;

&lt;p&gt;Before the explanation: the exam guide says it currently tests on Tableau 2022.3. Why would that be good news?&lt;/p&gt;

&lt;p&gt;Because it puts a fence around what you have to learn. Anything Tableau shipped after 2022.3 is outside the exam, so a newer feature you read about in a release note or a recent video is not something you need for this.&lt;/p&gt;

&lt;p&gt;That matters more than it sounds. Tableau ships several releases a year, and the tutorials you find by searching are written against whatever version was current when the author wrote them. A 2026 walkthrough of a 2025 feature is good learning and irrelevant preparation.&lt;/p&gt;

&lt;p&gt;Two practical consequences. First, if a menu in your copy of Tableau doesn't match a screenshot in your study material, the exam is more likely to follow the older wording. Second, when your notes and a video disagree, Tableau's own help documentation is the tiebreaker, not the video.&lt;/p&gt;

&lt;p&gt;Picture your own study list for a moment. How many items on it are features you've only ever seen in a recent release note? Those are the ones to move to the bottom.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Details people miss
&lt;/h2&gt;

&lt;p&gt;Before the list: someone tells you a wrong answer costs you marks. Are they right?&lt;/p&gt;

&lt;p&gt;No. Nothing in the published guide describes a penalty for a wrong answer, and the score is a percentage of correct responses. So there is never a reason to leave a question blank. Answer every one, including the ones you're guessing at.&lt;/p&gt;

&lt;p&gt;Five more worth knowing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The exam was renamed.&lt;/strong&gt; This is the exam that used to be called Tableau Desktop Specialist. Older study material, forum posts and course titles still use the old name, and they're usually talking about the same thing. Check the publication date and the section weights before trusting any of it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multiple select tells you how many to pick.&lt;/strong&gt; These questions say how many answers to choose. Read that number before reading the options, because choosing two when it asked for three is a wrong answer that felt right.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The passing score differs by language.&lt;/strong&gt; 48% on the English version, 55% on the Japanese. If you're reading a study group's advice, check which version they sat.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The section names are the syllabus.&lt;/strong&gt; "Connecting to and Preparing Data" is not a category invented for the score report. It's a list of the things you'll be asked about, and the full exam guide breaks each section into numbered objectives. Reading those objective lists is the cheapest hour of preparation available.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Certification maintenance is published separately and it changes.&lt;/strong&gt; The exam guide itself doesn't state an expiry date for this credential, and Salesforce keeps maintenance requirements on their own help pages, which were revised for the Winter '26 release. Do not assume a credential lasts forever because a blog said so. Check the maintenance page after you pass, and diarise it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why studying to the weights works
&lt;/h2&gt;

&lt;p&gt;Splitting your hours by the published weights is not just tidy. It's the only way to make an hour of study comparable to another hour, because an exam that weights its sections unequally is telling you the exchange rate in advance. Most people never look it up, and then spread their time evenly, which quietly overpays for the smallest section.&lt;/p&gt;

&lt;p&gt;Two findings from the memory research change how the hours themselves should be spent, and both are about how you practice rather than what you read.&lt;/p&gt;

&lt;p&gt;The first is that testing yourself beats re-reading. Roediger and Karpicke had students study passages and then either restudy them or take a test on them. On a retention test a week later, the students who had been tested remembered substantially more than the students who had restudied, even though restudying felt more productive at the time (Roediger &amp;amp; Karpicke, 2006, &lt;em&gt;Psychological Science&lt;/em&gt; , 17(3), 249–255). For a multiple-choice exam that is close to a free result: practice questions are both the preparation and the format.&lt;/p&gt;

&lt;p&gt;The second is that the same total hours produce more if you spread them out. Cepeda and colleagues reviewed 254 studies covering more than 14,000 participants and found spaced practice reliably beat massed practice on later recall (Cepeda, Pashler, Vul, Wixted, &amp;amp; Rohrer, 2006, &lt;em&gt;Psychological Bulletin&lt;/em&gt; , 132(3), 354–380). Six hours across six evenings beats six hours on a Sunday, at no extra cost.&lt;/p&gt;

&lt;p&gt;Put those together with the weights and you get the whole method. Split the hours by section weight, spread them across days, and spend them answering questions rather than reading about answers.&lt;/p&gt;

&lt;p&gt;On the exam facts themselves, Salesforce's published exam guide is the authority and it is the only source used above. Where a course description, a video or a summary disagrees with it, that page wins. This matters more than usual here, because the most commonly repeated third-party figures for this exam are wrong on the question count, the time limit and the passing score at the same time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building your own study order
&lt;/h2&gt;

&lt;p&gt;Think about the last certification or test you prepared for. Did you know the section weights before you started, or did you find them afterwards on the score report?&lt;/p&gt;

&lt;p&gt;Do this in order. It takes about twenty minutes and it's the only planning this exam needs.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Read the four section names and their numbered objectives&lt;/strong&gt; in the Salesforce exam guide. That list is the syllabus, and reading it is faster than any summary of it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Split your remaining hours 23 / 37 / 25 / 15.&lt;/strong&gt; If you have twenty hours left, that's about 5, 7, 5 and 3. Write those four numbers down where you'll see them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spend the first block on section 2&lt;/strong&gt; , the exploring and analyzing work. It's the biggest section and it's also the part that makes the tool useful after the exam, so none of that time is exam-only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Answer questions rather than read notes&lt;/strong&gt; , from the first evening. Getting one wrong early is the cheapest way to find out what you don't know.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spread the blocks across days, not into one weekend.&lt;/strong&gt; Same hours, more retained, and it costs nothing to schedule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Book the exam before you feel ready.&lt;/strong&gt; 48% and a free retake is a low-risk first attempt, and a booked date is what turns a study plan into study.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you have paper nearby, there's one sketch worth five minutes and it's optional. Draw the four sections as four bars at their real weights, then draw your last week of study as four bars beside them. Whichever pair is furthest apart is your next study session.&lt;/p&gt;

&lt;h2&gt;
  
  
  The whole thing on one screen
&lt;/h2&gt;

&lt;p&gt;This is the retrieval sheet. Cover the right column, work down the left, and say each answer out loud before you check it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Answer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Scored questions&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unscored questions&lt;/td&gt;
&lt;td&gt;Up to 5, mixed in, unmarked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time&lt;/td&gt;
&lt;td&gt;70 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Seconds per question&lt;/td&gt;
&lt;td&gt;93 at 45 questions, 105 at 40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Passing score&lt;/td&gt;
&lt;td&gt;48% English, 55% Japanese&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Questions needed to pass&lt;/td&gt;
&lt;td&gt;20 of 40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Questions you can miss&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fee&lt;/td&gt;
&lt;td&gt;US$75, retake free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prerequisite&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Version tested&lt;/td&gt;
&lt;td&gt;Tableau 2022.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Question format&lt;/td&gt;
&lt;td&gt;Multiple choice and multiple select. Nothing to build.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Section 1, Connecting and Preparing&lt;/td&gt;
&lt;td&gt;23%, about 9 questions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Section 2, Exploring and Analyzing&lt;/td&gt;
&lt;td&gt;37%, about 15 questions. The biggest.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Section 3, Sharing Insights&lt;/td&gt;
&lt;td&gt;25%, about 10 questions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Section 4, Tableau Concepts&lt;/td&gt;
&lt;td&gt;15%, about 6 questions. The smallest.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Former name&lt;/td&gt;
&lt;td&gt;Tableau Desktop Specialist&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Penalty for a wrong answer&lt;/td&gt;
&lt;td&gt;None published. Never leave one blank.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The one habit to keep.&lt;/strong&gt; Before preparing for any weighted exam, convert the percentages into question counts and split your hours the same way. It takes five minutes, it works on every certification you'll ever sit, and almost nobody does it.&lt;/p&gt;

&lt;p&gt;One last thought, and I'd like other people's answers. The fact that changed my own preparation was that nothing on this exam is built, only recognized, which makes practice questions worth more than practice workbooks. What's the fact about an exam you wish you'd known before you started studying for it, instead of afterwards?&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Salesforce. Salesforce Certified Tableau Desktop Foundations Exam Guide. Help article 005298988, help.salesforce.com. Checked 9 August 2026. The authority for every exam figure on this page.&lt;/li&gt;
&lt;li&gt;Roediger, H. L., &amp;amp; Karpicke, J. D. (2006). Test-enhanced learning: Taking memory tests improves long-term retention. &lt;em&gt;Psychological Science&lt;/em&gt; , 17(3), 249–255.&lt;/li&gt;
&lt;li&gt;Cepeda, N. J., Pashler, H., Vul, E., Wixted, J. T., &amp;amp; Rohrer, D. (2006). Distributed practice in verbal recall tasks: A review and quantitative synthesis. &lt;em&gt;Psychological Bulletin&lt;/em&gt; , 132(3), 354–380.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on Analyst Prep Kit: &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/tableau-desktop-foundations-exam/" rel="noopener noreferrer"&gt;Tableau Desktop Foundations Exam: 40 Questions, and 20 Right Passes It&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Visit the site for more beginner data analysis guides and free resources: &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/guides/" rel="noopener noreferrer"&gt;the full guide archive&lt;/a&gt; covers SQL, Excel, Power BI, Tableau, Python and statistics, and the &lt;a href="https://michaelnocito.github.io/analyst-prep-kit/" rel="noopener noreferrer"&gt;practice kits&lt;/a&gt; run in your browser with nothing to install.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If it was useful: &lt;a href="https://buymeacoffee.com/michaelnocito" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>tableau</category>
      <category>datavisualization</category>
      <category>dataanalysis</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
