A retention report looks simple and fails in a handful of repeatable ways. Knowing those ways first tells you which assertions are worth writing.
The standard cohort shape is well documented: assign each user to a cohort by their first activity month, count the cohort population, then count distinct active users in each later month and divide by the cohort size per this cohort SQL walkthrough and this retention SQL guide. Every join in that chain is a chance to duplicate rows, drop rows, or produce a null denominator.
The failure modes that actually reach a dashboard:
- A join to an events table matches multiple rows per user per month, so the retained count exceeds the cohort size and the retention rate prints above 100 percent.
- The cohort size is computed from a filtered set while the numerator is computed from an unfiltered one, so the two sides of the ratio describe different populations.
- A test or internal account is included in activity but excluded from signups, which inflates later months.
- A timezone or
DATE_TRUNCmismatch places a user's first event in the wrong month, creating a cohort with a size of zero and a null rate. - Division by zero returns null silently in most engines, so a whole cohort column disappears rather than erroring.
The general discipline of checking completeness, uniqueness, consistency and range before trusting data is the same one described in this SQL data validation primer and in Teradata's overview of validation types. Applying it inside the query rather than to the source tables is what makes the report self-auditing.



