The SQL style guide: conventions that keep team queries readable
Max Musing
Max MusingFounder and CEO of Basedash
· August 4, 2026

Max Musing
Max MusingFounder and CEO of Basedash
· August 4, 2026

A SQL style guide is a short set of rules a team agrees on for how to name, format, and structure queries. It covers things like whether keywords are uppercase or lowercase, how to indent, how to name columns and CTEs, and when to break a query into steps. The point is not that one style is correct. The point is that everyone on the team writes SQL the same way, so any query is fast to read, review, and reuse.
This guide is for analysts, engineers, and operators who write SQL that other people have to read: shared dashboard queries, dbt models, ad hoc analysis that gets pasted into a doc, or a query someone else will edit six months from now. If your SQL only ever runs once and no one else sees it, style matters less. The moment a query is shared, consistency starts saving real time.
If you only take away a few rules, take these:
snake_case for every table, column, and alias.with blocks), not nested subqueries.as when you alias a column or table.Everything below is the longer version, plus where well-known published guides disagree and how to get a team to actually follow the rules.
The value of a style guide shows up in three places.
Review speed. When every query is formatted the same way, a reviewer reads structure instead of decoding formatting. A diff that only changes logic is easy to approve. A diff where someone also re-indented the whole file hides the real change.
Onboarding. A new analyst who inherits a consistent codebase can predict where things are: imports at the top, one CTE per logical step, the final select at the bottom. Inconsistent SQL forces them to re-learn each author’s personal habits.
Reuse. In most teams, queries get copied. A well-named, well-structured query is safe to lift into a new dashboard. A dense, single-block query with aliases like t1 and x gets rewritten from scratch, which is how two slightly different versions of the same metric end up in production.
None of this requires the “best” style. It requires a style everyone shares.
Naming is where a style guide earns its keep, because names are what people read first.
| Element | Convention | Example |
|---|---|---|
| Tables | snake_case, collective or plural noun |
orders, customers |
| Columns | snake_case, singular, no table prefix |
order_total, email |
| Booleans | is_ or has_ prefix |
is_active, has_paid |
| Dates and timestamps | _date or _at suffix |
signup_date, created_at |
| CTEs | verbose, describe the transformation | paid_orders, revenue_by_month |
| Computed columns | name it as if it were a real column | sum(amount) as total_revenue |
A few rules that prevent most naming arguments:
camelCase. It is harder to scan than snake_case, which is the identifier convention both major public guides recommend (sqlstyle.guide, dbt Labs).tbl_ or sp_. The database already knows what is a table.events_joined_to_users describes a step. user_events sounds like a table.Formatting rules are the ones people fight about and the ones a machine can enforce for you. The specific choices matter less than making them once.
A workable default:
select, from, left join). Pick the opposite if you prefer, but decide.join, and each condition in a multi-part where on its own line.Here is the same query written two ways. Messy:
select id,name,sum(amt) total from Orders o join Customers c on o.cid=c.id where c.Country='US' group by 1,2
Clean:
with us_customers as (
select
customer_id,
name
from customers
where country = 'US'
),
customer_orders as (
select
customer_id,
sum(amount) as total_revenue
from orders
group by customer_id
)
select
us_customers.customer_id,
us_customers.name,
customer_orders.total_revenue
from us_customers
left join customer_orders
on us_customers.customer_id = customer_orders.customer_id
Both return the same rows. Only the second one is safe to hand to a teammate.
The single biggest readability win in SQL is building a query out of named steps. A common-table-expression (with block) lets you name each stage of the work and read the query top to bottom.
Two structural conventions from the dbt style guide are worth adopting directly:
Compare this to the alternative, where a select sits inside a from sits inside another select. Nested subqueries force you to read inside-out, and the intermediate results have no names. CTEs read in the order the work happens, and you can comment out the final select and swap in select * from any_cte to inspect any step while you build.
Joins are where ambiguity creeps in, so a few explicit rules help:
inner join, not bare join.orders.customer_id is unambiguous. A bare customer_id in a two-table join makes the reader guess.as for aliases. It is explicit and easy to scan.left join and reorder your from and join so the flow reads in one direction. A right join is often a sign you should swap which table you select from.Aliases are one place where good guides genuinely disagree, covered next.
Good SQL is mostly self-documenting if it is named and structured well, so comments should carry the information the code cannot.
-- exclude internal test accounts, see ticket 4821), a business rule, a known data quirk, or an intentional edge case.-- select the customer id adds nothing next to select customer_id.That last habit pays off most on queries that power a dashboard, where the next person needs to know the grain before they trust the number.
It helps to see that even the most cited public guides make opposite choices. This is the strongest argument for the “pick one and be consistent” principle: there is no universal right answer, only a team default.
| Decision | sqlstyle.guide (Simon Holywell) | dbt Labs |
|---|---|---|
| Keyword case | UPPERCASE (SELECT) |
lowercase (select) |
| Indentation | right-aligned “river” down the middle | 4 spaces, left-aligned |
group by |
list the columns explicitly | group by 1, 2 (by position) |
| Table aliases | short correlation from first letters | avoid initialisms, prefix the full table name |
| Identifiers | snake_case |
snake_case |
Explicit as |
required | required |
They agree on the things that most affect readability (snake_case identifiers, explicit as) and diverge on the things that are mostly taste (case, alignment, group by style). When your team writes its own guide, spend your debate budget on the rows where they agree and just pick a side on the rows where they do not.
One note on the group by row: grouping by position (group by 1, 2) is terse and common in analytics code, but it breaks silently if someone reorders the select list. Grouping by explicit column name is more verbose and more robust. Either is fine. Mixing them in the same codebase is not.
Drop this into your team wiki or the top of your dbt repo and edit the choices you disagree with. The value is having a written default, not the specific rows.
Naming
snake_case for all tables, columns, and aliasesis_/has_ for booleans, _at/_date for time columnstbl_/sp_ prefixes, no camelCaseFormatting
Structure
inner join, left join)as for every aliasComments
A style guide that lives only in a doc gets ignored. Enforcement should be as automatic as possible so no one spends review time arguing about commas.
The habit that ties it together: review AI-generated SQL against the same guide. Assistants produce working SQL that ignores your conventions, so treat their output like any other contribution. Our checklist for reviewing AI-generated SQL covers what to check beyond formatting.
t1, t2, and x save keystrokes and cost every future reader time. Alias to something meaningful.Style is a means, not an end. Loosen it when the cost outweighs the benefit:
select * to eyeball a table does not need CTEs and a header comment.The test is simple: will another person read or edit this query? If yes, style it. If no, do what is fastest.
Should SQL keywords be uppercase or lowercase?
Either works, and the two most-cited public guides split on it: sqlstyle.guide uses uppercase, dbt Labs uses lowercase. What matters is that your team picks one and applies it everywhere. Lowercase has become common in modern analytics codebases because it is faster to type and reads well alongside lowercase identifiers, but uppercase keywords make the SQL skeleton stand out. Choose one, put it in your guide, and let a formatter enforce it.
What is the difference between a SQL style guide and a linter?
The style guide is the set of rules your team agrees on. A linter like SQLFluff is the tool that checks and often auto-fixes SQL against those rules. The guide is the decision, the linter is the enforcement. You want both: the guide so people understand the intent, and the linter so the rules hold up without manual policing in every review.
Should I use CTEs or subqueries?
Prefer CTEs (with blocks) for anything a person will read. CTEs let you name each step and read the query top to bottom, while nested subqueries force inside-out reading and leave intermediate results unnamed. Modern databases plan CTEs and equivalent subqueries similarly in most cases, so the choice is mostly about readability. Reach for a subquery only for a small, obvious, single-use expression.
How do I get a team to follow a SQL style guide?
Automate it. Keep the guide in the repo next to the code, run a formatter and linter like SQLFluff in CI so violations fail the pull request before a human reviews it, and standardize where queries are written and shared so well-styled queries become the copy-paste template. Manual enforcement through code review alone rarely lasts, because reviewers get tired of flagging commas.
Does SQL style matter if a BI tool generates the query?
For the machine-generated SQL itself, no: query builders and BI tools emit SQL that no one hand-edits, so readability rules do not apply to it. Style matters for the SQL your team writes by hand, including the custom queries you write inside a BI tool to power a chart or dashboard. Those are shared, reused, and reviewed like any other code, so the same conventions apply.
Written by

Founder and CEO of Basedash
Max Musing is the founder and CEO of Basedash, an AI-native business intelligence platform designed to help teams explore analytics and build dashboards without writing SQL. His work focuses on applying large language models to structured data systems, improving query reliability, and building governed analytics workflows for production environments.
Basedash lets you build charts, dashboards, and reports in seconds using all your data.